77 * Copyright (C) 2022 Intel Corporation. All rights reserved.
88 */
99
10+ #include <linux/bits.h>
1011#include <linux/kernel.h>
12+ #include <linux/limits.h>
13+ #include <linux/math.h>
14+ #include <linux/math64.h>
1115#include <linux/module.h>
1216#include <linux/init.h>
1317#include <linux/types.h>
1418#include <linux/uaccess.h>
19+ #include <linux/uuid.h>
1520#include <linux/thermal.h>
1621#include <linux/acpi.h>
1722#include <linux/platform_device.h>
2126
2227#define ACPI_FAN_NOTIFY_STATE_CHANGED 0x80
2328
29+ /*
30+ * Defined inside the "Fan Noise Signal" section at
31+ * https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide.
32+ */
33+ static const guid_t acpi_fan_microsoft_guid = GUID_INIT (0xA7611840 , 0x99FE , 0x41AE , 0xA4 , 0x88 ,
34+ 0x35 , 0xC7 , 0x59 , 0x26 , 0xC8 , 0xEB );
35+ #define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1
36+ #define ACPI_FAN_DSM_SET_TRIP_POINTS 2
37+ #define ACPI_FAN_DSM_GET_OPERATING_RANGES 3
38+
39+ /*
40+ * Ensures that fans with a very low trip point granularity
41+ * do not send too many notifications.
42+ */
43+ static uint min_trip_distance = 100 ;
44+ module_param (min_trip_distance , uint , 0 );
45+ MODULE_PARM_DESC (min_trip_distance , "Minimum distance between fan speed trip points in RPM" );
46+
2447static const struct acpi_device_id fan_device_ids [] = {
2548 ACPI_FAN_DEVICE_IDS ,
2649 {"" , 0 },
@@ -310,6 +333,132 @@ static int acpi_fan_get_fps(struct acpi_device *device)
310333 return status ;
311334}
312335
336+ static int acpi_fan_dsm_init (struct device * dev )
337+ {
338+ union acpi_object dummy = {
339+ .package = {
340+ .type = ACPI_TYPE_PACKAGE ,
341+ .count = 0 ,
342+ .elements = NULL ,
343+ },
344+ };
345+ struct acpi_fan * fan = dev_get_drvdata (dev );
346+ union acpi_object * obj ;
347+ int ret = 0 ;
348+
349+ if (!acpi_check_dsm (fan -> handle , & acpi_fan_microsoft_guid , 0 ,
350+ BIT (ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY ) |
351+ BIT (ACPI_FAN_DSM_SET_TRIP_POINTS )))
352+ return 0 ;
353+
354+ dev_info (dev , "Using Microsoft fan extensions\n" );
355+
356+ obj = acpi_evaluate_dsm_typed (fan -> handle , & acpi_fan_microsoft_guid , 0 ,
357+ ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY , & dummy ,
358+ ACPI_TYPE_INTEGER );
359+ if (!obj )
360+ return - EIO ;
361+
362+ if (obj -> integer .value > U32_MAX )
363+ ret = - EOVERFLOW ;
364+ else
365+ fan -> fan_trip_granularity = obj -> integer .value ;
366+
367+ kfree (obj );
368+
369+ return ret ;
370+ }
371+
372+ static int acpi_fan_dsm_set_trip_points (struct device * dev , u64 upper , u64 lower )
373+ {
374+ union acpi_object args [2 ] = {
375+ {
376+ .integer = {
377+ .type = ACPI_TYPE_INTEGER ,
378+ .value = lower ,
379+ },
380+ },
381+ {
382+ .integer = {
383+ .type = ACPI_TYPE_INTEGER ,
384+ .value = upper ,
385+ },
386+ },
387+ };
388+ struct acpi_fan * fan = dev_get_drvdata (dev );
389+ union acpi_object in = {
390+ .package = {
391+ .type = ACPI_TYPE_PACKAGE ,
392+ .count = ARRAY_SIZE (args ),
393+ .elements = args ,
394+ },
395+ };
396+ union acpi_object * obj ;
397+
398+ obj = acpi_evaluate_dsm (fan -> handle , & acpi_fan_microsoft_guid , 0 ,
399+ ACPI_FAN_DSM_SET_TRIP_POINTS , & in );
400+ kfree (obj );
401+
402+ return 0 ;
403+ }
404+
405+ static int acpi_fan_dsm_start (struct device * dev )
406+ {
407+ struct acpi_fan * fan = dev_get_drvdata (dev );
408+ int ret ;
409+
410+ if (!fan -> fan_trip_granularity )
411+ return 0 ;
412+
413+ /*
414+ * Some firmware implementations only update the values returned by the
415+ * _FST control method when a notification is received. This usually
416+ * works with Microsoft Windows as setting up trip points will keep
417+ * triggering said notifications, but will cause issues when using _FST
418+ * without the Microsoft-specific trip point extension.
419+ *
420+ * Because of this, an initial notification needs to be triggered to
421+ * start the cycle of trip points updates. This is achieved by setting
422+ * the trip points sequencially to two separate ranges. As by the
423+ * Microsoft specification the firmware should trigger a notification
424+ * immediately if the fan speed is outside the trip point range. This
425+ * _should_ result in at least one notification as both ranges do not
426+ * overlap, meaning that the current fan speed needs to be outside at
427+ * least one range.
428+ */
429+ ret = acpi_fan_dsm_set_trip_points (dev , fan -> fan_trip_granularity , 0 );
430+ if (ret < 0 )
431+ return ret ;
432+
433+ return acpi_fan_dsm_set_trip_points (dev , fan -> fan_trip_granularity * 3 ,
434+ fan -> fan_trip_granularity * 2 );
435+ }
436+
437+ static int acpi_fan_dsm_update_trips_points (struct device * dev , struct acpi_fan_fst * fst )
438+ {
439+ struct acpi_fan * fan = dev_get_drvdata (dev );
440+ u64 upper , lower ;
441+
442+ if (!fan -> fan_trip_granularity )
443+ return 0 ;
444+
445+ if (!acpi_fan_speed_valid (fst -> speed ))
446+ return - EINVAL ;
447+
448+ upper = roundup_u64 (fst -> speed + min_trip_distance , fan -> fan_trip_granularity );
449+ if (fst -> speed <= min_trip_distance ) {
450+ lower = 0 ;
451+ } else {
452+ /*
453+ * Valid fan speed values cannot be larger than 32 bit, so
454+ * we can safely assume that no overflow will happen here.
455+ */
456+ lower = rounddown ((u32 )fst -> speed - min_trip_distance , fan -> fan_trip_granularity );
457+ }
458+
459+ return acpi_fan_dsm_set_trip_points (dev , upper , lower );
460+ }
461+
313462static void acpi_fan_notify_handler (acpi_handle handle , u32 event , void * context )
314463{
315464 struct device * dev = context ;
@@ -323,8 +472,13 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context
323472 * receive an ACPI event indicating that the fan state has changed.
324473 */
325474 ret = acpi_fan_get_fst (handle , & fst );
326- if (ret < 0 )
475+ if (ret < 0 ) {
327476 dev_err (dev , "Error retrieving current fan status: %d\n" , ret );
477+ } else {
478+ ret = acpi_fan_dsm_update_trips_points (dev , & fst );
479+ if (ret < 0 )
480+ dev_err (dev , "Failed to update trip points: %d\n" , ret );
481+ }
328482
329483 acpi_fan_notify_hwmon (dev );
330484 acpi_bus_generate_netlink_event ("fan" , dev_name (dev ), event , 0 );
@@ -394,6 +548,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
394548 }
395549
396550 if (fan -> has_fst ) {
551+ result = acpi_fan_dsm_init (& pdev -> dev );
552+ if (result )
553+ return result ;
554+
397555 result = devm_acpi_fan_create_hwmon (& pdev -> dev );
398556 if (result )
399557 return result ;
@@ -402,6 +560,12 @@ static int acpi_fan_probe(struct platform_device *pdev)
402560 if (result )
403561 return result ;
404562
563+ result = acpi_fan_dsm_start (& pdev -> dev );
564+ if (result ) {
565+ dev_err (& pdev -> dev , "Failed to start Microsoft fan extensions\n" );
566+ return result ;
567+ }
568+
405569 result = acpi_fan_create_attributes (device );
406570 if (result )
407571 return result ;
@@ -487,8 +651,14 @@ static int acpi_fan_suspend(struct device *dev)
487651
488652static int acpi_fan_resume (struct device * dev )
489653{
490- int result ;
491654 struct acpi_fan * fan = dev_get_drvdata (dev );
655+ int result ;
656+
657+ if (fan -> has_fst ) {
658+ result = acpi_fan_dsm_start (dev );
659+ if (result )
660+ dev_err (dev , "Failed to start Microsoft fan extensions: %d\n" , result );
661+ }
492662
493663 if (fan -> acpi4 )
494664 return 0 ;
0 commit comments