1b78b4982SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2b78b4982SMaximilian Luz /*
3b78b4982SMaximilian Luz * Surface Platform Profile / Performance Mode driver for Surface System
43427c443SIvor Wanders * Aggregator Module (thermal and fan subsystem).
5b78b4982SMaximilian Luz *
6221756e6SMaximilian Luz * Copyright (C) 2021-2022 Maximilian Luz <[email protected]>
7b78b4982SMaximilian Luz */
8b78b4982SMaximilian Luz
95f60d5f6SAl Viro #include <linux/unaligned.h>
10b78b4982SMaximilian Luz #include <linux/kernel.h>
11b78b4982SMaximilian Luz #include <linux/module.h>
12b78b4982SMaximilian Luz #include <linux/platform_profile.h>
13b78b4982SMaximilian Luz #include <linux/types.h>
14b78b4982SMaximilian Luz
15b78b4982SMaximilian Luz #include <linux/surface_aggregator/device.h>
16b78b4982SMaximilian Luz
173427c443SIvor Wanders // Enum for the platform performance profile sent to the TMP module.
18b78b4982SMaximilian Luz enum ssam_tmp_profile {
19b78b4982SMaximilian Luz SSAM_TMP_PROFILE_NORMAL = 1,
20b78b4982SMaximilian Luz SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
21b78b4982SMaximilian Luz SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
22b78b4982SMaximilian Luz SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
23b78b4982SMaximilian Luz };
24b78b4982SMaximilian Luz
253427c443SIvor Wanders // Enum for the fan profile sent to the FAN module. This fan profile is
263427c443SIvor Wanders // only sent to the EC if the 'has_fan' property is set. The integers are
273427c443SIvor Wanders // not a typo, they differ from the performance profile indices.
283427c443SIvor Wanders enum ssam_fan_profile {
293427c443SIvor Wanders SSAM_FAN_PROFILE_NORMAL = 2,
303427c443SIvor Wanders SSAM_FAN_PROFILE_BATTERY_SAVER = 1,
313427c443SIvor Wanders SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
323427c443SIvor Wanders SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4,
333427c443SIvor Wanders };
343427c443SIvor Wanders
35b78b4982SMaximilian Luz struct ssam_tmp_profile_info {
36b78b4982SMaximilian Luz __le32 profile;
37b78b4982SMaximilian Luz __le16 unknown1;
38b78b4982SMaximilian Luz __le16 unknown2;
39b78b4982SMaximilian Luz } __packed;
40b78b4982SMaximilian Luz
413427c443SIvor Wanders struct ssam_platform_profile_device {
42b78b4982SMaximilian Luz struct ssam_device *sdev;
43*07f531b3SKurt Borja struct device *ppdev;
443427c443SIvor Wanders bool has_fan;
45b78b4982SMaximilian Luz };
46b78b4982SMaximilian Luz
4703ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
48b78b4982SMaximilian Luz .target_category = SSAM_SSH_TC_TMP,
49b78b4982SMaximilian Luz .command_id = 0x02,
50b78b4982SMaximilian Luz });
51b78b4982SMaximilian Luz
5203ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
53b78b4982SMaximilian Luz .target_category = SSAM_SSH_TC_TMP,
54b78b4982SMaximilian Luz .command_id = 0x03,
55b78b4982SMaximilian Luz });
56b78b4982SMaximilian Luz
573427c443SIvor Wanders SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, {
583427c443SIvor Wanders .target_category = SSAM_SSH_TC_FAN,
593427c443SIvor Wanders .target_id = SSAM_SSH_TID_SAM,
603427c443SIvor Wanders .command_id = 0x0e,
613427c443SIvor Wanders .instance_id = 0x01,
623427c443SIvor Wanders });
633427c443SIvor Wanders
ssam_tmp_profile_get(struct ssam_device * sdev,enum ssam_tmp_profile * p)64b78b4982SMaximilian Luz static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
65b78b4982SMaximilian Luz {
66b78b4982SMaximilian Luz struct ssam_tmp_profile_info info;
67b78b4982SMaximilian Luz int status;
68b78b4982SMaximilian Luz
69b78b4982SMaximilian Luz status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
70b78b4982SMaximilian Luz if (status < 0)
71b78b4982SMaximilian Luz return status;
72b78b4982SMaximilian Luz
73b78b4982SMaximilian Luz *p = le32_to_cpu(info.profile);
74b78b4982SMaximilian Luz return 0;
75b78b4982SMaximilian Luz }
76b78b4982SMaximilian Luz
ssam_tmp_profile_set(struct ssam_device * sdev,enum ssam_tmp_profile p)77b78b4982SMaximilian Luz static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
78b78b4982SMaximilian Luz {
793427c443SIvor Wanders const __le32 profile_le = cpu_to_le32(p);
80b78b4982SMaximilian Luz
81b78b4982SMaximilian Luz return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
82b78b4982SMaximilian Luz }
83b78b4982SMaximilian Luz
ssam_fan_profile_set(struct ssam_device * sdev,enum ssam_fan_profile p)843427c443SIvor Wanders static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
853427c443SIvor Wanders {
863427c443SIvor Wanders const u8 profile = p;
873427c443SIvor Wanders
883427c443SIvor Wanders return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
893427c443SIvor Wanders }
903427c443SIvor Wanders
convert_ssam_tmp_to_profile(struct ssam_device * sdev,enum ssam_tmp_profile p)913427c443SIvor Wanders static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
92b78b4982SMaximilian Luz {
93b78b4982SMaximilian Luz switch (p) {
94b78b4982SMaximilian Luz case SSAM_TMP_PROFILE_NORMAL:
95b78b4982SMaximilian Luz return PLATFORM_PROFILE_BALANCED;
96b78b4982SMaximilian Luz
97b78b4982SMaximilian Luz case SSAM_TMP_PROFILE_BATTERY_SAVER:
98b78b4982SMaximilian Luz return PLATFORM_PROFILE_LOW_POWER;
99b78b4982SMaximilian Luz
100b78b4982SMaximilian Luz case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
101b78b4982SMaximilian Luz return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
102b78b4982SMaximilian Luz
103b78b4982SMaximilian Luz case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
104b78b4982SMaximilian Luz return PLATFORM_PROFILE_PERFORMANCE;
105b78b4982SMaximilian Luz
106b78b4982SMaximilian Luz default:
107b78b4982SMaximilian Luz dev_err(&sdev->dev, "invalid performance profile: %d", p);
108b78b4982SMaximilian Luz return -EINVAL;
109b78b4982SMaximilian Luz }
110b78b4982SMaximilian Luz }
111b78b4982SMaximilian Luz
1123427c443SIvor Wanders
convert_profile_to_ssam_tmp(struct ssam_device * sdev,enum platform_profile_option p)1133427c443SIvor Wanders static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
114b78b4982SMaximilian Luz {
115b78b4982SMaximilian Luz switch (p) {
116b78b4982SMaximilian Luz case PLATFORM_PROFILE_LOW_POWER:
117b78b4982SMaximilian Luz return SSAM_TMP_PROFILE_BATTERY_SAVER;
118b78b4982SMaximilian Luz
119b78b4982SMaximilian Luz case PLATFORM_PROFILE_BALANCED:
120b78b4982SMaximilian Luz return SSAM_TMP_PROFILE_NORMAL;
121b78b4982SMaximilian Luz
122b78b4982SMaximilian Luz case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
123b78b4982SMaximilian Luz return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
124b78b4982SMaximilian Luz
125b78b4982SMaximilian Luz case PLATFORM_PROFILE_PERFORMANCE:
126b78b4982SMaximilian Luz return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
127b78b4982SMaximilian Luz
128b78b4982SMaximilian Luz default:
129b78b4982SMaximilian Luz /* This should have already been caught by platform_profile_store(). */
130b78b4982SMaximilian Luz WARN(true, "unsupported platform profile");
131b78b4982SMaximilian Luz return -EOPNOTSUPP;
132b78b4982SMaximilian Luz }
133b78b4982SMaximilian Luz }
134b78b4982SMaximilian Luz
convert_profile_to_ssam_fan(struct ssam_device * sdev,enum platform_profile_option p)1353427c443SIvor Wanders static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
1363427c443SIvor Wanders {
1373427c443SIvor Wanders switch (p) {
1383427c443SIvor Wanders case PLATFORM_PROFILE_LOW_POWER:
1393427c443SIvor Wanders return SSAM_FAN_PROFILE_BATTERY_SAVER;
1403427c443SIvor Wanders
1413427c443SIvor Wanders case PLATFORM_PROFILE_BALANCED:
1423427c443SIvor Wanders return SSAM_FAN_PROFILE_NORMAL;
1433427c443SIvor Wanders
1443427c443SIvor Wanders case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
1453427c443SIvor Wanders return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
1463427c443SIvor Wanders
1473427c443SIvor Wanders case PLATFORM_PROFILE_PERFORMANCE:
1483427c443SIvor Wanders return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
1493427c443SIvor Wanders
1503427c443SIvor Wanders default:
1513427c443SIvor Wanders /* This should have already been caught by platform_profile_store(). */
1523427c443SIvor Wanders WARN(true, "unsupported platform profile");
1533427c443SIvor Wanders return -EOPNOTSUPP;
1543427c443SIvor Wanders }
1553427c443SIvor Wanders }
1563427c443SIvor Wanders
ssam_platform_profile_get(struct device * dev,enum platform_profile_option * profile)157cf3ea098SKurt Borja static int ssam_platform_profile_get(struct device *dev,
158b78b4982SMaximilian Luz enum platform_profile_option *profile)
159b78b4982SMaximilian Luz {
1603427c443SIvor Wanders struct ssam_platform_profile_device *tpd;
161b78b4982SMaximilian Luz enum ssam_tmp_profile tp;
162b78b4982SMaximilian Luz int status;
163b78b4982SMaximilian Luz
164cf3ea098SKurt Borja tpd = dev_get_drvdata(dev);
165b78b4982SMaximilian Luz
166b78b4982SMaximilian Luz status = ssam_tmp_profile_get(tpd->sdev, &tp);
167b78b4982SMaximilian Luz if (status)
168b78b4982SMaximilian Luz return status;
169b78b4982SMaximilian Luz
1703427c443SIvor Wanders status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
171b78b4982SMaximilian Luz if (status < 0)
172b78b4982SMaximilian Luz return status;
173b78b4982SMaximilian Luz
174b78b4982SMaximilian Luz *profile = status;
175b78b4982SMaximilian Luz return 0;
176b78b4982SMaximilian Luz }
177b78b4982SMaximilian Luz
ssam_platform_profile_set(struct device * dev,enum platform_profile_option profile)178cf3ea098SKurt Borja static int ssam_platform_profile_set(struct device *dev,
179b78b4982SMaximilian Luz enum platform_profile_option profile)
180b78b4982SMaximilian Luz {
1813427c443SIvor Wanders struct ssam_platform_profile_device *tpd;
182b78b4982SMaximilian Luz int tp;
183b78b4982SMaximilian Luz
184cf3ea098SKurt Borja tpd = dev_get_drvdata(dev);
185b78b4982SMaximilian Luz
1863427c443SIvor Wanders tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
187b78b4982SMaximilian Luz if (tp < 0)
188b78b4982SMaximilian Luz return tp;
189b78b4982SMaximilian Luz
1903427c443SIvor Wanders tp = ssam_tmp_profile_set(tpd->sdev, tp);
1913427c443SIvor Wanders if (tp < 0)
1923427c443SIvor Wanders return tp;
1933427c443SIvor Wanders
1943427c443SIvor Wanders if (tpd->has_fan) {
1953427c443SIvor Wanders tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
1963427c443SIvor Wanders if (tp < 0)
1973427c443SIvor Wanders return tp;
1983427c443SIvor Wanders tp = ssam_fan_profile_set(tpd->sdev, tp);
1993427c443SIvor Wanders }
2003427c443SIvor Wanders
2013427c443SIvor Wanders return tp;
202b78b4982SMaximilian Luz }
203b78b4982SMaximilian Luz
ssam_platform_profile_probe(void * drvdata,unsigned long * choices)20458d5629dSKurt Borja static int ssam_platform_profile_probe(void *drvdata, unsigned long *choices)
20558d5629dSKurt Borja {
20658d5629dSKurt Borja set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
20758d5629dSKurt Borja set_bit(PLATFORM_PROFILE_BALANCED, choices);
20858d5629dSKurt Borja set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
20958d5629dSKurt Borja set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
21058d5629dSKurt Borja
21158d5629dSKurt Borja return 0;
21258d5629dSKurt Borja }
21358d5629dSKurt Borja
214b5ca1a44SKurt Borja static const struct platform_profile_ops ssam_platform_profile_ops = {
21558d5629dSKurt Borja .probe = ssam_platform_profile_probe,
216b5ca1a44SKurt Borja .profile_get = ssam_platform_profile_get,
217b5ca1a44SKurt Borja .profile_set = ssam_platform_profile_set,
218b5ca1a44SKurt Borja };
219b5ca1a44SKurt Borja
surface_platform_profile_probe(struct ssam_device * sdev)220b78b4982SMaximilian Luz static int surface_platform_profile_probe(struct ssam_device *sdev)
221b78b4982SMaximilian Luz {
2223427c443SIvor Wanders struct ssam_platform_profile_device *tpd;
223b78b4982SMaximilian Luz
224b78b4982SMaximilian Luz tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
225b78b4982SMaximilian Luz if (!tpd)
226b78b4982SMaximilian Luz return -ENOMEM;
227b78b4982SMaximilian Luz
228b78b4982SMaximilian Luz tpd->sdev = sdev;
2299b3bb37bSMario Limonciello ssam_device_set_drvdata(sdev, tpd);
230b78b4982SMaximilian Luz
2313427c443SIvor Wanders tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
2323427c443SIvor Wanders
233*07f531b3SKurt Borja tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile",
234*07f531b3SKurt Borja tpd, &ssam_platform_profile_ops);
235*07f531b3SKurt Borja
236*07f531b3SKurt Borja return PTR_ERR_OR_ZERO(tpd->ppdev);
237b78b4982SMaximilian Luz }
238b78b4982SMaximilian Luz
239b78b4982SMaximilian Luz static const struct ssam_device_id ssam_platform_profile_match[] = {
24078abf1b5SMaximilian Luz { SSAM_SDEV(TMP, SAM, 0x00, 0x01) },
241b78b4982SMaximilian Luz { },
242b78b4982SMaximilian Luz };
243b78b4982SMaximilian Luz MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
244b78b4982SMaximilian Luz
245b78b4982SMaximilian Luz static struct ssam_device_driver surface_platform_profile = {
246b78b4982SMaximilian Luz .probe = surface_platform_profile_probe,
247b78b4982SMaximilian Luz .match_table = ssam_platform_profile_match,
248b78b4982SMaximilian Luz .driver = {
249b78b4982SMaximilian Luz .name = "surface_platform_profile",
250b78b4982SMaximilian Luz .probe_type = PROBE_PREFER_ASYNCHRONOUS,
251b78b4982SMaximilian Luz },
252b78b4982SMaximilian Luz };
253b78b4982SMaximilian Luz module_ssam_device_driver(surface_platform_profile);
254b78b4982SMaximilian Luz
255b78b4982SMaximilian Luz MODULE_AUTHOR("Maximilian Luz <[email protected]>");
256b78b4982SMaximilian Luz MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
257b78b4982SMaximilian Luz MODULE_LICENSE("GPL");
258