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