]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ACPI: fan: Add fan speed reporting for fans with only _FST
authorJoshua Grisham <josh@joshuagrisham.com>
Sun, 2 Nov 2025 14:35:13 +0000 (09:35 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 13 Nov 2025 20:34:00 +0000 (15:34 -0500)
[ Upstream commit 6c00f29f74cb2c063b6f31a0b6d73f9db132b9ac ]

Add support for ACPI fans with _FST to report their speed even if they do
not support fan control.

As suggested by Armin Wolf [1] and per the Windows Thermal Management
Design Guide [2], Samsung Galaxy Book series devices (and possibly many
more devices where the Windows guide was strictly followed) only implement
the _FST method and do not support ACPI-based fan control.

Currently, these fans are not supported by the kernel driver but this patch
will make some very small adjustments to allow them to be supported.

This patch is tested and working for me on a Samsung Galaxy Book2 Pro whose
DSDT (and several other Samsung Galaxy Book series notebooks which
currently have the same issue) can be found at [3].

Link: https://lore.kernel.org/platform-driver-x86/53c5075b-1967-45d0-937f-463912dd966d@gmx.de
Link: https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide
Link: https://github.com/joshuagrisham/samsung-galaxybook-extras/tree/8e3087a06b8bdcdfdd081367af4b744a56cc4ee9/dsdt
Signed-off-by: Joshua Grisham <josh@joshuagrisham.com>
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Link: https://patch.msgid.link/20250222094407.9753-1-josh@joshuagrisham.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Stable-dep-of: d91a1d129b63 ("ACPI: fan: Use platform device for devres-related actions")
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/acpi/fan.h
drivers/acpi/fan_attr.c
drivers/acpi/fan_core.c
drivers/acpi/fan_hwmon.c

index 72a60c87e76f2244c3f7cfff764f19ecb5bec051..c1c527f620acc983403be199ea5a6cb7511dd6f8 100644 (file)
@@ -49,6 +49,7 @@ struct acpi_fan_fst {
 struct acpi_fan {
        acpi_handle handle;
        bool acpi4;
+       bool has_fst;
        struct acpi_fan_fif fif;
        struct acpi_fan_fps *fps;
        int fps_count;
index c5f4b819ceded5c55c714de601aca0516ec8e347..95528937a57291d38a94ad5f61f48459d9662a8b 100644 (file)
@@ -75,15 +75,6 @@ int acpi_fan_create_attributes(struct acpi_device *device)
        struct acpi_fan *fan = acpi_driver_data(device);
        int i, status;
 
-       sysfs_attr_init(&fan->fine_grain_control.attr);
-       fan->fine_grain_control.show = show_fine_grain_control;
-       fan->fine_grain_control.store = NULL;
-       fan->fine_grain_control.attr.name = "fine_grain_control";
-       fan->fine_grain_control.attr.mode = 0444;
-       status = sysfs_create_file(&device->dev.kobj, &fan->fine_grain_control.attr);
-       if (status)
-               return status;
-
        /* _FST is present if we are here */
        sysfs_attr_init(&fan->fst_speed.attr);
        fan->fst_speed.show = show_fan_speed;
@@ -92,7 +83,19 @@ int acpi_fan_create_attributes(struct acpi_device *device)
        fan->fst_speed.attr.mode = 0444;
        status = sysfs_create_file(&device->dev.kobj, &fan->fst_speed.attr);
        if (status)
-               goto rem_fine_grain_attr;
+               return status;
+
+       if (!fan->acpi4)
+               return 0;
+
+       sysfs_attr_init(&fan->fine_grain_control.attr);
+       fan->fine_grain_control.show = show_fine_grain_control;
+       fan->fine_grain_control.store = NULL;
+       fan->fine_grain_control.attr.name = "fine_grain_control";
+       fan->fine_grain_control.attr.mode = 0444;
+       status = sysfs_create_file(&device->dev.kobj, &fan->fine_grain_control.attr);
+       if (status)
+               goto rem_fst_attr;
 
        for (i = 0; i < fan->fps_count; ++i) {
                struct acpi_fan_fps *fps = &fan->fps[i];
@@ -109,18 +112,18 @@ int acpi_fan_create_attributes(struct acpi_device *device)
 
                        for (j = 0; j < i; ++j)
                                sysfs_remove_file(&device->dev.kobj, &fan->fps[j].dev_attr.attr);
-                       goto rem_fst_attr;
+                       goto rem_fine_grain_attr;
                }
        }
 
        return 0;
 
-rem_fst_attr:
-       sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
-
 rem_fine_grain_attr:
        sysfs_remove_file(&device->dev.kobj, &fan->fine_grain_control.attr);
 
+rem_fst_attr:
+       sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
+
        return status;
 }
 
@@ -129,9 +132,13 @@ void acpi_fan_delete_attributes(struct acpi_device *device)
        struct acpi_fan *fan = acpi_driver_data(device);
        int i;
 
+       sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
+
+       if (!fan->acpi4)
+               return;
+
        for (i = 0; i < fan->fps_count; ++i)
                sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr);
 
-       sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
        sysfs_remove_file(&device->dev.kobj, &fan->fine_grain_control.attr);
 }
index cee8f1c78ee7121dc2f4ef95de6f18d96729ac05..5a8a254e9a78787da072df37e9809ec6f2d68c60 100644 (file)
@@ -208,12 +208,16 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = {
  * --------------------------------------------------------------------------
 */
 
+static bool acpi_fan_has_fst(struct acpi_device *device)
+{
+       return acpi_has_method(device->handle, "_FST");
+}
+
 static bool acpi_fan_is_acpi4(struct acpi_device *device)
 {
        return acpi_has_method(device->handle, "_FIF") &&
               acpi_has_method(device->handle, "_FPS") &&
-              acpi_has_method(device->handle, "_FSL") &&
-              acpi_has_method(device->handle, "_FST");
+              acpi_has_method(device->handle, "_FSL");
 }
 
 static int acpi_fan_get_fif(struct acpi_device *device)
@@ -337,7 +341,12 @@ static int acpi_fan_probe(struct platform_device *pdev)
        device->driver_data = fan;
        platform_set_drvdata(pdev, fan);
 
-       if (acpi_fan_is_acpi4(device)) {
+       if (acpi_fan_has_fst(device)) {
+               fan->has_fst = true;
+               fan->acpi4 = acpi_fan_is_acpi4(device);
+       }
+
+       if (fan->acpi4) {
                result = acpi_fan_get_fif(device);
                if (result)
                        return result;
@@ -345,7 +354,9 @@ static int acpi_fan_probe(struct platform_device *pdev)
                result = acpi_fan_get_fps(device);
                if (result)
                        return result;
+       }
 
+       if (fan->has_fst) {
                result = devm_acpi_fan_create_hwmon(device);
                if (result)
                        return result;
@@ -353,9 +364,9 @@ static int acpi_fan_probe(struct platform_device *pdev)
                result = acpi_fan_create_attributes(device);
                if (result)
                        return result;
+       }
 
-               fan->acpi4 = true;
-       } else {
+       if (!fan->acpi4) {
                result = acpi_device_update_power(device, NULL);
                if (result) {
                        dev_err(&device->dev, "Failed to set initial power state\n");
@@ -401,7 +412,7 @@ err_remove_link:
 err_unregister:
        thermal_cooling_device_unregister(cdev);
 err_end:
-       if (fan->acpi4)
+       if (fan->has_fst)
                acpi_fan_delete_attributes(device);
 
        return result;
@@ -411,7 +422,7 @@ static void acpi_fan_remove(struct platform_device *pdev)
 {
        struct acpi_fan *fan = platform_get_drvdata(pdev);
 
-       if (fan->acpi4) {
+       if (fan->has_fst) {
                struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
 
                acpi_fan_delete_attributes(device);
index 787bf8f40077e574718f2f45371e49e6b3271b01..4209a9923efcb948ac9d5b5c87b20b50b824b429 100644 (file)
@@ -43,6 +43,10 @@ static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_
                case hwmon_fan_input:
                        return 0444;
                case hwmon_fan_target:
+                       /* Only acpi4 fans support fan control. */
+                       if (!fan->acpi4)
+                               return 0;
+
                        /*
                         * When in fine grain control mode, not every fan control value
                         * has an associated fan performance state.
@@ -57,6 +61,10 @@ static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_
        case hwmon_power:
                switch (attr) {
                case hwmon_power_input:
+                       /* Only acpi4 fans support fan control. */
+                       if (!fan->acpi4)
+                               return 0;
+
                        /*
                         * When in fine grain control mode, not every fan control value
                         * has an associated fan performance state.