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