This patch adds functionality that switches the fan profile when the
platform profile is switched on the Microsoft Surface Pro 9.
Previously, the fan profile was not changed and that results in poor
thermal performance. This makes the behaviour and functionality identical
to what the Windows drivers do.
A plot of the different responses to system load, as well as recordings
from the SSAM bus can be found at [1]. Based on discussions with
Maximilian Luz there this patch proposes the following changes:
In surface_aggregator_registry:
- Rename ssam_node_tmp_pprof to ssam_node_tmp_perf_profile for clarity.
- Introduce ssam_node_tmp_perf_profile_with_fan that has the has_fan
boolean property set.
- Use the new ssam_node_tmp_perf_profile_with_fan for the Surface Pro 9.
In the platform profile module:
- Rename ssam_tmp_profile_device to ssam_platform_profile_device to make it
clear it handles more than just the TMP subsystem.
- Rename the enum conversion method to make distinction between TMP and FAN
clear.
- Add enum and set function & request for the fan profile.
- Ensure that if the module's has_fan boolean is true, the fan profile is
switched whenever the platform profile changes.
[1]: https://github.com/linux-surface/kernel/pull/145
Ivor Wanders (1):
platform/surface: platform_profile: add fan profile switching
.../surface/surface_aggregator_registry.c | 36 +++++---
.../surface/surface_platform_profile.c | 86 ++++++++++++++++---
2 files changed, 99 insertions(+), 23 deletions(-)
--
2.17.1
Change naming from tmp to platform profile to clarify the module may
interact with both the TMP and FAN subystems. Add functionality that
switches the fan profile when the platform profile is changed when
a fan is present.
Signed-off-by: Ivor Wanders <[email protected]>
---
.../surface/surface_aggregator_registry.c | 36 +++++---
.../surface/surface_platform_profile.c | 86 ++++++++++++++++---
2 files changed, 99 insertions(+), 23 deletions(-)
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index aeb3feae40ff..05fd98998d67 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -68,12 +68,26 @@ static const struct software_node ssam_node_bat_sb3base = {
.parent = &ssam_node_hub_base,
};
-/* Platform profile / performance-mode device. */
-static const struct software_node ssam_node_tmp_pprof = {
+/* Platform profile / performance-mode device without a fan. */
+static const struct software_node ssam_node_tmp_perf_profile = {
.name = "ssam:01:03:01:00:01",
.parent = &ssam_node_root,
};
+/* Platform profile / performance-mode device with a fan, such that
+ * the fan controller profile can also be switched.
+ */
+static const struct property_entry ssam_node_tmp_perf_profile_has_fan[] = {
+ PROPERTY_ENTRY_BOOL("has_fan"),
+ { }
+};
+
+static const struct software_node ssam_node_tmp_perf_profile_with_fan = {
+ .name = "ssam:01:03:01:00:01",
+ .parent = &ssam_node_root,
+ .properties = ssam_node_tmp_perf_profile_has_fan,
+};
+
/* Tablet-mode switch via KIP subsystem. */
static const struct software_node ssam_node_kip_tablet_switch = {
.name = "ssam:01:0e:01:00:01",
@@ -202,7 +216,7 @@ static const struct software_node ssam_node_pos_tablet_switch = {
*/
static const struct software_node *ssam_node_group_gen5[] = {
&ssam_node_root,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -213,7 +227,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_bat_sb3base,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_bas_dtx,
&ssam_node_hid_base_keyboard,
&ssam_node_hid_base_touchpad,
@@ -227,7 +241,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_hid_main_keyboard,
&ssam_node_hid_main_touchpad,
&ssam_node_hid_main_iid5,
@@ -239,7 +253,7 @@ static const struct software_node *ssam_node_group_sl5[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_hid_main_keyboard,
&ssam_node_hid_main_touchpad,
&ssam_node_hid_main_iid5,
@@ -252,7 +266,7 @@ static const struct software_node *ssam_node_group_sls[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_pos_tablet_switch,
&ssam_node_hid_sam_keyboard,
&ssam_node_hid_sam_penstash,
@@ -268,7 +282,7 @@ static const struct software_node *ssam_node_group_slg1[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -277,7 +291,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
NULL,
};
@@ -287,7 +301,7 @@ static const struct software_node *ssam_node_group_sp8[] = {
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile,
&ssam_node_kip_tablet_switch,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
@@ -304,7 +318,7 @@ static const struct software_node *ssam_node_group_sp9[] = {
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
- &ssam_node_tmp_pprof,
+ &ssam_node_tmp_perf_profile_with_fan,
&ssam_node_pos_tablet_switch,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
index a5a3941b3f43..e54d0a8f7daa 100644
--- a/drivers/platform/surface/surface_platform_profile.c
+++ b/drivers/platform/surface/surface_platform_profile.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface Platform Profile / Performance Mode driver for Surface System
- * Aggregator Module (thermal subsystem).
+ * Aggregator Module (thermal and fan subsystem).
*
* Copyright (C) 2021-2022 Maximilian Luz <[email protected]>
*/
@@ -14,6 +14,7 @@
#include <linux/surface_aggregator/device.h>
+// Enum for the platform performance profile sent to the TMP module.
enum ssam_tmp_profile {
SSAM_TMP_PROFILE_NORMAL = 1,
SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
@@ -21,15 +22,26 @@ enum ssam_tmp_profile {
SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
};
+// Enum for the fan profile sent to the FAN module. This fan profile is
+// only sent to the EC if the 'has_fan' property is set. The integers are
+// not a typo, they differ from the performance profile indices.
+enum ssam_fan_profile {
+ SSAM_FAN_PROFILE_NORMAL = 2,
+ SSAM_FAN_PROFILE_BATTERY_SAVER = 1,
+ SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
+ SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4,
+};
+
struct ssam_tmp_profile_info {
__le32 profile;
__le16 unknown1;
__le16 unknown2;
} __packed;
-struct ssam_tmp_profile_device {
+struct ssam_platform_profile_device {
struct ssam_device *sdev;
struct platform_profile_handler handler;
+ bool has_fan;
};
SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
@@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
.command_id = 0x03,
});
+SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, char, {
+ .target_category = SSAM_SSH_TC_FAN,
+ .target_id = SSAM_SSH_TID_SAM,
+ .command_id = 0x0e,
+ .instance_id = 0x01,
+});
+
static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
{
struct ssam_tmp_profile_info info;
@@ -62,7 +81,14 @@ static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile
return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
}
-static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
+static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
+{
+ char profile = p;
+
+ return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
+}
+
+static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
{
switch (p) {
case SSAM_TMP_PROFILE_NORMAL:
@@ -83,7 +109,8 @@ static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profi
}
}
-static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
+
+static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
{
switch (p) {
case PLATFORM_PROFILE_LOW_POWER:
@@ -105,20 +132,42 @@ static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profi
}
}
+static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
+{
+ switch (p) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ return SSAM_FAN_PROFILE_BATTERY_SAVER;
+
+ case PLATFORM_PROFILE_BALANCED:
+ return SSAM_FAN_PROFILE_NORMAL;
+
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
+
+ case PLATFORM_PROFILE_PERFORMANCE:
+ return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
+
+ default:
+ /* This should have already been caught by platform_profile_store(). */
+ WARN(true, "unsupported platform profile");
+ return -EOPNOTSUPP;
+ }
+}
+
static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
enum platform_profile_option *profile)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
enum ssam_tmp_profile tp;
int status;
- tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+ tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
status = ssam_tmp_profile_get(tpd->sdev, &tp);
if (status)
return status;
- status = convert_ssam_to_profile(tpd->sdev, tp);
+ status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
if (status < 0)
return status;
@@ -129,21 +178,32 @@ static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
enum platform_profile_option profile)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
int tp;
- tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+ tpd = container_of(pprof, struct ssam_platform_profile_device, handler);
+
+ tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
+ if (tp < 0)
+ return tp;
- tp = convert_profile_to_ssam(tpd->sdev, profile);
+ tp = ssam_tmp_profile_set(tpd->sdev, tp);
if (tp < 0)
return tp;
- return ssam_tmp_profile_set(tpd->sdev, tp);
+ if (tpd->has_fan) {
+ tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
+ if (tp < 0)
+ return tp;
+ tp = ssam_fan_profile_set(tpd->sdev, tp);
+ }
+
+ return tp;
}
static int surface_platform_profile_probe(struct ssam_device *sdev)
{
- struct ssam_tmp_profile_device *tpd;
+ struct ssam_platform_profile_device *tpd;
tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
if (!tpd)
@@ -154,6 +214,8 @@ static int surface_platform_profile_probe(struct ssam_device *sdev)
tpd->handler.profile_get = ssam_platform_profile_get;
tpd->handler.profile_set = ssam_platform_profile_set;
+ tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
+
set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
--
2.17.1
On 3/2/24 18:01, Ivor Wanders wrote:
> This patch adds functionality that switches the fan profile when the
> platform profile is switched on the Microsoft Surface Pro 9.
>
> Previously, the fan profile was not changed and that results in poor
> thermal performance. This makes the behaviour and functionality identical
> to what the Windows drivers do.
>
> A plot of the different responses to system load, as well as recordings
> from the SSAM bus can be found at [1]. Based on discussions with
> Maximilian Luz there this patch proposes the following changes:
>
> In surface_aggregator_registry:
> - Rename ssam_node_tmp_pprof to ssam_node_tmp_perf_profile for clarity.
> - Introduce ssam_node_tmp_perf_profile_with_fan that has the has_fan
> boolean property set.
> - Use the new ssam_node_tmp_perf_profile_with_fan for the Surface Pro 9.
>
> In the platform profile module:
> - Rename ssam_tmp_profile_device to ssam_platform_profile_device to make it
> clear it handles more than just the TMP subsystem.
> - Rename the enum conversion method to make distinction between TMP and FAN
> clear.
> - Add enum and set function & request for the fan profile.
> - Ensure that if the module's has_fan boolean is true, the fan profile is
> switched whenever the platform profile changes.
>
>
> [1]: https://github.com/linux-surface/kernel/pull/145
>
> Ivor Wanders (1):
> platform/surface: platform_profile: add fan profile switching
>
> .../surface/surface_aggregator_registry.c | 36 +++++---
> .../surface/surface_platform_profile.c | 86 ++++++++++++++++---
> 2 files changed, 99 insertions(+), 23 deletions(-)
>
Looks good to me.
Reviewed-by: Maximilian Luz <[email protected]>
On Sat, 2 Mar 2024, Ivor Wanders wrote:
> Change naming from tmp to platform profile to clarify the module may
> interact with both the TMP and FAN subystems. Add functionality that
> switches the fan profile when the platform profile is changed when
> a fan is present.
>
> Signed-off-by: Ivor Wanders <[email protected]>
> ---
> diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
> index a5a3941b3f43..e54d0a8f7daa 100644
> --- a/drivers/platform/surface/surface_platform_profile.c
> +++ b/drivers/platform/surface/surface_platform_profile.c
> @@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
> .command_id = 0x03,
> });
>
> +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, char, {
Is char correct type here or do you perhaps mean e.g., u8 that is more
approriate for 1-byte long binary data?
> + .target_category = SSAM_SSH_TC_FAN,
> + .target_id = SSAM_SSH_TID_SAM,
> + .command_id = 0x0e,
> + .instance_id = 0x01,
> +});
> +
> static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
> {
> struct ssam_tmp_profile_info info;
> @@ -62,7 +81,14 @@ static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile
> return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
> }
>
> -static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
> +static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
> +{
> + char profile = p;
u8 ?
--
i.
> Is char correct type here or do you perhaps mean e.g., u8 that is more
approriate for 1-byte long binary data? [...] u8 ?
Thanks for the review Ilpo. Agreed, u8 would be better, I also noticed that
this variable, and the profile_le one in the function just above it can be
const, I'll change this type to u8 and make both profile variable const in v2.
~Ivor