xref: /linux/drivers/platform/x86/dell/dell-pc.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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