1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Surface Platform Profile / Performance Mode driver for Surface System |
4 | * Aggregator Module (thermal subsystem). |
5 | * |
6 | * Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com> |
7 | */ |
8 | |
9 | #include <asm/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 ssam_tmp_profile { |
18 | SSAM_TMP_PROFILE_NORMAL = 1, |
19 | SSAM_TMP_PROFILE_BATTERY_SAVER = 2, |
20 | SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, |
21 | SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, |
22 | }; |
23 | |
24 | struct ssam_tmp_profile_info { |
25 | __le32 profile; |
26 | __le16 unknown1; |
27 | __le16 unknown2; |
28 | } __packed; |
29 | |
30 | struct ssam_tmp_profile_device { |
31 | struct ssam_device *sdev; |
32 | struct platform_profile_handler handler; |
33 | }; |
34 | |
35 | SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { |
36 | .target_category = SSAM_SSH_TC_TMP, |
37 | .command_id = 0x02, |
38 | }); |
39 | |
40 | SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { |
41 | .target_category = SSAM_SSH_TC_TMP, |
42 | .command_id = 0x03, |
43 | }); |
44 | |
45 | static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) |
46 | { |
47 | struct ssam_tmp_profile_info info; |
48 | int status; |
49 | |
50 | status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); |
51 | if (status < 0) |
52 | return status; |
53 | |
54 | *p = le32_to_cpu(info.profile); |
55 | return 0; |
56 | } |
57 | |
58 | static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) |
59 | { |
60 | __le32 profile_le = cpu_to_le32(p); |
61 | |
62 | return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); |
63 | } |
64 | |
65 | static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) |
66 | { |
67 | switch (p) { |
68 | case SSAM_TMP_PROFILE_NORMAL: |
69 | return PLATFORM_PROFILE_BALANCED; |
70 | |
71 | case SSAM_TMP_PROFILE_BATTERY_SAVER: |
72 | return PLATFORM_PROFILE_LOW_POWER; |
73 | |
74 | case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: |
75 | return PLATFORM_PROFILE_BALANCED_PERFORMANCE; |
76 | |
77 | case SSAM_TMP_PROFILE_BEST_PERFORMANCE: |
78 | return PLATFORM_PROFILE_PERFORMANCE; |
79 | |
80 | default: |
81 | dev_err(&sdev->dev, "invalid performance profile: %d" , p); |
82 | return -EINVAL; |
83 | } |
84 | } |
85 | |
86 | static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) |
87 | { |
88 | switch (p) { |
89 | case PLATFORM_PROFILE_LOW_POWER: |
90 | return SSAM_TMP_PROFILE_BATTERY_SAVER; |
91 | |
92 | case PLATFORM_PROFILE_BALANCED: |
93 | return SSAM_TMP_PROFILE_NORMAL; |
94 | |
95 | case PLATFORM_PROFILE_BALANCED_PERFORMANCE: |
96 | return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; |
97 | |
98 | case PLATFORM_PROFILE_PERFORMANCE: |
99 | return SSAM_TMP_PROFILE_BEST_PERFORMANCE; |
100 | |
101 | default: |
102 | /* This should have already been caught by platform_profile_store(). */ |
103 | WARN(true, "unsupported platform profile" ); |
104 | return -EOPNOTSUPP; |
105 | } |
106 | } |
107 | |
108 | static int ssam_platform_profile_get(struct platform_profile_handler *pprof, |
109 | enum platform_profile_option *profile) |
110 | { |
111 | struct ssam_tmp_profile_device *tpd; |
112 | enum ssam_tmp_profile tp; |
113 | int status; |
114 | |
115 | tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); |
116 | |
117 | status = ssam_tmp_profile_get(sdev: tpd->sdev, p: &tp); |
118 | if (status) |
119 | return status; |
120 | |
121 | status = convert_ssam_to_profile(sdev: tpd->sdev, p: tp); |
122 | if (status < 0) |
123 | return status; |
124 | |
125 | *profile = status; |
126 | return 0; |
127 | } |
128 | |
129 | static int ssam_platform_profile_set(struct platform_profile_handler *pprof, |
130 | enum platform_profile_option profile) |
131 | { |
132 | struct ssam_tmp_profile_device *tpd; |
133 | int tp; |
134 | |
135 | tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); |
136 | |
137 | tp = convert_profile_to_ssam(sdev: tpd->sdev, p: profile); |
138 | if (tp < 0) |
139 | return tp; |
140 | |
141 | return ssam_tmp_profile_set(sdev: tpd->sdev, p: tp); |
142 | } |
143 | |
144 | static int surface_platform_profile_probe(struct ssam_device *sdev) |
145 | { |
146 | struct ssam_tmp_profile_device *tpd; |
147 | |
148 | tpd = devm_kzalloc(dev: &sdev->dev, size: sizeof(*tpd), GFP_KERNEL); |
149 | if (!tpd) |
150 | return -ENOMEM; |
151 | |
152 | tpd->sdev = sdev; |
153 | |
154 | tpd->handler.profile_get = ssam_platform_profile_get; |
155 | tpd->handler.profile_set = ssam_platform_profile_set; |
156 | |
157 | set_bit(nr: PLATFORM_PROFILE_LOW_POWER, addr: tpd->handler.choices); |
158 | set_bit(nr: PLATFORM_PROFILE_BALANCED, addr: tpd->handler.choices); |
159 | set_bit(nr: PLATFORM_PROFILE_BALANCED_PERFORMANCE, addr: tpd->handler.choices); |
160 | set_bit(nr: PLATFORM_PROFILE_PERFORMANCE, addr: tpd->handler.choices); |
161 | |
162 | return platform_profile_register(pprof: &tpd->handler); |
163 | } |
164 | |
165 | static void surface_platform_profile_remove(struct ssam_device *sdev) |
166 | { |
167 | platform_profile_remove(); |
168 | } |
169 | |
170 | static const struct ssam_device_id ssam_platform_profile_match[] = { |
171 | { SSAM_SDEV(TMP, SAM, 0x00, 0x01) }, |
172 | { }, |
173 | }; |
174 | MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); |
175 | |
176 | static struct ssam_device_driver surface_platform_profile = { |
177 | .probe = surface_platform_profile_probe, |
178 | .remove = surface_platform_profile_remove, |
179 | .match_table = ssam_platform_profile_match, |
180 | .driver = { |
181 | .name = "surface_platform_profile" , |
182 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
183 | }, |
184 | }; |
185 | module_ssam_device_driver(surface_platform_profile); |
186 | |
187 | MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>" ); |
188 | MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module" ); |
189 | MODULE_LICENSE("GPL" ); |
190 | |