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