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