From nobody Mon Jun 8 06:36:22 2026 Received: from mail-pj1-f50.google.com (mail-pj1-f50.google.com [209.85.216.50]) (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 83D89332918 for ; Wed, 3 Jun 2026 01:20:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780449611; cv=none; b=PZDHY+g9NwpQUgBsWWPzD9c6a5hXiVvxBPH9gOrSOq1O4UBBhA/CjBHu/Pjs0Gep8tiKHv4Zarujl0q31LPZksk56vIbrbIzsJkKHui/xmibus8NuCWjPXvwLSusk3YxqMhHKpK7rawHYkMm6UziSzFEdrbuoOcInNAaOu++6jo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780449611; c=relaxed/simple; bh=X2dEVd080G3tFtSLGg0TPfgttDaglj7OY7qYN8LWK1w=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Za3dYzp1suf8Vj10iXgrRyKDv2R4qqQka15fEhyDQ4/RbY3BP45lgv6nr/tUl5EknHet+NailfRCoPKPsU26ccmFnWuQ8tgCSgqWFgXU4Q3sVYu1yMqC/FjgO9+TPI37n/OI8oM4MSrReG3GYuxRQm/UiJdZ3GChMan8IRpACd8= 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=LvXPIZMm; arc=none smtp.client-ip=209.85.216.50 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="LvXPIZMm" Received: by mail-pj1-f50.google.com with SMTP id 98e67ed59e1d1-36bcf3d2565so2911105a91.3 for ; Tue, 02 Jun 2026 18:20:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780449610; x=1781054410; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=iCMJDiCIgzAtqnt6dz74YTjqS3h40asLQNcG80V0akc=; b=LvXPIZMmX0W+a7Rj6WAY7n68OHk+uuW8hnN6d68zRWOhCqybnAYOrzBEftRmDnlyKo /J3bsfmQV5gK07f0ObzwT+Rzh0hR5d1yZU/L45bzYQ4keUACsEp6pfJRIj5XE3M0qPB7 1nCt2UtNa9dSo5JWR4jt7M4H1RGX+uGfDow+wECSIpCRkaFhZQa3YeE045nqwjvkdaCo IfMNx/wk8RFGZdBRNoooftx+Szq7QzXrEUZhOScMpqG/ezZPRF2qevU9IzABLFKkYsGC ZxYqu3/Nvy5uCVvnW7UB2FwjFyCuOiE7pa6yWlhu9N5VocO/SspoMUwfjVd1MqcGeK99 JDPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780449610; x=1781054410; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=iCMJDiCIgzAtqnt6dz74YTjqS3h40asLQNcG80V0akc=; b=RQFgAj5QWt+IkFw0Kgx3Ih/OfVpCit22roJvV9/3U+4R3OnMC7Y1tJfFZmOzyYpl6s 63xd5umetX3hoIdlAXukDv0mR/ODP/oE4u8DG+cNzQR4vyVlbxWk0X9vxKlFwK6m6frD /29l4uHaMsu166dMXkBxfg3VqtukxQqsPRDJYFlBErZ8GibE3Hw5Ulcc3kw5qaXu9LiF kHCU5j6S+cz2zKGW3cZsSWHp1JqEjsKjaDo6em/sT6CWCbEKhBMknwDtBD8VCc4RfzZu OwZaRa8LJfc2XGqfZiElG4NLEbglA5mH/t2kDzjgunzTo4eUV7lJT5H8Ag3ptM7IUbBk gHiQ== X-Forwarded-Encrypted: i=1; AFNElJ89yaNegy74JsQgXVEjZbewxmDuQhsbpQ2MWaqER5oqKy5vK2EKgAkn2iKn5F8E7bz1lN/FGQotsKlM7WM=@vger.kernel.org X-Gm-Message-State: AOJu0Yzj029T4/BDPgS3WEGIHc7ptRCz+mG8s/DAJEzLYo9sqce28iZo zG3VKnzgDSI+EKncgWD7qUi6zw5MJR6VEOX2iZ8OgV43sDy1JUG+psHq3trmWnUKl/I= X-Gm-Gg: Acq92OEWo1b2Uk9erhihdXvZy7CV06Vdiy+oqRdXN6HwwvvajUQR4FSw3WSn6eycVZN /Qbn6A+b8gXxXYc0DNmyxM8fLVUT2NaLzPrZedO0om61TtzZatZudjZR44MkzQ5IiP4pORug/7o Pt1TuC7SQ72RjWo02z5DNZTu7xOc3UaZvvdBEGON/LC1Jye9cVPyYSdYZktnBqWC9/n6G3/2rum SE+Rv6reHTjYampLa6KsgWnUwUHKMdnvoYAbIRg87BNHY3JZwTlVH0QDKFeDJGW65BMMlIerWrZ oP3UetGo7V03yPUngAaNL2UtFNm8/uY/UnajFE1WZA4gaQpVPGOnPMDy5ASSwm98SGYIHWDwaGI rMuD3XHi7rxScdWq3EeGbnEA4I+lrR2m3bWVpC3iESCCZEznpn8QprgI1Gj1qJyhEX3WCw7l3P2 Dz2KB29TXRhOY0zDcEhwmpKQDdvpaTNOp7KD9ysYFhZ/gmE7hp81YHHa6gkzhwYWGw X-Received: by 2002:a17:90b:4b8b:b0:36d:bbe0:de7c with SMTP id 98e67ed59e1d1-36e30f0fb4cmr1119150a91.12.1780449609593; Tue, 02 Jun 2026 18:20:09 -0700 (PDT) Received: from localhost ([175.176.17.146]) by smtp.googlemail.com with ESMTPSA id 41be03b00d2f7-c85df0341f6sm500364a12.7.2026.06.02.18.20.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Jun 2026 18:20:09 -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: [PATCH] platform/x86: panasonic-laptop: add fan speed mode for newer models Date: Wed, 3 Jun 2026 09:19:33 +0800 Message-ID: <20260603011934.43467-1-alexyeo362@gmail.com> X-Mailer: git-send-email 2.54.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 =3D Arg0 REFM () } Method (REFM, 0, Serialized) { If ((\S0IX =3D=3D 0x03)) { Local0 =3D 0x05 } ElseIf ((CEFM =3D=3D Zero)) { Local0 =3D Zero } Else { Local0 =3D 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 --- drivers/platform/x86/panasonic-laptop.c | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86= /panasonic-laptop.c index b83113c26f88..3a8bbd633fd1 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -507,6 +507,76 @@ static int set_optd_power_state(int new_state) return result; } =20 +/* 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_status status =3D AE_OK; + acpi_handle handle; + + /* check if read and set are available */ + /* read fan speed profile */ + status =3D acpi_get_handle(NULL, "\\_SB.PC00.LPCB.EC0.CEFM", &handle); + if (ACPI_FAILURE(status)) + goto out; + /* set fan speed profile */ + status =3D acpi_get_handle(NULL, "\\_SB.PC00.LPCB.EC0.SEFM", &handle); + if (ACPI_FAILURE(status)) + goto out; + +out: + return status; +} + +/* get fan mode state */ + +static int get_fan_mode_state(void) +{ + acpi_status status; + unsigned long long state; + + /* bios default is zero which seems to be some sort of performance mode */ + status =3D 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"); + state =3D -EIO; + } + int result =3D (int)state; + return result; +} + +/* set fan mode */ + +static int set_fan_mode_state(int new_state) +{ + int result; + acpi_status status; + + result =3D get_fan_mode_state(); + if (result < 0) + goto out; + if (new_state =3D=3D result) + goto out; + + union acpi_object param[1]; + struct acpi_object_list input; + + param[0].type =3D ACPI_TYPE_INTEGER; + param[0].integer.value =3D new_state; + input.count =3D 1; /* takes one arg */ + input.pointer =3D param; + + status =3D acpi_evaluate_object(NULL, "\\_SB.PC00.LPCB.EC0.SEFM", + &input, NULL); + if (ACPI_FAILURE(status)) { + pr_err("_SB.PC00.LPCB.EC0.SEFM evaluation failed\n"); + return -EINVAL; + } +out: + return result; +} + =20 /* sysfs user interface functions */ =20 @@ -778,6 +848,29 @@ static ssize_t cdpower_store(struct device *dev, struc= t device_attribute *attr, return count; } =20 +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + int state =3D 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 err, val; + + err =3D kstrtoint(buf, 10, &val); + if (err) + return err; + set_fan_mode_state(val); + return count; +} + static DEVICE_ATTR_RO(numbatt); static DEVICE_ATTR_RO(lcdtype); static DEVICE_ATTR_RW(mute); @@ -787,6 +880,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); =20 static umode_t pcc_sysfs_is_visible(struct kobject *kobj, struct attribute= *attr, int idx) { @@ -803,6 +897,9 @@ static umode_t pcc_sysfs_is_visible(struct kobject *kob= j, struct attribute *attr if (attr =3D=3D &dev_attr_current_brightness.attr) return (pcc->num_sifr > SINF_CUR_BRIGHT) ? attr->mode : 0; =20 + if (attr =3D=3D &dev_attr_fan_mode.attr) /* mostly present on newer model= s */ + return (ACPI_SUCCESS(check_fan_mode_present())) ? attr->mode : 0; + return attr->mode; } =20 @@ -816,6 +913,7 @@ static struct attribute *pcc_sysfs_entries[] =3D { &dev_attr_dc_brightness.attr, &dev_attr_current_brightness.attr, &dev_attr_cdpower.attr, + &dev_attr_fan_mode.attr, NULL, }; =20 --=20 2.54.0