1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Driver for Dell laptop extras
4 *
5 * Copyright (c) Lyndon Sanche <[email protected]>
6 *
7 * Based on documentation in the libsmbios package:
8 * Copyright (C) 2005-2014 Dell Inc.
9 */
10
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
13 #include <linux/bitfield.h>
14 #include <linux/bits.h>
15 #include <linux/dmi.h>
16 #include <linux/err.h>
17 #include <linux/init.h>
18 #include <linux/kernel.h>
19 #include <linux/module.h>
20 #include <linux/platform_profile.h>
21 #include <linux/platform_device.h>
22 #include <linux/slab.h>
23
24 #include "dell-smbios.h"
25
26 static struct platform_device *platform_device;
27 static int supported_modes;
28
29 static const struct dmi_system_id dell_device_table[] __initconst = {
30 {
31 .ident = "Dell Inc.",
32 .matches = {
33 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
34 },
35 },
36 {
37 .ident = "Dell Computer Corporation",
38 .matches = {
39 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
40 },
41 },
42 { }
43 };
44 MODULE_DEVICE_TABLE(dmi, dell_device_table);
45
46 /* Derived from smbios-thermal-ctl
47 *
48 * cbClass 17
49 * cbSelect 19
50 * User Selectable Thermal Tables(USTT)
51 * cbArg1 determines the function to be performed
52 * cbArg1 0x0 = Get Thermal Information
53 * cbRES1 Standard return codes (0, -1, -2)
54 * cbRES2, byte 0 Bitmap of supported thermal modes. A mode is supported if
55 * its bit is set to 1
56 * Bit 0 Balanced
57 * Bit 1 Cool Bottom
58 * Bit 2 Quiet
59 * Bit 3 Performance
60 * cbRES2, byte 1 Bitmap of supported Active Acoustic Controller (AAC) modes.
61 * Each mode corresponds to the supported thermal modes in
62 * byte 0. A mode is supported if its bit is set to 1.
63 * Bit 0 AAC (Balanced)
64 * Bit 1 AAC (Cool Bottom
65 * Bit 2 AAC (Quiet)
66 * Bit 3 AAC (Performance)
67 * cbRes3, byte 0 Current Thermal Mode
68 * Bit 0 Balanced
69 * Bit 1 Cool Bottom
70 * Bit 2 Quiet
71 * Bit 3 Performanc
72 * cbRes3, byte 1 AAC Configuration type
73 * 0 Global (AAC enable/disable applies to all supported USTT modes)
74 * 1 USTT mode specific
75 * cbRes3, byte 2 Current Active Acoustic Controller (AAC) Mode
76 * If AAC Configuration Type is Global,
77 * 0 AAC mode disabled
78 * 1 AAC mode enabled
79 * If AAC Configuration Type is USTT mode specific (multiple bits may be set),
80 * Bit 0 AAC (Balanced)
81 * Bit 1 AAC (Cool Bottom
82 * Bit 2 AAC (Quiet)
83 * Bit 3 AAC (Performance)
84 * cbRes3, byte 3 Current Fan Failure Mode
85 * Bit 0 Minimal Fan Failure (at least one fan has failed, one fan working)
86 * Bit 1 Catastrophic Fan Failure (all fans have failed)
87 *
88 * cbArg1 0x1 (Set Thermal Information), both desired thermal mode and
89 * desired AAC mode shall be applied
90 * cbArg2, byte 0 Desired Thermal Mode to set
91 * (only one bit may be set for this parameter)
92 * Bit 0 Balanced
93 * Bit 1 Cool Bottom
94 * Bit 2 Quiet
95 * Bit 3 Performance
96 * cbArg2, byte 1 Desired Active Acoustic Controller (AAC) Mode to set
97 * If AAC Configuration Type is Global,
98 * 0 AAC mode disabled
99 * 1 AAC mode enabled
100 * If AAC Configuration Type is USTT mode specific
101 * (multiple bits may be set for this parameter),
102 * Bit 0 AAC (Balanced)
103 * Bit 1 AAC (Cool Bottom
104 * Bit 2 AAC (Quiet)
105 * Bit 3 AAC (Performance)
106 */
107
108 #define DELL_ACC_GET_FIELD GENMASK(19, 16)
109 #define DELL_ACC_SET_FIELD GENMASK(11, 8)
110 #define DELL_THERMAL_SUPPORTED GENMASK(3, 0)
111
112 enum thermal_mode_bits {
113 DELL_BALANCED = BIT(0),
114 DELL_COOL_BOTTOM = BIT(1),
115 DELL_QUIET = BIT(2),
116 DELL_PERFORMANCE = BIT(3),
117 };
118
thermal_get_mode(void)119 static int thermal_get_mode(void)
120 {
121 struct calling_interface_buffer buffer;
122 int state;
123 int ret;
124
125 dell_fill_request(&buffer, 0x0, 0, 0, 0);
126 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
127 if (ret)
128 return ret;
129 state = buffer.output[2];
130 if (state & DELL_BALANCED)
131 return DELL_BALANCED;
132 else if (state & DELL_COOL_BOTTOM)
133 return DELL_COOL_BOTTOM;
134 else if (state & DELL_QUIET)
135 return DELL_QUIET;
136 else if (state & DELL_PERFORMANCE)
137 return DELL_PERFORMANCE;
138 else
139 return -ENXIO;
140 }
141
thermal_get_supported_modes(int * supported_bits)142 static int thermal_get_supported_modes(int *supported_bits)
143 {
144 struct calling_interface_buffer buffer;
145 int ret;
146
147 dell_fill_request(&buffer, 0x0, 0, 0, 0);
148 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
149 /* Thermal function not supported */
150 if (ret == -ENXIO) {
151 *supported_bits = 0;
152 return 0;
153 }
154 if (ret)
155 return ret;
156 *supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]);
157 return 0;
158 }
159
thermal_get_acc_mode(int * acc_mode)160 static int thermal_get_acc_mode(int *acc_mode)
161 {
162 struct calling_interface_buffer buffer;
163 int ret;
164
165 dell_fill_request(&buffer, 0x0, 0, 0, 0);
166 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
167 if (ret)
168 return ret;
169 *acc_mode = FIELD_GET(DELL_ACC_GET_FIELD, buffer.output[3]);
170 return 0;
171 }
172
thermal_set_mode(enum thermal_mode_bits state)173 static int thermal_set_mode(enum thermal_mode_bits state)
174 {
175 struct calling_interface_buffer buffer;
176 int ret;
177 int acc_mode;
178
179 ret = thermal_get_acc_mode(&acc_mode);
180 if (ret)
181 return ret;
182
183 dell_fill_request(&buffer, 0x1, FIELD_PREP(DELL_ACC_SET_FIELD, acc_mode) | state, 0, 0);
184 return dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
185 }
186
thermal_platform_profile_set(struct device * dev,enum platform_profile_option profile)187 static int thermal_platform_profile_set(struct device *dev,
188 enum platform_profile_option profile)
189 {
190 switch (profile) {
191 case PLATFORM_PROFILE_BALANCED:
192 return thermal_set_mode(DELL_BALANCED);
193 case PLATFORM_PROFILE_PERFORMANCE:
194 return thermal_set_mode(DELL_PERFORMANCE);
195 case PLATFORM_PROFILE_QUIET:
196 return thermal_set_mode(DELL_QUIET);
197 case PLATFORM_PROFILE_COOL:
198 return thermal_set_mode(DELL_COOL_BOTTOM);
199 default:
200 return -EOPNOTSUPP;
201 }
202 }
203
thermal_platform_profile_get(struct device * dev,enum platform_profile_option * profile)204 static int thermal_platform_profile_get(struct device *dev,
205 enum platform_profile_option *profile)
206 {
207 int ret;
208
209 ret = thermal_get_mode();
210 if (ret < 0)
211 return ret;
212
213 switch (ret) {
214 case DELL_BALANCED:
215 *profile = PLATFORM_PROFILE_BALANCED;
216 break;
217 case DELL_PERFORMANCE:
218 *profile = PLATFORM_PROFILE_PERFORMANCE;
219 break;
220 case DELL_COOL_BOTTOM:
221 *profile = PLATFORM_PROFILE_COOL;
222 break;
223 case DELL_QUIET:
224 *profile = PLATFORM_PROFILE_QUIET;
225 break;
226 default:
227 return -EINVAL;
228 }
229
230 return 0;
231 }
232
thermal_platform_profile_probe(void * drvdata,unsigned long * choices)233 static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices)
234 {
235 if (supported_modes & DELL_QUIET)
236 set_bit(PLATFORM_PROFILE_QUIET, choices);
237 if (supported_modes & DELL_COOL_BOTTOM)
238 set_bit(PLATFORM_PROFILE_COOL, choices);
239 if (supported_modes & DELL_BALANCED)
240 set_bit(PLATFORM_PROFILE_BALANCED, choices);
241 if (supported_modes & DELL_PERFORMANCE)
242 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
243
244 return 0;
245 }
246
247 static const struct platform_profile_ops dell_pc_platform_profile_ops = {
248 .probe = thermal_platform_profile_probe,
249 .profile_get = thermal_platform_profile_get,
250 .profile_set = thermal_platform_profile_set,
251 };
252
thermal_init(void)253 static int thermal_init(void)
254 {
255 struct device *ppdev;
256 int ret;
257
258 /* If thermal commands are not supported, exit without error */
259 if (!dell_smbios_class_is_supported(CLASS_INFO))
260 return 0;
261
262 /* If thermal modes are not supported, exit without error */
263 ret = thermal_get_supported_modes(&supported_modes);
264 if (ret < 0)
265 return ret;
266 if (!supported_modes)
267 return 0;
268
269 platform_device = platform_device_register_simple("dell-pc", PLATFORM_DEVID_NONE, NULL, 0);
270 if (IS_ERR(platform_device))
271 return PTR_ERR(platform_device);
272
273 ppdev = devm_platform_profile_register(&platform_device->dev, "dell-pc",
274 NULL, &dell_pc_platform_profile_ops);
275 if (IS_ERR(ppdev)) {
276 ret = PTR_ERR(ppdev);
277 goto cleanup_platform_device;
278 }
279
280 return 0;
281
282 cleanup_platform_device:
283 platform_device_unregister(platform_device);
284
285 return ret;
286 }
287
thermal_cleanup(void)288 static void thermal_cleanup(void)
289 {
290 platform_device_unregister(platform_device);
291 }
292
dell_init(void)293 static int __init dell_init(void)
294 {
295 int ret;
296
297 if (!dmi_check_system(dell_device_table))
298 return -ENODEV;
299
300 /* Do not fail module if thermal modes not supported, just skip */
301 ret = thermal_init();
302 if (ret)
303 goto fail_thermal;
304
305 return 0;
306
307 fail_thermal:
308 thermal_cleanup();
309 return ret;
310 }
311
dell_exit(void)312 static void __exit dell_exit(void)
313 {
314 thermal_cleanup();
315 }
316
317 module_init(dell_init);
318 module_exit(dell_exit);
319
320 MODULE_AUTHOR("Lyndon Sanche <[email protected]>");
321 MODULE_DESCRIPTION("Dell PC driver");
322 MODULE_LICENSE("GPL");
323