diff options
Diffstat (limited to 'drivers/platform/surface/surface_platform_profile.c')
| -rw-r--r-- | drivers/platform/surface/surface_platform_profile.c | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c new file mode 100644 index 000000000000..0e479e35e66e --- /dev/null +++ b/drivers/platform/surface/surface_platform_profile.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Platform Profile / Performance Mode driver for Surface System + * Aggregator Module (thermal and fan subsystem). + * + * Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <linux/unaligned.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_profile.h> +#include <linux/types.h> + +#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, + SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, + 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_platform_profile_device { + struct ssam_device *sdev; + struct device *ppdev; + bool has_fan; +}; + +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x02, +}); + +SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x03, +}); + +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, { + .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; + int status; + + status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); + if (status < 0) + return status; + + *p = le32_to_cpu(info.profile); + return 0; +} + +static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) +{ + const __le32 profile_le = cpu_to_le32(p); + + return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); +} + +static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p) +{ + const u8 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: + return PLATFORM_PROFILE_BALANCED; + + case SSAM_TMP_PROFILE_BATTERY_SAVER: + return PLATFORM_PROFILE_LOW_POWER; + + case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: + return PLATFORM_PROFILE_BALANCED_PERFORMANCE; + + case SSAM_TMP_PROFILE_BEST_PERFORMANCE: + return PLATFORM_PROFILE_PERFORMANCE; + + default: + dev_err(&sdev->dev, "invalid performance profile: %d", p); + return -EINVAL; + } +} + + +static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p) +{ + switch (p) { + case PLATFORM_PROFILE_LOW_POWER: + return SSAM_TMP_PROFILE_BATTERY_SAVER; + + case PLATFORM_PROFILE_BALANCED: + return SSAM_TMP_PROFILE_NORMAL; + + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; + + case PLATFORM_PROFILE_PERFORMANCE: + return SSAM_TMP_PROFILE_BEST_PERFORMANCE; + + default: + /* This should have already been caught by platform_profile_store(). */ + WARN(true, "unsupported platform profile"); + return -EOPNOTSUPP; + } +} + +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 device *dev, + enum platform_profile_option *profile) +{ + struct ssam_platform_profile_device *tpd; + enum ssam_tmp_profile tp; + int status; + + tpd = dev_get_drvdata(dev); + + status = ssam_tmp_profile_get(tpd->sdev, &tp); + if (status) + return status; + + status = convert_ssam_tmp_to_profile(tpd->sdev, tp); + if (status < 0) + return status; + + *profile = status; + return 0; +} + +static int ssam_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct ssam_platform_profile_device *tpd; + int tp; + + tpd = dev_get_drvdata(dev); + + tp = convert_profile_to_ssam_tmp(tpd->sdev, profile); + if (tp < 0) + return tp; + + tp = ssam_tmp_profile_set(tpd->sdev, tp); + if (tp < 0) + return 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 ssam_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops ssam_platform_profile_ops = { + .probe = ssam_platform_profile_probe, + .profile_get = ssam_platform_profile_get, + .profile_set = ssam_platform_profile_set, +}; + +static int surface_platform_profile_probe(struct ssam_device *sdev) +{ + struct ssam_platform_profile_device *tpd; + + tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); + if (!tpd) + return -ENOMEM; + + tpd->sdev = sdev; + ssam_device_set_drvdata(sdev, tpd); + + tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan"); + + tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile", + tpd, &ssam_platform_profile_ops); + + return PTR_ERR_OR_ZERO(tpd->ppdev); +} + +static const struct ssam_device_id ssam_platform_profile_match[] = { + { SSAM_SDEV(TMP, SAM, 0x00, 0x01) }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); + +static struct ssam_device_driver surface_platform_profile = { + .probe = surface_platform_profile_probe, + .match_table = ssam_platform_profile_match, + .driver = { + .name = "surface_platform_profile", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_platform_profile); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); |
