From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pf1-f172.google.com (mail-pf1-f172.google.com [209.85.210.172]) (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 E64B32C3257 for ; Wed, 10 Jun 2026 03:18:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.172 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781061493; cv=none; b=r+CglfW8Q/AjnHJ9uzllvfRfmtSiNDy/w4/FtnUoOvuoxwoupTy0pRwj2nFU91fdof9HQoULrTmVXKvhhgWaUgxvYXuxaPk25uWJJYWycqF1cko3XvU3mmAeohH+/ZmL3to6LAgGwsiSFPBLiWEktoOB96XzsKGMTYGqwPFvszs= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781061493; c=relaxed/simple; bh=NG24U6/5jrStgO2L4zMqEzbYcc1cwAg97rtD1lY3cqQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ja10StHPCN1aIsMvrVzNX9TXl1Tlu54LK9A7U3IOVi03hel1qh4uuiu0F00ht+b31gYE8WFW35+RlPswFKALkIz2X8ZPntTvC2id3Hga1q8w5Ze8PeOJuxpktlJbzwGNBIz+4fz7bgYEBdNBF6/Sr4tTQFuvXGctUkDKIA7X8Ug= 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=FGVSc22H; arc=none smtp.client-ip=209.85.210.172 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="FGVSc22H" Received: by mail-pf1-f172.google.com with SMTP id d2e1a72fcca58-842358aaf36so2335746b3a.2 for ; Tue, 09 Jun 2026 20:18:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781061491; x=1781666291; 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=tVaER5sIXqhj/hybfyD6aUknKi/3+lFXTVdwDopBgB0=; b=FGVSc22HONVjDHgwqK6d+QN62pmMCyMrw233wKWpiN7jI7J1L5SppXjwNEQAjbkzlj UZx83FO4mKI34y24HbyVdmYmGGoZ5f00ikdksOvhcH3C57onZXLHtNEyCjI440zSImCD HDJlpFzyAhN9L8XwwTrLWb8gP4qwWyQ+dPqSLTBE+tJ0jHRQQy2rrjXWq9a9aES93duc 6ovpx0uiuQlBns7E3kOrDbcSsRgRnv8YGnuos+RKK1kyP0f3eegmCPQhhvD6Exgh9WdH nbJeMrt4MVoSBkbCCwCgi8mUx0tbdjDUBQ0tJtSOaxC4RfTKmUS52JVqlS5bVwUYt/Sj I+UA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781061491; x=1781666291; 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=tVaER5sIXqhj/hybfyD6aUknKi/3+lFXTVdwDopBgB0=; b=sg/It1LOP8ZphmPs+/Ke0aFHZUyFfmqP9mlhjMQO+bdV7/IrO4Jcy1uepr1KXrqL6l zHN3yu5lrvfaNwrMnpG1vJrP/qbIX6MfgD7pzgOhe29uPfaKJSRdCKCzNUPgdvez1fnk 98PpfVlx0eC3HRcbid+5IzJy2kxdrmh+hLZlfZJX+tbi4xC512HSAByAVT0AHTw9XIAs wUiAlHT+Y8UlurOmMEtqTCl9aSdDTavXB6rpnPG31TuSBmqhEkL/UBDGQf3fc5rC5jsL JlGa8zZ58JVjoon65p/35lIZCI3TD75rD3A1xHNXLloxxpz5S7/tagC+w5uUyxgR8Npv mk3Q== X-Forwarded-Encrypted: i=1; AFNElJ8qi+f2PuDWzAoxsz0RRronAK50lRny3+LHlbaJC5Geo9MOJ2FkeeIHlaoeWzMgxUglVnU6MX50Uzp0Tdk=@vger.kernel.org X-Gm-Message-State: AOJu0YyDGaF6L22Zih3qCgkRawwHgQt4ct3WpRB/CDTUU7qhkoT614l7 5jorMn+awc5CRhXSszpiZUpWWqZ9GSIjQf7CHbHWk3S8L/NLMAFqH35A X-Gm-Gg: Acq92OFo4mlVl7BU5lfM4zAStDtpLcok8t9kTeZ4WxgqIEvv1zDiU57teryBgZtwTNw Pz0dOgVy6xPubiCS4o6YLUNFOmE6QmS2gzzQc8d+iam8U++rZjLb9K1zCIKnZGdNgVZ7AwXEEWY 8bIYX1d7nSx+N8oQXvjIgQ4dUv9VyrKdD/gdpRsNCSmzE9EwyTLA/4syvgHYf94svnHklg7Wo9O YeuItJbH7glVrrX57Z/cB1tTL2waXnnAM/xT4lgQJWQglWrcp2VCrkqoqcnMo2zedEVyDqxe141 L3Gv1L0zTFCBdifXjOS80nIud8PbARbTbqcUvRlMTxDaXRL8RpjM7jsZ5UyN2ha1olVdShD983f bF6HLLo1MswaQ4eHlAPDEE/ySAmIPxx9ZPnn3EpNCDYIt1gF1hZk/crWM5K9k9gDksyLjRW0OKJ 3SkcOooAtRuyRFVfMZvaFJjvoapWly7ADaDeQZVM0YCW7zPMw6ppQLZ5zAMjo+WDV8Xg== X-Received: by 2002:a05:6a00:114e:b0:83a:7565:3505 with SMTP id d2e1a72fcca58-842b0e12e57mr23778850b3a.8.1781061491065; Tue, 09 Jun 2026 20:18:11 -0700 (PDT) Received: from localhost ([112.204.126.243]) by smtp.googlemail.com with ESMTPSA id d2e1a72fcca58-842828d6bd1sm28281391b3a.44.2026.06.09.20.18.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 09 Jun 2026 20:18:10 -0700 (PDT) From: Alex Yeo To: platform-driver-x86@vger.kernel.org Cc: alexyeo362@gmail.com, Kenneth Chan , Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= , linux-kernel@vger.kernel.org Subject: [RFC PATCH v2] platform/x86: panasonic-laptop: add fan speed mode for newer models Date: Wed, 10 Jun 2026 11:17:54 +0800 Message-ID: <20260610031756.546459-1-alexyeo362@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit I have a CF-SR4 and Linux works out of the box. Compared to previous models, this one seems to have the fans running at high speed by default when the computer starts. When Windows 11 is booted up, the high fan speeds persist just until the login screen. Once the login screen shows up, the fan spins down. I had suspected that this might be the laptop ramping down the fans when the OS declares that it is Windows but this does not seem to be the case. After analyzing the DSDT and SSDT of the computer, I have found the following: File ssdt10.dsl under \_SB.PC00.LPCB.EC0: Name (CEFM, Zero) Method (SEFM, 1, Serialized) { CEFM = Arg0 REFM () } Method (REFM, 0, Serialized) { If ((\S0IX == 0x03)) { Local0 = 0x05 } ElseIf ((CEFM == Zero)) { Local0 = Zero } Else { Local0 = 0x02 } \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0x79, Local0, Zero) } \_SB.PC00.LPCB.EC0.CEFM would seem be the current value for the fan profile. On startup, this is set to 0. Based on the code SEFM seems the be the method to set the fan profile and REFM is executed right after. I don't have access to information as to what the argument officially means but based on testing, any number above zero makes the fans spin down and behave like the older models where it stops or runs at low speed when its cool and ramps up when the processor gets hot. The only relevant values for CEFM seem to be just 0 and any number above that just gets treated the same. I personally use just 0 and 1. 0 seems to be the high fan speed mode and 1 makes it behave like Windows. Giving 0 as an argument reverts the fan back to the way it was during startup where the lowest fan speed is quite high and when load is applied, it seems to ramp up to an even higher speed which I think would be its 100%. A value of 1 seems to have its max speed capped lower than 0. For both modes, fan management is still automatic. fan_mode only shows up in sysfs only if \_SB.PC00.LPCB.EC0.CEFM and \_SB.PC00.LPCB.EC0.SEFM are both present which should mean it should not show up on unsupported models. I have tried not hiding it and it just outputs a generic error when the value is read. I also saw that variables such as eco_mode are kept in memory, however for fan_mode I rely on getting and setting the value via ACPI. Signed-off-by: Alex Yeo --- Thank you for taking the time to read the patch and give feedback. I have revised the patch according to the comments. However, for some parts I would like to clarify the following: >Include for pr_err() is missing it seems. >This file seems to be lacking sysfs.h, please add the include now. For both pr_err() and sysfs.h they should be included as both are referenced/used multiple times within the same file. >kstrtouint()? It takes in input via sysfs something like: $ echo 1 | sudo tee "/sys/devices/LNXSYSTM:00/LNXSYBUS:00/MAT0019:00/fan_mode" and based on the other functions within the same file, the input is expected to be a string which is converted into an int via kstrtouint() as CEFM is an int >But I'm unsure whether sysfs is right place for this interface. acer-wmi >driver seems to have fan mode auto/turbo selection using hwmon interface >but this driver doesn't have hwmon as is... I do get what you mean. My personal goal is to have this work smoothly with software like thermald as the vendor appears to use Intel Dynamic Tuning Technology with non-standard ACPI methods to control the fan. I will take a look into that and see if exposing the control via a different way works better. To summarize, it appears the OS calls SEFM to let the EC know the OS has taken control of the fan. The fan then spins down until it stops completely (level 0). This then allows the OS to set the fan level from 0 to 100. If the processor gets too hot, the EC intervenes and ramps up the fan. >From what I can gather, when running Windows, the vendor's driver together with Intel DTT work together to control the fan. Given this, I would like to ask what is the most appropriate place in the kernel to expose this functionality? I understand that hwmon was mentioned but I also see that interacting with /sys/class/thermal may also be an option. Thank you for your time. drivers/platform/x86/panasonic-laptop.c | 99 +++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index b83113c26f88..b05f11c223f9 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -507,6 +507,72 @@ static int set_optd_power_state(int new_state) return result; } +/* on newer models (ex: CF-SR4), fan mode can be set */ +/* check if this is available */ + +static acpi_status check_fan_mode_present(void) +{ + acpi_handle handle; + + /* check if read (CEFM) and set (SEFM) are available */ + status = acpi_get_handle(NULL, "\\_SB.PC00.LPCB.EC0.CEFM", &handle); + if (ACPI_FAILURE(status)) + return -ENOENT; + status = acpi_get_handle(NULL, "\\_SB.PC00.LPCB.EC0.SEFM", &handle); + if (ACPI_FAILURE(status)) + return -ENOENT; + + return 0; +} + +/* get fan mode state */ + +static int get_fan_mode_state(void) +{ + unsigned long long state; + acpi_status status; + + /* BIOS default is zero which seems to be some sort of performance mode */ + status = acpi_evaluate_integer(NULL, "\\_SB.PC00.LPCB.EC0.CEFM", NULL, &state); + if (ACPI_FAILURE(status)) { + pr_err("evaluation error _SB.PC00.LPCB.EC0.CEFM\n"); + return -EIO; + } + + return (int)state; +} + +/* set fan mode */ + +static int set_fan_mode_state(int new_state) +{ + acpi_status status; + int result; + + result = get_fan_mode_state(); + if (result < 0) + return result; + if (new_state == result) + return result; + + union acpi_object param[1]; + struct acpi_object_list input; + + param[0].type = ACPI_TYPE_INTEGER; + param[0].integer.value = new_state; + input.count = 1; /* takes one arg */ + input.pointer = param; + + status = acpi_evaluate_object(NULL, "\\_SB.PC00.LPCB.EC0.SEFM", + &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("Setting of fan mode via _SB.PC00.LPCB.EC0.SEFM evaluation failed. You may have an unsupported model.\n"); + return -EINVAL; + } + + return result; +} + /* sysfs user interface functions */ @@ -778,6 +844,34 @@ static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int state; + + state = get_fan_mode_state(); + if (state < 0) + return state; + + return sysfs_emit(buf, "%d\n", state); +} + +static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int status; + int err; + int val; + + err = kstrtoint(buf, 10, &val); + if (err) + return err; + + status = set_fan_mode_state(val); + + return status; +} + static DEVICE_ATTR_RO(numbatt); static DEVICE_ATTR_RO(lcdtype); static DEVICE_ATTR_RW(mute); @@ -787,6 +881,7 @@ static DEVICE_ATTR_RW(ac_brightness); static DEVICE_ATTR_RW(dc_brightness); static DEVICE_ATTR_RW(current_brightness); static DEVICE_ATTR_RW(cdpower); +static DEVICE_ATTR_RW(fan_mode); static umode_t pcc_sysfs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) { @@ -803,6 +898,9 @@ static umode_t pcc_sysfs_is_visible(struct kobject *kobj, struct attribute *attr if (attr == &dev_attr_current_brightness.attr) return (pcc->num_sifr > SINF_CUR_BRIGHT) ? attr->mode : 0; + if (attr == &dev_attr_fan_mode.attr) /* mostly present on newer models */ + return (ACPI_SUCCESS(check_fan_mode_present())) ? attr->mode : 0; + return attr->mode; } @@ -816,6 +914,7 @@ static struct attribute *pcc_sysfs_entries[] = { &dev_attr_dc_brightness.attr, &dev_attr_current_brightness.attr, &dev_attr_cdpower.attr, + &dev_attr_fan_mode.attr, NULL, }; -- 2.54.0