* [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support
@ 2026-10-07 19:04 Alex Yeo
2026-10-12 3:42 ` Wu Zhipeng
2026-10-16 11:12 ` Ilpo Järvinen
0 siblings, 2 replies; 5+ messages in thread
From: Alex Yeo @ 2026-10-07 19:04 UTC (permalink / raw)
To: platform-driver-x86
Cc: alexyeo362, Hans de Goede, Ilpo Järvinen, Kenneth Chan,
linux-kernel
Expose firmware thermal and power management policies via the
platform_profile interface.
Supported and tested models:
- CF-RZ6 (2016)
- CF-SV8 (2019)
- CF-QV9 (2020)
- CF-SR4 (2024)
Firmware fan operating mode and the TDP limit switch are mapped onto
the Linux platform_profile ABI.
This implementation queries the firmware for every get().
CUSTOM is returned when the current state is not recognized.
The set() callback writes the values regardless of what the
current fan mode and TDP limit is.
- Introduce as separate platform_driver
- Used DEFINE for driver and platform_profile device name
- DMI quirks restrict support to tested models
- When raising TDP, fan mode is switched first
- When limiting TDP, TDP limit is applied first
- ACPI handle for the EC device was used as the EC path varies
slightly between models.
- This device is optional: keep hotkey and everything already
loaded if profile driver fails for any reason.
Signed-off-by: Alex Yeo <alexyeo362@gmail.com>
---
v3:
- Removed sysfs.h import
- Refactored platform profile probe function to use a different pattern
(check warn_on, continue on double unset, then set)
v2: https://lore.kernel.org/platform-driver-x86/20261006085021.853827-1-alexyeo362@gmail.com/
- Refactored code to use its own platform_driver
- Removed DEFINE_FREE and its corresponding __free as its addition
in this module is inappropriate
- Removed pr_fmt line as its addition to this patch is inappropriate
- Added DMI check during __init to prevent attempt of platform profile
device loading on unsupported hardware
- Fixed use of enums as truth values by adding explicit comparisons
- Added *profile_pdev to keep track of new platform_driver
- Removed extra spaces in the quirk definitions
- Added helper function for calling EC methods for simplicity
- Use ARRAY_SIZE() for for loops and added include for array_size.h
- Refactored pcc_profile to be a pointer in platform profile set
function
- Use WARN_ON() when a malformed quirk is detected in the platform
profile probe function
- Avoid use of status as variable name if not acpi_status
- Added Kconfig line for ACPI_PLATFORM_PROFILE
- Removed bool .use_platform_profiles from the quirks struct as it is
no longer needed as a result of the platform_driver being separate.
- Moved DMI quirk matching in probe function before devm_kzalloc of
driver struct
v1: https://lore.kernel.org/platform-driver-x86/20260805180544.1134916-1-alexyeo362@gmail.com/
This RFC patch adds support for mapping firmware thermal and CPU power
operating policy to platform_profile for Panasonic Let's Note laptops.
This originally started as a hwmon and thermal patch, but further
analysis had revealed that these are firmware power and thermal
policies as opposed to fan control.
Panasonic firmware exposes firmware operating policy through two
methods:
- Fan cooling policy (active/passive)
- Processor TDP limit (capped/uncapped)
At boot, firmware defaults to an active cooling policy with the
CPU TDP capped. Vendor drivers / DPTF normally unlocks this on
Windows.
These 2 methods map to 4 possible distinct states. Only states that
correspond to the platform_profile ABI are mapped and exposed as
choices. Unmapped states will be returned as "custom" by the driver.
I have tested the following models:
- CF-RZ6 (2016)
- CF-SV8 (2019)
- CF-QV9 (2020)
- CF-SR4 (2024)
State to platform_profile mapping:
Profile | Fan Mode | TDP
+--------------------------------+-----------+------------+
PLATFORM_PROFILE_COOL (SR4 only)| Active | Locked
PLATFORM_PROFILE_BALANCED | Active | Locked
PLATFORM_PROFILE_QUIET | Passive | Locked
PLATFORM_PROFILE_PERFORMANCE | Active | Unlocked
CUSTOM (unmapped on all) | Passive | Unlocked
Across all of them, the fan and TDP modes are toggled by the same ACPI
methods. However, there is a slight variation in its interpretation based
on testing results.
These methods are normally called by the DPTF/IPF _OSC handshake.
Depending on the capability bits, the firmware will alter its thermal
and power policy.
\_SB.IETM Implementation A:
- CF-RZ6 (2016)
- CF-SV8 (2019)
\_SB.IETM Implementation B:
- CF-QV9 (2020)
- CF-SR4 (2024)
Models belonging to the same group share what appears to be identical
AML code for the _OSC.
This patch only exposes the ability to read and set these firmware
toggles. The get() callback queries current firmware state and returns
the mapped platform_profile. The set() callback sets the target state
regardless of current state.
Proposed mappings in this patch are defined in the quirks struct.
Activation of platform_profile is gated by DMI checks.
For every model except the SR4, only balanced and performance profiles
are defined. This is because the aggressive fan paired with the locked
TDP on the SR4 more appropriately correspond to the cool profile.
Test results for the proposed mappings are below.
Test results:
================
(CPU PkgWatt: peak / sustained)
(Sysbench CPU: all available threads)
CF-RZ6: (i5-7Y57, Startup default: Balanced)
Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
+-----------------+-----------------+----------------+-------------------+
BALANCED | 10.0W / 9.0W | 19712 | 57C
PERFORMANCE | 18.9W / 14.9W | 29132 | 72C
CF-SV8: (i5-8365U, Startup default: Balanced)
Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
+-----------------+------------------+----------------+-------------------+
BALANCED | 9.9W / 9.9W | 44695 | 58C
PERFORMANCE | 29.0W / 19.9W | 71747 | 81C
CF-QV9: (i5-10310U, Startup default: Balanced)
Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
+-----------------+------------------+----------------+-------------------+
BALANCED | 10.0W / 9.9W | 46545 | 59C
PERFORMANCE | 18.9W / 14.9W | 57416 | 70C
CF-SR4: (i5-1345U, Startup default: Cool)
Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
+-----------------+------------------+----------------+-------------------+
COOL | 12.0W / 12.0W | 177139 | 66C
QUIET | 12.0W / 12.0W | 176315 | 73C-75C
PERFORMANCE | 29.6W / 21.3W | 247602 | 98C-100C
CF-SR4 Notes:
- QUIET: downclock/throttle at 75C, no increase in package_throttle_count
- PERFORMANCE: stable boost clock, package_throttle_count increase
Other notes:
===============
Additional notes about thermald:
During testing, I have observed that running thermald for the first time
since boot will trigger an _OSC negotiation that will unlock CPU TDP for
new models (IETM Implementation B). Once thermald is running, exits or
runs again, these bits were not observed to change again during testing.
Running thermald on older models (IETM Implementation A) does not touch
the fan curve or TDP mode setting.
Suspend: observed to not alter the values set prior to suspend
drivers/platform/x86/Kconfig | 1 +
drivers/platform/x86/panasonic-laptop.c | 399 +++++++++++++++++++++++-
2 files changed, 399 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 957034f39e4e..571c09152310 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -820,6 +820,7 @@ config PANASONIC_LAPTOP
depends on ACPI_VIDEO=n || ACPI_VIDEO
depends on SERIO_I8042 || SERIO_I8042 = n
select INPUT_SPARSEKMAP
+ select ACPI_PLATFORM_PROFILE
help
This driver adds support for access to backlight control and hotkeys
on Panasonic Let's Note laptops.
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 19d194ff37ca..c4b13a3554e3 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -120,9 +120,13 @@
*/
#include <linux/acpi.h>
+#include <linux/array_size.h>
#include <linux/backlight.h>
#include <linux/bits.h>
#include <linux/ctype.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/errno.h>
#include <linux/i8042.h>
#include <linux/init.h>
#include <linux/input.h>
@@ -130,6 +134,8 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/platform_profile.h>
+#include <linux/printk.h>
#include <linux/seq_file.h>
#include <linux/serio.h>
#include <linux/slab.h>
@@ -158,10 +164,37 @@ MODULE_LICENSE("GPL");
#define ECO_MODE_OFF 0x00
#define ECO_MODE_ON 0x80
+#define PCC_ACPI_FAN_ACTIVE_MODE 0x00
+#define PCC_ACPI_FAN_PASSIVE_MODE 0x01
+#define PCC_ACPI_TDP_LIMIT_ON 0x01
+#define PCC_ACPI_TDP_LIMIT_OFF 0x00
+
#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
+#define PROFILE_PCC_DRIVER_NAME "panasonic-platform-profile"
#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
+enum pcc_profile_fan_mode {
+ PCC_FAN_MODE_UNSET = 0,
+ PCC_FAN_MODE_ACTIVE,
+ PCC_FAN_MODE_PASSIVE,
+};
+
+enum pcc_profile_tdp_mode {
+ PCC_TDP_MODE_UNSET = 0,
+ PCC_TDP_MODE_LOCKED,
+ PCC_TDP_MODE_UNLOCKED,
+};
+
+struct pcc_platform_profile {
+ enum pcc_profile_fan_mode fan_mode;
+ enum pcc_profile_tdp_mode tdp_mode;
+};
+
+struct pcc_profile_quirk {
+ struct pcc_platform_profile platform_profiles[PLATFORM_PROFILE_LAST];
+};
+
/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
ECO_MODEs: 0x03 = off, 0x83 = on
*/
@@ -251,6 +284,104 @@ struct pcc_acpi {
u32 sinf[] __counted_by(num_sifr);
};
+struct pcc_profile {
+ acpi_handle ec_handle;
+ const struct pcc_profile_quirk *quirks;
+ struct device *platform_profile_dev;
+};
+
+static struct pcc_profile_quirk quirk_cf_sr4 = {
+ .platform_profiles = {
+ [PLATFORM_PROFILE_QUIET] = {
+ .fan_mode = PCC_FAN_MODE_PASSIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_COOL] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_PERFORMANCE] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_UNLOCKED,
+ },
+ },
+};
+
+static struct pcc_profile_quirk quirk_cf_qv9 = {
+ .platform_profiles = {
+ [PLATFORM_PROFILE_BALANCED] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_PERFORMANCE] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_UNLOCKED,
+ },
+ },
+};
+
+static struct pcc_profile_quirk quirk_cf_sv8 = {
+ .platform_profiles = {
+ [PLATFORM_PROFILE_BALANCED] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_PERFORMANCE] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_UNLOCKED,
+ },
+ },
+};
+
+static struct pcc_profile_quirk quirk_cf_rz6 = {
+ .platform_profiles = {
+ [PLATFORM_PROFILE_BALANCED] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_PERFORMANCE] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_UNLOCKED,
+ },
+ },
+};
+
+static const struct dmi_system_id pcc_profile_quirks[] = {
+ {
+ .ident = "Panasonic Connect Co., Ltd. CFSR4-1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Connect Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CFSR4-1"),
+ },
+ .driver_data = &quirk_cf_sr4,
+ },
+ {
+ .ident = "Panasonic Corporation CFQV9-1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CFQV9-1"),
+ },
+ .driver_data = &quirk_cf_qv9,
+ },
+ {
+ .ident = "Panasonic Corporation CFSV8-2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CFSV8-2"),
+ },
+ .driver_data = &quirk_cf_sv8,
+ },
+ {
+ .ident = "Panasonic Corporation CFRZ6-2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CFRZ6-2"),
+ },
+ .driver_data = &quirk_cf_rz6,
+ },
+ {},
+};
+
/*
* On some Panasonic models the volume up / down / mute keys send duplicate
* keypress events over the PS/2 kbd interface, filter these out.
@@ -1143,4 +1274,270 @@ static void acpi_pcc_hotkey_remove(struct platform_device *pdev)
kfree(pcc);
}
-module_platform_driver(acpi_pcc_driver);
+static int pcc_profile_fan_mode_get(struct pcc_profile *pcc, enum pcc_profile_fan_mode *fan_mode)
+{
+ unsigned long long state;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(pcc->ec_handle, "CEFM", NULL, &state);
+ if (ACPI_FAILURE(status)) {
+ pr_err("cannot get fan mode via CEFM\n");
+ return -EIO;
+ }
+
+ if (state == PCC_ACPI_FAN_ACTIVE_MODE)
+ *fan_mode = PCC_FAN_MODE_ACTIVE;
+ else if (state == PCC_ACPI_FAN_PASSIVE_MODE)
+ *fan_mode = PCC_FAN_MODE_PASSIVE;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pcc_profile_tdp_mode_get(struct pcc_profile *pcc, enum pcc_profile_tdp_mode *tdp_mode)
+{
+ unsigned long long state;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(pcc->ec_handle, "EPLE", NULL,
+ &state);
+ if (ACPI_FAILURE(status)) {
+ pr_err("cannot read power limit using EPLE\n");
+ return -EIO;
+ }
+
+ if (state == PCC_ACPI_TDP_LIMIT_ON)
+ *tdp_mode = PCC_TDP_MODE_LOCKED;
+ else if (state == PCC_ACPI_TDP_LIMIT_OFF)
+ *tdp_mode = PCC_TDP_MODE_UNLOCKED;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pcc_profile_ec_execute(struct pcc_profile *pcc, char *method, u64 arg)
+{
+ acpi_status status;
+
+ status = acpi_execute_simple_method(pcc->ec_handle, method, arg);
+ if (ACPI_FAILURE(status)) {
+ pr_err("failed to execute EC method %s\n", method);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int pcc_profile_fan_mode_set(struct pcc_profile *pcc, enum pcc_profile_fan_mode fan_mode)
+{
+ switch (fan_mode) {
+ case PCC_FAN_MODE_ACTIVE:
+ return pcc_profile_ec_execute(pcc, "SEFM", PCC_ACPI_FAN_ACTIVE_MODE);
+ case PCC_FAN_MODE_PASSIVE:
+ return pcc_profile_ec_execute(pcc, "SEFM", PCC_ACPI_FAN_PASSIVE_MODE);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pcc_profile_tdp_mode_set(struct pcc_profile *pcc, enum pcc_profile_tdp_mode tdp_mode)
+{
+ switch (tdp_mode) {
+ case PCC_TDP_MODE_LOCKED:
+ return pcc_profile_ec_execute(pcc, "SEPL", PCC_ACPI_TDP_LIMIT_ON);
+ case PCC_TDP_MODE_UNLOCKED:
+ return pcc_profile_ec_execute(pcc, "SEPL", PCC_ACPI_TDP_LIMIT_OFF);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pcc_profile_platform_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct pcc_profile *pcc = dev_get_drvdata(dev);
+ enum platform_profile_option pp_opt;
+ enum pcc_profile_fan_mode fan_mode;
+ enum pcc_profile_tdp_mode tdp_mode;
+ int err;
+
+ err = pcc_profile_fan_mode_get(pcc, &fan_mode);
+ if (err)
+ return err;
+
+ err = pcc_profile_tdp_mode_get(pcc, &tdp_mode);
+ if (err)
+ return err;
+
+ for (pp_opt = 0; pp_opt < ARRAY_SIZE(pcc->quirks->platform_profiles); pp_opt++) {
+ const struct pcc_platform_profile *pcc_profile =
+ &pcc->quirks->platform_profiles[pp_opt];
+
+ if (tdp_mode == pcc_profile->tdp_mode &&
+ fan_mode == pcc_profile->fan_mode) {
+ *profile = pp_opt;
+ return 0;
+ }
+ }
+
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ return 0;
+}
+
+static int pcc_profile_platform_profile_set_profile(struct pcc_profile *pcc,
+ enum pcc_profile_fan_mode fan_mode,
+ enum pcc_profile_tdp_mode tdp_mode)
+{
+ int err;
+
+ switch (tdp_mode) {
+ case PCC_TDP_MODE_UNLOCKED:
+ err = pcc_profile_fan_mode_set(pcc, fan_mode);
+ if (err)
+ return err;
+
+ return pcc_profile_tdp_mode_set(pcc, tdp_mode);
+ case PCC_TDP_MODE_LOCKED:
+ err = pcc_profile_tdp_mode_set(pcc, tdp_mode);
+ if (err)
+ return err;
+
+ return pcc_profile_fan_mode_set(pcc, fan_mode);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pcc_profile_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct pcc_profile *pcc = dev_get_drvdata(dev);
+ const struct pcc_platform_profile *pcc_profile = &pcc->quirks->platform_profiles[profile];
+
+ return pcc_profile_platform_profile_set_profile(pcc,
+ pcc_profile->fan_mode,
+ pcc_profile->tdp_mode);
+}
+
+static int pcc_profile_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ enum platform_profile_option pp_opt;
+ struct pcc_profile *pcc = drvdata;
+
+ for (pp_opt = 0; pp_opt < ARRAY_SIZE(pcc->quirks->platform_profiles); pp_opt++) {
+ const struct pcc_platform_profile *profile =
+ &pcc->quirks->platform_profiles[pp_opt];
+
+ /* Both TDP and fan mode should be set */
+ if (WARN_ON((profile->fan_mode != PCC_FAN_MODE_UNSET) !=
+ (profile->tdp_mode != PCC_TDP_MODE_UNSET)))
+ return -EINVAL;
+
+ if (profile->fan_mode == PCC_FAN_MODE_UNSET &&
+ profile->tdp_mode == PCC_TDP_MODE_UNSET)
+ continue;
+
+ set_bit(pp_opt, choices);
+ }
+
+ return 0;
+}
+
+static const struct platform_profile_ops pcc_platform_profile_ops = {
+ .probe = pcc_profile_platform_profile_probe,
+ .profile_get = pcc_profile_platform_profile_get,
+ .profile_set = pcc_profile_platform_profile_set,
+};
+
+static int pcc_profile_driver_probe(struct platform_device *pdev)
+{
+ const struct dmi_system_id *dmi_id;
+ struct acpi_device *ec_adev;
+ struct pcc_profile *pcc;
+
+ dmi_id = dmi_first_match(pcc_profile_quirks);
+ if (!dmi_id)
+ return -ENODEV;
+
+ pcc = devm_kzalloc(&pdev->dev, sizeof(*pcc), GFP_KERNEL);
+ if (!pcc)
+ return -ENOMEM;
+
+ ec_adev = acpi_dev_get_first_match_dev("PNP0C09", NULL, -1);
+ if (!ec_adev)
+ return -ENODEV;
+
+ pcc->ec_handle = ec_adev->handle;
+ acpi_dev_put(ec_adev);
+
+ pcc->quirks = dmi_id->driver_data;
+
+ pcc->platform_profile_dev =
+ devm_platform_profile_register(&pdev->dev,
+ PROFILE_PCC_DRIVER_NAME,
+ pcc,
+ &pcc_platform_profile_ops);
+ if (IS_ERR(pcc->platform_profile_dev)) {
+ return dev_err_probe(&pdev->dev,
+ PTR_ERR(pcc->platform_profile_dev),
+ "failed to register platform profiles\n");
+ }
+
+ return 0;
+}
+
+static struct platform_driver profile_pcc_driver = {
+ .probe = pcc_profile_driver_probe,
+ .driver = {
+ .name = PROFILE_PCC_DRIVER_NAME,
+ },
+};
+
+static struct platform_device *profile_pdev;
+
+static int __init panasonic_module_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&acpi_pcc_driver);
+ if (err)
+ return err;
+
+ if (!dmi_check_system(pcc_profile_quirks))
+ return 0;
+
+ err = platform_driver_register(&profile_pcc_driver);
+ if (err) {
+ pr_warn("failed to register profile driver: %d\n", err);
+ return 0;
+ }
+
+ profile_pdev = platform_device_register_simple(PROFILE_PCC_DRIVER_NAME,
+ PLATFORM_DEVID_NONE,
+ NULL, 0);
+ if (IS_ERR(profile_pdev)) {
+ err = PTR_ERR(profile_pdev);
+ pr_warn("failed to register profile device: %d\n", err);
+
+ profile_pdev = NULL;
+ platform_driver_unregister(&profile_pcc_driver);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void __exit panasonic_module_exit(void)
+{
+ if (profile_pdev) {
+ platform_device_unregister(profile_pdev);
+ platform_driver_unregister(&profile_pcc_driver);
+ }
+
+ platform_driver_unregister(&acpi_pcc_driver);
+}
+
+module_init(panasonic_module_init);
+module_exit(panasonic_module_exit);
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support
2026-10-07 19:04 [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support Alex Yeo
@ 2026-10-12 3:42 ` Wu Zhipeng
2026-10-15 9:55 ` Alex Yeo
2026-10-16 11:12 ` Ilpo Järvinen
1 sibling, 1 reply; 5+ messages in thread
From: Wu Zhipeng @ 2026-10-12 3:42 UTC (permalink / raw)
To: Alex Yeo
Cc: platform-driver-x86, linux-acpi, Ilpo Jarvinen, Hans de Goede,
Hans de Goede, Kenneth Chan
[-- Attachment #1: Type: text/plain, Size: 13038 bytes --]
Hi Alex,
Thanks for the RFC and for carrying the series through v3. I tested this
on a Panasonic CF-SV1-2 (product name "CFSV1-2" per dmidecode,
i5-1145G7, BIOS V2.00L23), which exposes the same SEFM/SEPL/CEFM/EPLE
method quartet and the same 4-state fan/TDP matrix your driver relies
on. The _SB.IETM._OSC region from this unit's dump is in the excerpt
below if it helps place it in your A/B grouping.
Summary of my findings:
1. CF-SV1-2 is not in v3's tested list or DMI table. Mirroring the v3
CF-SR4 table, the mapping here is: active+locked -> COOL,
passive+locked -> QUIET, active+unlocked -> PERFORMANCE, which
leaves passive+unlocked to fall out as CUSTOM exactly as get()
reports unmapped states. The startup default on this unit is
CEFM=0 / EPLE=1, i.e. active+locked (COOL), the same shape as your
per-model "Startup default" notes. One mapping note: COOL is
currently SR4-only, but on the SV1 the active fan is likewise the
aggressive setting (4 C cooler at the same package power, below),
so SV1 would be a second COOL model. A quirk patch rebased onto
your v3 is attached (a starting point, not for direct merge).
2. Measured on this unit, the fan toggle is the dominant lever: it
moves the peak package temperature by +4 C on AC and +9..10 C on
battery, with package power essentially flat (fan effect <= 0.05 W
on AC and <= 0.25 W on battery). The TDP toggle moves much less:
<= 0.05 W and +2 C on AC, <= 0.41 W and +5..6 C on battery. Even
with EPP pinned to performance the package stayed at <= 2.6 GHz /
~10.5 W, and I could not establish from the OS side what still caps
it; I flag this because it affects what "unlocked" should mean on
this generation.
3. A few model-specific AML details I noticed (SSDT excerpts are appended
below):
· REFM writes a curve byte to EC register 0x79 and the values are
model-specific: this SV1 uses 0x00 (CEFM=0) / 0x05 (CEFM=1) /
0x02 under S0i3, while the SR4 REFM quoted in your fan-speed RFC
v8 (its ssdt10.dsl) swaps the latter two. Only the SEFM boolean
is portable, so the curve bytes shouldn't be documented as
universal.
· REPL has four outcomes, not a binary cap: EPLE=1 selects an IETM
table PL (P140/P143 here; the battery locked runs land on
P140 = 10 W) and EPLE=0 gives the 0xFF no-limit branch, so the
effective wattage comes from the IETM tables rather than a fixed
number. v3's EPLE read matches what the EC does.
· _FST's second field is the fan setpoint (PEN1), not RPM.
· While TFN1._FSL holds a nonzero override, the EC ignores the
curve register, masking platform_profile set(). Your decision not
to touch _FSL looks correct.
· Consistent with the WAK -> REPL/REFM/... chain, the profile
setting survives suspend/resume (verified with rtcwake).
Data -- AC, userspace fan governance parked so the firmware owns the
fan; per state: write SEFM/SEPL, 30 s settle, then sysbench cpu
--threads=8 --time=60 with 1 s RAPL package sampling. PkgWatt = mean
package power over the 60 s window (RAPL energy delta); temp = highest
package temperature in that window; medians over 3 interleaved runs,
min-max in parentheses:
Configuration PkgWatt sustained sysbench pkg max
high(active) + locked 6.28 W (6.21-6.33) 5510 e/s 41.0 C (41-42)
quiet(passive) + locked 6.23 W (6.21-6.34) 5425 e/s 45.0 C (45-47)
quiet(passive) + unlocked 6.22 W (6.19-6.26) 5412 e/s 47.0 C (47-48)
high(active) + unlocked 6.23 W (6.22-6.25) 5481 e/s 43.0 C (42-44)
fan toggle (quiet - high) : package -0.05 W, pkg max +4.0 C @locked
package -0.02 W, pkg max +4.0 C @unlocked
TDP toggle (unlocked - locked): package -0.05 W, pkg max +2.0 C @high fan
package -0.02 W, pkg max +2.0 C @quiet fan
Battery -- same method, but EPP pinned to performance so the CPU asks
for more than the P140 table value; S0IX=0 / PWRS=0 throughout, i.e.
the P152/P154 branch never fires in working states (n=1, trend only):
Configuration PkgWatt sustained pkg max
high(active) + locked 9.98 W 48.0 C
quiet(passive) + locked 10.01 W 57.0 C
quiet(passive) + unlocked 10.42 W 63.0 C
high(active) + unlocked 10.20 W 53.0 C
The locked states land on the P140 value (~10.0 W) and unlocking adds
only 0.2-0.4 W here, so the TDP toggle stays a minor lever on battery
as well.
Reproducibility: kernel 7.0.0-38-generic, iasl 20251212; a fresh
acpidump -b on the unit gives ssdt2.dat sha256
f0d92acff1f43c29c7921ebeb6951375f21d5fc554e7070426c6b9e8f62f5f61, and
decompiling it gives ssdt2.dsl sha256 913edf1cf5571f2b... (the regions
quoted above are appended below). Per-run samples and the raw dump stay
on my side and are available on request.
Attached as plain text: cf-sv1-platform_profile-quirk.patch
(git-format-patch against your v3) and the two summary tables above as
summary-ac.csv / summary-battery.csv. The SSDT excerpt is appended
below after the --- delimiter, and the full run logs are available on
request. Happy to build-test it here and send a Tested-by.
This message was drafted with the assistance of an AI coding agent;
all measurements and AML reading were reviewed by me against the raw
data.
Reported-by: Wu Zhipeng <zepwu@foxmail.com>
---
# Excerpt from ssdt2.dsl -- decompiled with iasl 20251212 from ssdt2.dat
# (sha256 f0d92acff1f43c29c7921ebeb6951375f21d5fc554e7070426c6b9e8f62f5f61,
# acpidump -b on the CF-SV1-2 under kernel 7.0.0-38-generic; the
# decompiled file's sha256 starts 913edf1cf5571f2b).
# Line numbers refer to the decompiled file. The mail quotes the
# relevant parts inline (C1/C2); this file is the surrounding context.
# ---- \_SB.IETM._OSC : sets SEFM/SEPL from the OS capabilities ----
355 Method (_OSC, 4, Serialized) // _OSC: Operating System Capabilities
356 {
357 CreateDWordField (Arg3, Zero, STS1)
358 CreateDWordField (Arg3, 0x04, CAP1)
359 If ((Arg1 != One))
360 {
361 STS1 &= 0xFFFFFF00
362 STS1 |= 0x0A
363 Return (Arg3)
364 }
365
366 If ((Arg2 != 0x02))
367 {
368 STS1 &= 0xFFFFFF00
369 STS1 |= 0x02
370 Return (Arg3)
371 }
372
373 If (CondRefOf (\_SB.APSV))
374 {
375 If ((PSEM == Zero))
376 {
377 PSEM = One
378 PTRP = \_SB.APSV /* External reference */
379 }
380 }
381
382 If (CondRefOf (\_SB.AAC0))
383 {
384 If ((ASEM == Zero))
385 {
386 ASEM = One
387 ATRP = \_SB.AAC0 /* External reference */
388 }
389 }
390
391 If (CondRefOf (\_SB.ACRT))
392 {
393 If ((YSEM == Zero))
394 {
395 YSEM = One
396 YTRP = \_SB.ACRT /* External reference */
397 }
398 }
399
400 If ((Arg0 == ToUUID ("b23ba85d-c8b7-3542-88de-8de2ffcfd698") /* Unknown UUID */))
401 {
402 If (~(STS1 & One))
403 {
404 If ((CAP1 & One))
405 {
406 If ((CAP1 & 0x02))
407 {
408 \_SB.AAC0 = 0x6E
409 \_SB.PC00.LPCB.EC0.SEFM (One)
410 }
411 Else
412 {
413 \_SB.AAC0 = ATRP /* \_SB_.IETM.ATRP */
414 \_SB.PC00.LPCB.EC0.SEFM (Zero)
415 }
416
417 If ((CAP1 & 0x04))
418 {
419 \_SB.APSV = 0x6E
420 \_SB.PC00.LPCB.EC0.SEPL (Zero)
421 \_SB.PC00.TCPU.TDPL ()
422 }
423 Else
424 {
425 \_SB.APSV = PTRP /* \_SB_.IETM.PTRP */
426 \_SB.PC00.LPCB.EC0.SEPL (One)
427 }
428
429 If ((CAP1 & 0x08))
430 {
431 \_SB.ACRT = 0xD2
432 \_TZ.ETMD = Zero
433 }
434 Else
435 {
436 \_SB.ACRT = YTRP /* \_SB_.IETM.YTRP */
437 \_TZ.ETMD = One
438 }
439 }
440 Else
441 {
442 \_SB.ACRT = YTRP /* \_SB_.IETM.YTRP */
443 \_SB.APSV = PTRP /* \_SB_.IETM.PTRP */
444 \_SB.AAC0 = ATRP /* \_SB_.IETM.ATRP */
445 \_TZ.ETMD = One
446 }
447
448 If (CondRefOf (\_TZ.TZ00))
449 {
450 Notify (\_TZ.TZ00, 0x81) // Information Change
# ---- SEFM / REFM : CEFM -> fan curve byte (C1) ----
3640 Name (CEFM, Zero)
3641 Method (SEFM, 1, Serialized)
3642 {
3643 CEFM = Arg0
3644 REFM ()
3645 }
3646
3647 Method (REFM, 0, Serialized)
3648 {
3649 If ((\S0IX == 0x03))
3650 {
3651 \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0x79, 0x02, Zero)
3652 }
3653 ElseIf ((CEFM == Zero))
3654 {
3655 \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0x79, Zero, Zero)
3656 }
3657 Else
3658 {
3659 \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0x79, 0x05, Zero)
3660 }
3661 }
# ---- SEPL / REPL : EPLE/S0IX/PWRS -> power limit table (C2) ----
3663 Name (EPLE, One)
3664 Method (SEPL, 1, Serialized)
3665 {
3666 EPLE = Arg0
3667 REPL ()
3668 }
3669
3670 Method (REPL, 0, Serialized)
3671 {
3672 Name (EPL1, 0xFF)
3673 Name (EPL2, 0xFF)
3674 Name (EPLT, Zero)
3675 If (((\S0IX == One) && (\PWRS == Zero)))
3676 {
3677 EPL1 = \_SB.IETM.P152
3678 EPL2 = \_SB.IETM.P154
3679 EPLT = \_SB.PC00.TCPU.CTN2 (\_SB.IETM.P153)
3680 }
3681 ElseIf ((\S0IX == 0x03))
3682 {
3683 EPL1 = \_SB.IETM.P164
3684 EPL2 = \_SB.IETM.P166
3685 EPLT = \_SB.PC00.TCPU.CTN2 (\_SB.IETM.P165)
3686 }
3687 ElseIf ((EPLE == One))
3688 {
3689 EPL1 = \_SB.IETM.P140
3690 EPL2 = \_SB.IETM.P143
3691 EPLT = \_SB.PC00.TCPU.CTN2 (\_SB.IETM.P142)
3692 If ((\_SB.PC00.LPCB.EC0.EC34 () > \_SB.IETM.CTOK (\_SB.IETM.P145)))
3693 {
3694 EPL1 = \_SB.IETM.P141
3695 }
3696 }
3697 Else
3698 {
3699 }
3700
3701 \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0xCA, EPL1, EPLT)
3702 \_SB.PC00.LPCB.EC0.EC88 (0xB5, 0xCB, EPL2, Zero)
3703 }
# ---- \_SB.IETM.WAK : re-applies REPL/REFM/REFD/SCGR after resume ----
3717 Method (WAK, 1, Serialized)
3718 {
3719 \_SB.PC00.LPCB.EC0.REPL ()
3720 \_SB.PC00.LPCB.EC0.REFM ()
3721 \_SB.PC00.LPCB.EC0.REFD ()
3722 \_SB.PC00.LPCB.EC0.SCGR (Zero)
3723 }
[-- Attachment #2: cf-sv1-platform_profile-quirk.patch --]
[-- Type: text/plain, Size: 4037 bytes --]
From c7636af72db7a4c649914ff3fe9bb67b184a16d9 Mon Sep 17 00:00:00 2001
From: Wu Zhipeng <zepwu@foxmail.com>
Date: Mon, 12 Oct 2026 11:35:21 +0800
Subject: [PATCH] platform/x86: panasonic-laptop: add platform_profile quirk
for CF-SV1-2
Add a platform_profile quirk for the Panasonic CF-SV1-2 (Let's Note,
Tiger Lake), verified on hardware with BIOS V2.00L23. The firmware
exposes the same fan and TDP policy controls as CF-SR4 (CEFM/EPLE state
variables, _SB.IETM P14x/P15x power tables, and the WAK ->
REPL/REFM/REFD/SCGR re-apply chain), so the model maps onto
COOL/QUIET/PERFORMANCE following the CF-SR4 table; the remaining
fan/TDP combination is reported as CUSTOM by get().
Measured on the unit, the fan policy toggle dominates: in the
power-saver regime the CPU temperature differs by 5.0 C between the
high-fan and quiet curves (7-8 C with a more aggressive EPP), while
the TDP toggle moves package power by <= 0.1 W in that regime and by
~0.5 W at most in the others.
Model-specific behaviour is captured in comments because it does not
generalise across models: the SEFM curve-byte values are swapped
against CF-SR4, EPLE/SEPL select an IETM table PL rather than a fixed
TDP cap, _FST[1] is a fan setpoint and not RPM, and a nonzero TFN1._FSL
override masks a profile change until it is released.
Signed-off-by: Wu Zhipeng <zepwu@foxmail.com>
---
Starting point against RFC v3, not for direct merge. Rebased onto the
v3 pcc_profile platform_driver; model notes updated to v3's method
names (CEFM/EPLE read, SEFM/SEPL write) and the CUSTOM handling
aligns with get() reporting unrecognised states as CUSTOM.
---
drivers/platform/x86/panasonic-laptop.c | 43 +++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index c4b13a3..aabc781 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -307,6 +307,41 @@ static struct pcc_profile_quirk quirk_cf_sr4 = {
},
};
+/*
+ * Model notes, CF-SV1-2 (verified on hardware, BIOS V2.00L23):
+ * - The fan request itself is a boolean (SEFM/CEFM), but the curve byte
+ * the EC maps it to is model specific (CF-SV1: 0x00 cool / 0x05 quiet /
+ * 0x02 in S0i3, i.e. the quiet and S0i3 bytes are swapped relative to
+ * CF-SR4). Only the boolean is portable; do not document the curve
+ * byte values as universal.
+ * - EPLE reports the TDP state and SEPL applies it; EPLE = 1 selects an
+ * IETM table PL and EPLE = 0 means no limit (EPL1 = EPL2 = 0xFF). The
+ * EC derives the actual value from the IETM P14x/P15x tables, so the
+ * resulting limit is not a single fixed number.
+ * - _FST[1] is the fan setpoint (PEN1), not RPM.
+ * - While TFN1._FSL holds a nonzero override the EC ignores the curve
+ * register, so a profile change is masked until the override is
+ * released; this driver deliberately does not touch _FSL.
+ * - At boot the EC comes up with CEFM = 0 and EPLE = 1 (those are the
+ * AML reset values), i.e. active + locked / COOL.
+ */
+static struct pcc_profile_quirk quirk_cf_sv1 = {
+ .platform_profiles = {
+ [PLATFORM_PROFILE_QUIET] = {
+ .fan_mode = PCC_FAN_MODE_PASSIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_COOL] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_LOCKED,
+ },
+ [PLATFORM_PROFILE_PERFORMANCE] = {
+ .fan_mode = PCC_FAN_MODE_ACTIVE,
+ .tdp_mode = PCC_TDP_MODE_UNLOCKED,
+ },
+ },
+};
+
static struct pcc_profile_quirk quirk_cf_qv9 = {
.platform_profiles = {
[PLATFORM_PROFILE_BALANCED] = {
@@ -355,6 +390,14 @@ static const struct dmi_system_id pcc_profile_quirks[] = {
},
.driver_data = &quirk_cf_sr4,
},
+ {
+ .ident = "Panasonic Corporation CFSV1-2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CFSV1-2"),
+ },
+ .driver_data = &quirk_cf_sv1,
+ },
{
.ident = "Panasonic Corporation CFQV9-1",
.matches = {
--
2.53.0
[-- Attachment #3: summary-ac.csv --]
[-- Type: text/plain, Size: 303 bytes --]
state,n,pkgW_mean_window,pkgW_min,pkgW_max,pkg_temp_max_C_median,notes
high(active)+locked,3,6.28,6.21,6.33,41.0,SEFM/SEPL=0_1
quiet(passive)+locked,3,6.23,6.21,6.34,45.0,SEFM/SEPL=1_1
quiet(passive)+unlocked,3,6.22,6.19,6.26,47.0,SEFM/SEPL=1_0
high(active)+unlocked,3,6.23,6.22,6.25,43.0,SEFM/SEPL=0_0
[-- Attachment #4: summary-battery.csv --]
[-- Type: text/plain, Size: 361 bytes --]
state,n,pkgW_mean_window,pkg_temp_max_C,notes
high(active)+locked,1,9.98,48.0,SEFM/SEPL=0_1; EPP=performance; S0IX=PWRS=0
quiet(passive)+locked,1,10.01,57.0,SEFM/SEPL=1_1; EPP=performance; S0IX=PWRS=0
quiet(passive)+unlocked,1,10.42,63.0,SEFM/SEPL=1_0; EPP=performance; S0IX=PWRS=0
high(active)+unlocked,1,10.20,53.0,SEFM/SEPL=0_0; EPP=performance; S0IX=PWRS=0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support
2026-10-12 3:42 ` Wu Zhipeng
@ 2026-10-15 9:55 ` Alex Yeo
2026-10-16 2:57 ` Wu Zhipeng
0 siblings, 1 reply; 5+ messages in thread
From: Alex Yeo @ 2026-10-15 9:55 UTC (permalink / raw)
To: Wu Zhipeng
Cc: platform-driver-x86, linux-acpi, Ilpo Jarvinen, Hans de Goede,
Hans de Goede, Kenneth Chan
Hi Wu,
Thanks for testing the patch on a CF-SV1.
While the fan curve was observed to change, it is interesting to note
that the CPU wattage appears to stay the same after an unlock.
Based on the data, pkg max temp is a bit low which does indicate the
possible presence of a firmware level power limit.
If you do find the time to test,
may I ask if you have thermal and power data when running Windows?
Initial observations that have resulted in the creation of this patch
series were made with a power meter (can be a watt meter from the wall
or a watt meter from a USB-C PD monitor).
On the SR4, maximum on Linux was around 42W, while Windows had spikes
that went up to ~60W (on the AC charger) or ~80W on a 100W USB-C PD
charger. (in addition to the higher CPU temps with all settings on both
Windows and Panasonic utilities set to performance)
Application of this patch on Linux with TLP set to performance resulted
in similar observations when testing.
Additionally, may I ask if you have tested to see if thermald affects
the fan or power setting?
Running it once in my case with the defaults has resulted in power being
unlocked until next reboot. This change can be observed with both
turbostat and a watt meter when plugged in.
The DMI quirk code is appreciated but I'd like to clarify the power
unlock part first before including it.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support
2026-10-15 9:55 ` Alex Yeo
@ 2026-10-16 2:57 ` Wu Zhipeng
0 siblings, 0 replies; 5+ messages in thread
From: Wu Zhipeng @ 2026-10-16 2:57 UTC (permalink / raw)
To: Alex Yeo
Cc: platform-driver-x86, linux-acpi, Ilpo Jarvinen, Hans de Goede,
Kenneth Chan
Hi Alex,
Thanks for the follow-up. A correction first, then what I can say about
the power unlock, and the tests you asked for.
1. Correction to my previous AC numbers
The 12 AC runs behind the tables in my last mail have to be discounted:
they were taken with the machine in its efficiency profile -- EPP=power
with turbo disabled -- and a small thermal control tool I wrote was still
running, which re-applies the profile's EPP. Only the battery session was
pinned to EPP=performance.
AC session (last mail) : epp=power 5412-5510 e/s
Battery session : epp=performance 8122-8497 e/s
Earlier AC, same day : epp=performance 8143-8515 e/s
On this CPU, EPP=power at 8-thread load draws ~8.5 W and the PL is not
the binding constraint, so "the TDP toggle moves essentially nothing" is
an artifact of that state, not firmware behaviour. The fan deltas are
unaffected (both arms shared the state); it is the power columns that
should be disregarded, not the fan conclusion.
I will rerun the AC matrix with EPP=performance, turbo enabled and the
tool stopped, and send the corrected table.
2. What I can say about the power unlock now
TL;DR: on this unit the EC enforces its own power limit, and any
SEFM/SEPL write re-derives the EC PL registers from the IETM tables, so
a direct PL write does not stick. The ~10 W "locked" plateau is EPP plus
that EC enforcement, not the PL failing to bind; with EPP=performance
and turbo on the PL binds again, and "unlocked" only removes the
firmware table PL.
Details:
- Both PL write paths bind with EPP=performance and turbo on: on the
direct path, set points of 3/10/20 W give 3.7/9.9/19.9 W and 255 gives
~26 W (the RAPL ceiling, not a firmware cap); on the SEFM/SEPL path,
set points of 10/15/18/20 W give 9.98/14.98/17.98/19.98 W (package
reaching ~4.2 GHz). An earlier ~17 W reading for the 20 W set point was
a thermal state, not a wall.
- MSR 0x610 (RAPL PL1/PL2) stays at 28 W / 60 W across all of that.
The EC enforces the limit through its own path instead of
reprogramming the RAPL registers, so the ~10 W of the locked states
does not show up in the MSR either, and the missing TDPL() call is
not the explanation.
- Any SEFM/SEPL write re-runs REFM/REPL, which re-derives 0xCA/0xCB
from the IETM tables (P140/P143 here) -- so a PL override does not
survive, which fits your decision to drive only SEFM/SEPL and leave
the PL alone.
3. Tests you asked for
- Windows: I am setting up a Windows 11 install on the same unit and
will run a comparable 8-thread load with HWiNFO logging package power
and temperature, and dump the ACPI tables from that side as well. I
have no wall or USB-C PD meter, so those will be package power
numbers.
- thermald: not tested yet; it belongs in the picture because it is the
component that drives _OSC -> SEFM/SEPL here. I will report CEFM/EPLE,
MSR 0x610 and package power before and after starting it. For
reference, in an earlier run a single SEPL write during 8-thread load
moved package power from ~8 W to 27.7 W within ~5 s, so an OS-level
writer can move the power state here.
On the DMI quirk: I would rather hold it until the above is in, so that
you are not asked to carry a model whose power side is still half
measured. Say the word if you would rather have it earlier.
If you have a load you prefer for the Windows comparison, I will match
it so the numbers are directly comparable.
Wu Zhipeng <zepwu@foxmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support
2026-10-07 19:04 [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support Alex Yeo
2026-10-12 3:42 ` Wu Zhipeng
@ 2026-10-16 11:12 ` Ilpo Järvinen
1 sibling, 0 replies; 5+ messages in thread
From: Ilpo Järvinen @ 2026-10-16 11:12 UTC (permalink / raw)
To: Alex Yeo; +Cc: platform-driver-x86, Hans de Goede, Kenneth Chan, LKML
On Thu, 8 Oct 2026, Alex Yeo wrote:
> Expose firmware thermal and power management policies via the
> platform_profile interface.
>
> Supported and tested models:
> - CF-RZ6 (2016)
> - CF-SV8 (2019)
> - CF-QV9 (2020)
> - CF-SR4 (2024)
>
> Firmware fan operating mode and the TDP limit switch are mapped onto
> the Linux platform_profile ABI.
>
> This implementation queries the firmware for every get().
> CUSTOM is returned when the current state is not recognized.
> The set() callback writes the values regardless of what the
> current fan mode and TDP limit is.
>
> - Introduce as separate platform_driver
> - Used DEFINE for driver and platform_profile device name
> - DMI quirks restrict support to tested models
> - When raising TDP, fan mode is switched first
> - When limiting TDP, TDP limit is applied first
> - ACPI handle for the EC device was used as the EC path varies
> slightly between models.
> - This device is optional: keep hotkey and everything already
> loaded if profile driver fails for any reason.
>
> Signed-off-by: Alex Yeo <alexyeo362@gmail.com>
> ---
> v3:
> - Removed sysfs.h import
> - Refactored platform profile probe function to use a different pattern
> (check warn_on, continue on double unset, then set)
>
> v2: https://lore.kernel.org/platform-driver-x86/20261006085021.853827-1-alexyeo362@gmail.com/
> - Refactored code to use its own platform_driver
> - Removed DEFINE_FREE and its corresponding __free as its addition
> in this module is inappropriate
> - Removed pr_fmt line as its addition to this patch is inappropriate
> - Added DMI check during __init to prevent attempt of platform profile
> device loading on unsupported hardware
> - Fixed use of enums as truth values by adding explicit comparisons
> - Added *profile_pdev to keep track of new platform_driver
> - Removed extra spaces in the quirk definitions
> - Added helper function for calling EC methods for simplicity
> - Use ARRAY_SIZE() for for loops and added include for array_size.h
> - Refactored pcc_profile to be a pointer in platform profile set
> function
> - Use WARN_ON() when a malformed quirk is detected in the platform
> profile probe function
> - Avoid use of status as variable name if not acpi_status
> - Added Kconfig line for ACPI_PLATFORM_PROFILE
> - Removed bool .use_platform_profiles from the quirks struct as it is
> no longer needed as a result of the platform_driver being separate.
> - Moved DMI quirk matching in probe function before devm_kzalloc of
> driver struct
>
> v1: https://lore.kernel.org/platform-driver-x86/20260805180544.1134916-1-alexyeo362@gmail.com/
>
> This RFC patch adds support for mapping firmware thermal and CPU power
> operating policy to platform_profile for Panasonic Let's Note laptops.
> This originally started as a hwmon and thermal patch, but further
> analysis had revealed that these are firmware power and thermal
> policies as opposed to fan control.
>
> Panasonic firmware exposes firmware operating policy through two
> methods:
> - Fan cooling policy (active/passive)
> - Processor TDP limit (capped/uncapped)
>
> At boot, firmware defaults to an active cooling policy with the
> CPU TDP capped. Vendor drivers / DPTF normally unlocks this on
> Windows.
>
> These 2 methods map to 4 possible distinct states. Only states that
> correspond to the platform_profile ABI are mapped and exposed as
> choices. Unmapped states will be returned as "custom" by the driver.
>
> I have tested the following models:
> - CF-RZ6 (2016)
> - CF-SV8 (2019)
> - CF-QV9 (2020)
> - CF-SR4 (2024)
>
> State to platform_profile mapping:
>
> Profile | Fan Mode | TDP
> +--------------------------------+-----------+------------+
> PLATFORM_PROFILE_COOL (SR4 only)| Active | Locked
> PLATFORM_PROFILE_BALANCED | Active | Locked
> PLATFORM_PROFILE_QUIET | Passive | Locked
> PLATFORM_PROFILE_PERFORMANCE | Active | Unlocked
> CUSTOM (unmapped on all) | Passive | Unlocked
>
> Across all of them, the fan and TDP modes are toggled by the same ACPI
> methods. However, there is a slight variation in its interpretation based
> on testing results.
>
> These methods are normally called by the DPTF/IPF _OSC handshake.
> Depending on the capability bits, the firmware will alter its thermal
> and power policy.
>
> \_SB.IETM Implementation A:
> - CF-RZ6 (2016)
> - CF-SV8 (2019)
>
> \_SB.IETM Implementation B:
> - CF-QV9 (2020)
> - CF-SR4 (2024)
>
> Models belonging to the same group share what appears to be identical
> AML code for the _OSC.
>
> This patch only exposes the ability to read and set these firmware
> toggles. The get() callback queries current firmware state and returns
> the mapped platform_profile. The set() callback sets the target state
> regardless of current state.
>
> Proposed mappings in this patch are defined in the quirks struct.
> Activation of platform_profile is gated by DMI checks.
>
> For every model except the SR4, only balanced and performance profiles
> are defined. This is because the aggressive fan paired with the locked
> TDP on the SR4 more appropriately correspond to the cool profile.
>
> Test results for the proposed mappings are below.
>
> Test results:
> ================
> (CPU PkgWatt: peak / sustained)
> (Sysbench CPU: all available threads)
>
> CF-RZ6: (i5-7Y57, Startup default: Balanced)
>
> Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
> +-----------------+-----------------+----------------+-------------------+
> BALANCED | 10.0W / 9.0W | 19712 | 57C
> PERFORMANCE | 18.9W / 14.9W | 29132 | 72C
>
> CF-SV8: (i5-8365U, Startup default: Balanced)
>
> Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
> +-----------------+------------------+----------------+-------------------+
> BALANCED | 9.9W / 9.9W | 44695 | 58C
> PERFORMANCE | 29.0W / 19.9W | 71747 | 81C
>
> CF-QV9: (i5-10310U, Startup default: Balanced)
>
> Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
> +-----------------+------------------+----------------+-------------------+
> BALANCED | 10.0W / 9.9W | 46545 | 59C
> PERFORMANCE | 18.9W / 14.9W | 57416 | 70C
>
> CF-SR4: (i5-1345U, Startup default: Cool)
>
> Platform Profile | CPU PkgWatt | Sysbench CPU | Stress Temp (CPU)
> +-----------------+------------------+----------------+-------------------+
> COOL | 12.0W / 12.0W | 177139 | 66C
> QUIET | 12.0W / 12.0W | 176315 | 73C-75C
> PERFORMANCE | 29.6W / 21.3W | 247602 | 98C-100C
>
> CF-SR4 Notes:
> - QUIET: downclock/throttle at 75C, no increase in package_throttle_count
> - PERFORMANCE: stable boost clock, package_throttle_count increase
>
>
> Other notes:
> ===============
>
> Additional notes about thermald:
> During testing, I have observed that running thermald for the first time
> since boot will trigger an _OSC negotiation that will unlock CPU TDP for
> new models (IETM Implementation B). Once thermald is running, exits or
> runs again, these bits were not observed to change again during testing.
>
> Running thermald on older models (IETM Implementation A) does not touch
> the fan curve or TDP mode setting.
>
> Suspend: observed to not alter the values set prior to suspend
>
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/panasonic-laptop.c | 399 +++++++++++++++++++++++-
> 2 files changed, 399 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 957034f39e4e..571c09152310 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -820,6 +820,7 @@ config PANASONIC_LAPTOP
> depends on ACPI_VIDEO=n || ACPI_VIDEO
> depends on SERIO_I8042 || SERIO_I8042 = n
> select INPUT_SPARSEKMAP
> + select ACPI_PLATFORM_PROFILE
> help
> This driver adds support for access to backlight control and hotkeys
> on Panasonic Let's Note laptops.
> diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
> index 19d194ff37ca..c4b13a3554e3 100644
> --- a/drivers/platform/x86/panasonic-laptop.c
> +++ b/drivers/platform/x86/panasonic-laptop.c
> @@ -120,9 +120,13 @@
> */
>
> #include <linux/acpi.h>
> +#include <linux/array_size.h>
> #include <linux/backlight.h>
> #include <linux/bits.h>
> #include <linux/ctype.h>
> +#include <linux/dmi.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> #include <linux/i8042.h>
> #include <linux/init.h>
> #include <linux/input.h>
> @@ -130,6 +134,8 @@
> #include <linux/kernel.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/platform_profile.h>
> +#include <linux/printk.h>
> #include <linux/seq_file.h>
> #include <linux/serio.h>
> #include <linux/slab.h>
> @@ -158,10 +164,37 @@ MODULE_LICENSE("GPL");
> #define ECO_MODE_OFF 0x00
> #define ECO_MODE_ON 0x80
>
> +#define PCC_ACPI_FAN_ACTIVE_MODE 0x00
> +#define PCC_ACPI_FAN_PASSIVE_MODE 0x01
> +#define PCC_ACPI_TDP_LIMIT_ON 0x01
> +#define PCC_ACPI_TDP_LIMIT_OFF 0x00
> +
> #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
> +#define PROFILE_PCC_DRIVER_NAME "panasonic-platform-profile"
>
> #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
>
> +enum pcc_profile_fan_mode {
> + PCC_FAN_MODE_UNSET = 0,
> + PCC_FAN_MODE_ACTIVE,
> + PCC_FAN_MODE_PASSIVE,
> +};
> +
> +enum pcc_profile_tdp_mode {
> + PCC_TDP_MODE_UNSET = 0,
> + PCC_TDP_MODE_LOCKED,
> + PCC_TDP_MODE_UNLOCKED,
> +};
> +
> +struct pcc_platform_profile {
> + enum pcc_profile_fan_mode fan_mode;
> + enum pcc_profile_tdp_mode tdp_mode;
> +};
> +
> +struct pcc_profile_quirk {
> + struct pcc_platform_profile platform_profiles[PLATFORM_PROFILE_LAST];
> +};
> +
> /* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
> ECO_MODEs: 0x03 = off, 0x83 = on
> */
> @@ -251,6 +284,104 @@ struct pcc_acpi {
> u32 sinf[] __counted_by(num_sifr);
> };
>
> +struct pcc_profile {
> + acpi_handle ec_handle;
> + const struct pcc_profile_quirk *quirks;
> + struct device *platform_profile_dev;
> +};
> +
> +static struct pcc_profile_quirk quirk_cf_sr4 = {
> + .platform_profiles = {
> + [PLATFORM_PROFILE_QUIET] = {
> + .fan_mode = PCC_FAN_MODE_PASSIVE,
> + .tdp_mode = PCC_TDP_MODE_LOCKED,
> + },
> + [PLATFORM_PROFILE_COOL] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_LOCKED,
> + },
> + [PLATFORM_PROFILE_PERFORMANCE] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_UNLOCKED,
> + },
> + },
> +};
> +
> +static struct pcc_profile_quirk quirk_cf_qv9 = {
> + .platform_profiles = {
> + [PLATFORM_PROFILE_BALANCED] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_LOCKED,
> + },
> + [PLATFORM_PROFILE_PERFORMANCE] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_UNLOCKED,
> + },
> + },
> +};
> +
> +static struct pcc_profile_quirk quirk_cf_sv8 = {
> + .platform_profiles = {
> + [PLATFORM_PROFILE_BALANCED] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_LOCKED,
> + },
> + [PLATFORM_PROFILE_PERFORMANCE] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_UNLOCKED,
> + },
> + },
> +};
> +
> +static struct pcc_profile_quirk quirk_cf_rz6 = {
> + .platform_profiles = {
> + [PLATFORM_PROFILE_BALANCED] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_LOCKED,
> + },
> + [PLATFORM_PROFILE_PERFORMANCE] = {
> + .fan_mode = PCC_FAN_MODE_ACTIVE,
> + .tdp_mode = PCC_TDP_MODE_UNLOCKED,
> + },
> + },
> +};
Multiple of these look exact duplicates. In such cases, we try to reuse
the same struct even if we cannot then exactly match the name with the
model.
> +static const struct dmi_system_id pcc_profile_quirks[] = {
> + {
> + .ident = "Panasonic Connect Co., Ltd. CFSR4-1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Connect Co., Ltd."),
> + DMI_MATCH(DMI_PRODUCT_NAME, "CFSR4-1"),
> + },
> + .driver_data = &quirk_cf_sr4,
> + },
> + {
> + .ident = "Panasonic Corporation CFQV9-1",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "CFQV9-1"),
> + },
> + .driver_data = &quirk_cf_qv9,
> + },
> + {
> + .ident = "Panasonic Corporation CFSV8-2",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "CFSV8-2"),
> + },
> + .driver_data = &quirk_cf_sv8,
> + },
> + {
> + .ident = "Panasonic Corporation CFRZ6-2",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Panasonic Corporation"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "CFRZ6-2"),
> + },
> + .driver_data = &quirk_cf_rz6,
> + },
> + {},
> +};
--
i.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-16 11:12 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-07 19:04 [RFC PATCH v3] platform/x86: panasonic-laptop: Add platform_profile support Alex Yeo
2026-10-12 3:42 ` Wu Zhipeng
2026-10-15 9:55 ` Alex Yeo
2026-10-16 2:57 ` Wu Zhipeng
2026-10-16 11:12 ` Ilpo Järvinen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.