1 // SPDX-License-Identifier: GPL-2.0+
3 * Surface Platform Profile / Performance Mode driver for Surface System
4 * Aggregator Module (thermal and fan subsystem).
9 #include <linux/unaligned.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/platform_profile.h>
13 #include <linux/types.h>
15 #include <linux/surface_aggregator/device.h>
17 // Enum for the platform performance profile sent to the TMP module.
18 enum ssam_tmp_profile {
19 SSAM_TMP_PROFILE_NORMAL = 1,
20 SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
21 SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
22 SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
25 // Enum for the fan profile sent to the FAN module. This fan profile is
26 // only sent to the EC if the 'has_fan' property is set. The integers are
27 // not a typo, they differ from the performance profile indices.
28 enum ssam_fan_profile {
29 SSAM_FAN_PROFILE_NORMAL = 2,
30 SSAM_FAN_PROFILE_BATTERY_SAVER = 1,
31 SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
32 SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4,
35 struct ssam_tmp_profile_info {
41 struct ssam_platform_profile_device {
42 struct ssam_device *sdev;
47 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
48 .target_category = SSAM_SSH_TC_TMP,
52 SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
53 .target_category = SSAM_SSH_TC_TMP,
57 SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, {
58 .target_category = SSAM_SSH_TC_FAN,
59 .target_id = SSAM_SSH_TID_SAM,
64 static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
66 struct ssam_tmp_profile_info info;
69 status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
73 *p = le32_to_cpu(info.profile);
77 static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
79 const __le32 profile_le = cpu_to_le32(p);
81 return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
84 static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
88 return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
91 static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
94 case SSAM_TMP_PROFILE_NORMAL:
95 return PLATFORM_PROFILE_BALANCED;
97 case SSAM_TMP_PROFILE_BATTERY_SAVER:
98 return PLATFORM_PROFILE_LOW_POWER;
100 case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
101 return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
103 case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
104 return PLATFORM_PROFILE_PERFORMANCE;
107 dev_err(&sdev->dev, "invalid performance profile: %d", p);
113 static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
116 case PLATFORM_PROFILE_LOW_POWER:
117 return SSAM_TMP_PROFILE_BATTERY_SAVER;
119 case PLATFORM_PROFILE_BALANCED:
120 return SSAM_TMP_PROFILE_NORMAL;
122 case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
123 return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
125 case PLATFORM_PROFILE_PERFORMANCE:
126 return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
129 /* This should have already been caught by platform_profile_store(). */
130 WARN(true, "unsupported platform profile");
135 static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
138 case PLATFORM_PROFILE_LOW_POWER:
139 return SSAM_FAN_PROFILE_BATTERY_SAVER;
141 case PLATFORM_PROFILE_BALANCED:
142 return SSAM_FAN_PROFILE_NORMAL;
144 case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
145 return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
147 case PLATFORM_PROFILE_PERFORMANCE:
148 return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
151 /* This should have already been caught by platform_profile_store(). */
152 WARN(true, "unsupported platform profile");
157 static int ssam_platform_profile_get(struct device *dev,
158 enum platform_profile_option *profile)
160 struct ssam_platform_profile_device *tpd;
161 enum ssam_tmp_profile tp;
164 tpd = dev_get_drvdata(dev);
166 status = ssam_tmp_profile_get(tpd->sdev, &tp);
170 status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
178 static int ssam_platform_profile_set(struct device *dev,
179 enum platform_profile_option profile)
181 struct ssam_platform_profile_device *tpd;
184 tpd = dev_get_drvdata(dev);
186 tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
190 tp = ssam_tmp_profile_set(tpd->sdev, tp);
195 tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
198 tp = ssam_fan_profile_set(tpd->sdev, tp);
204 static int ssam_platform_profile_probe(void *drvdata, unsigned long *choices)
206 set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
207 set_bit(PLATFORM_PROFILE_BALANCED, choices);
208 set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
209 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
214 static const struct platform_profile_ops ssam_platform_profile_ops = {
215 .probe = ssam_platform_profile_probe,
216 .profile_get = ssam_platform_profile_get,
217 .profile_set = ssam_platform_profile_set,
220 static int surface_platform_profile_probe(struct ssam_device *sdev)
222 struct ssam_platform_profile_device *tpd;
224 tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
229 ssam_device_set_drvdata(sdev, tpd);
231 tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
233 tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile",
234 tpd, &ssam_platform_profile_ops);
236 return PTR_ERR_OR_ZERO(tpd->ppdev);
239 static const struct ssam_device_id ssam_platform_profile_match[] = {
240 { SSAM_SDEV(TMP, SAM, 0x00, 0x01) },
243 MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
245 static struct ssam_device_driver surface_platform_profile = {
246 .probe = surface_platform_profile_probe,
247 .match_table = ssam_platform_profile_match,
249 .name = "surface_platform_profile",
250 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
253 module_ssam_device_driver(surface_platform_profile);
256 MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
257 MODULE_LICENSE("GPL");