]> Git Repo - linux.git/blob - drivers/platform/surface/surface_platform_profile.c
Merge tag 'xtensa-20250126' of https://github.com/jcmvbkbc/linux-xtensa
[linux.git] / drivers / platform / surface / surface_platform_profile.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Surface Platform Profile / Performance Mode driver for Surface System
4  * Aggregator Module (thermal and fan subsystem).
5  *
6  * Copyright (C) 2021-2022 Maximilian Luz <[email protected]>
7  */
8
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>
14
15 #include <linux/surface_aggregator/device.h>
16
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,
23 };
24
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,
33 };
34
35 struct ssam_tmp_profile_info {
36         __le32 profile;
37         __le16 unknown1;
38         __le16 unknown2;
39 } __packed;
40
41 struct ssam_platform_profile_device {
42         struct ssam_device *sdev;
43         struct device *ppdev;
44         bool has_fan;
45 };
46
47 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
48         .target_category = SSAM_SSH_TC_TMP,
49         .command_id      = 0x02,
50 });
51
52 SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
53         .target_category = SSAM_SSH_TC_TMP,
54         .command_id      = 0x03,
55 });
56
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,
60         .command_id = 0x0e,
61         .instance_id = 0x01,
62 });
63
64 static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
65 {
66         struct ssam_tmp_profile_info info;
67         int status;
68
69         status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
70         if (status < 0)
71                 return status;
72
73         *p = le32_to_cpu(info.profile);
74         return 0;
75 }
76
77 static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
78 {
79         const __le32 profile_le = cpu_to_le32(p);
80
81         return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
82 }
83
84 static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
85 {
86         const u8 profile = p;
87
88         return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
89 }
90
91 static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
92 {
93         switch (p) {
94         case SSAM_TMP_PROFILE_NORMAL:
95                 return PLATFORM_PROFILE_BALANCED;
96
97         case SSAM_TMP_PROFILE_BATTERY_SAVER:
98                 return PLATFORM_PROFILE_LOW_POWER;
99
100         case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
101                 return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
102
103         case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
104                 return PLATFORM_PROFILE_PERFORMANCE;
105
106         default:
107                 dev_err(&sdev->dev, "invalid performance profile: %d", p);
108                 return -EINVAL;
109         }
110 }
111
112
113 static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
114 {
115         switch (p) {
116         case PLATFORM_PROFILE_LOW_POWER:
117                 return SSAM_TMP_PROFILE_BATTERY_SAVER;
118
119         case PLATFORM_PROFILE_BALANCED:
120                 return SSAM_TMP_PROFILE_NORMAL;
121
122         case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
123                 return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
124
125         case PLATFORM_PROFILE_PERFORMANCE:
126                 return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
127
128         default:
129                 /* This should have already been caught by platform_profile_store(). */
130                 WARN(true, "unsupported platform profile");
131                 return -EOPNOTSUPP;
132         }
133 }
134
135 static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
136 {
137         switch (p) {
138         case PLATFORM_PROFILE_LOW_POWER:
139                 return SSAM_FAN_PROFILE_BATTERY_SAVER;
140
141         case PLATFORM_PROFILE_BALANCED:
142                 return SSAM_FAN_PROFILE_NORMAL;
143
144         case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
145                 return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
146
147         case PLATFORM_PROFILE_PERFORMANCE:
148                 return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
149
150         default:
151                 /* This should have already been caught by platform_profile_store(). */
152                 WARN(true, "unsupported platform profile");
153                 return -EOPNOTSUPP;
154         }
155 }
156
157 static int ssam_platform_profile_get(struct device *dev,
158                                      enum platform_profile_option *profile)
159 {
160         struct ssam_platform_profile_device *tpd;
161         enum ssam_tmp_profile tp;
162         int status;
163
164         tpd = dev_get_drvdata(dev);
165
166         status = ssam_tmp_profile_get(tpd->sdev, &tp);
167         if (status)
168                 return status;
169
170         status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
171         if (status < 0)
172                 return status;
173
174         *profile = status;
175         return 0;
176 }
177
178 static int ssam_platform_profile_set(struct device *dev,
179                                      enum platform_profile_option profile)
180 {
181         struct ssam_platform_profile_device *tpd;
182         int tp;
183
184         tpd = dev_get_drvdata(dev);
185
186         tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
187         if (tp < 0)
188                 return tp;
189
190         tp = ssam_tmp_profile_set(tpd->sdev, tp);
191         if (tp < 0)
192                 return tp;
193
194         if (tpd->has_fan) {
195                 tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
196                 if (tp < 0)
197                         return tp;
198                 tp = ssam_fan_profile_set(tpd->sdev, tp);
199         }
200
201         return tp;
202 }
203
204 static int ssam_platform_profile_probe(void *drvdata, unsigned long *choices)
205 {
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);
210
211         return 0;
212 }
213
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,
218 };
219
220 static int surface_platform_profile_probe(struct ssam_device *sdev)
221 {
222         struct ssam_platform_profile_device *tpd;
223
224         tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
225         if (!tpd)
226                 return -ENOMEM;
227
228         tpd->sdev = sdev;
229         ssam_device_set_drvdata(sdev, tpd);
230
231         tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
232
233         tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile",
234                                                     tpd, &ssam_platform_profile_ops);
235
236         return PTR_ERR_OR_ZERO(tpd->ppdev);
237 }
238
239 static const struct ssam_device_id ssam_platform_profile_match[] = {
240         { SSAM_SDEV(TMP, SAM, 0x00, 0x01) },
241         { },
242 };
243 MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
244
245 static struct ssam_device_driver surface_platform_profile = {
246         .probe = surface_platform_profile_probe,
247         .match_table = ssam_platform_profile_match,
248         .driver = {
249                 .name = "surface_platform_profile",
250                 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
251         },
252 };
253 module_ssam_device_driver(surface_platform_profile);
254
255 MODULE_AUTHOR("Maximilian Luz <[email protected]>");
256 MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
257 MODULE_LICENSE("GPL");
This page took 0.042634 seconds and 4 git commands to generate.