File Index
Files and directories
Sign in
FILE

dell-pc.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/dell/dell-pc.c C
  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
 27static struct faux_device *dell_pc_fdev;
 28static int supported_modes;
 29
 30static 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};
 45MODULE_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
113enum 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
120static 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
143static 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
156static 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
169static 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
183static 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
200static 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
229static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices)
230{
231	int current_mode;
232
233	if (supported_modes & DELL_QUIET)
234		__set_bit(PLATFORM_PROFILE_QUIET, choices);
235	if (supported_modes & DELL_COOL_BOTTOM)
236		__set_bit(PLATFORM_PROFILE_COOL, choices);
237	if (supported_modes & DELL_BALANCED)
238		__set_bit(PLATFORM_PROFILE_BALANCED, choices);
239	if (supported_modes & DELL_PERFORMANCE)
240		__set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
241
242	/* Make sure that ACPI is in sync with the profile set by USTT */
243	current_mode = thermal_get_mode();
244	if (current_mode < 0)
245		return current_mode;
246
247	thermal_set_mode(current_mode);
248
249	return 0;
250}
251
252static const struct platform_profile_ops dell_pc_platform_profile_ops = {
253	.probe = thermal_platform_profile_probe,
254	.profile_get = thermal_platform_profile_get,
255	.profile_set = thermal_platform_profile_set,
256};
257
258static int dell_pc_faux_probe(struct faux_device *fdev)
259{
260	struct device *ppdev;
261	int ret;
262
263	if (!dell_smbios_class_is_supported(CLASS_INFO))
264		return -ENODEV;
265
266	ret = thermal_get_supported_modes(&supported_modes);
267	if (ret < 0)
268		return ret;
269
270	ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL,
271					       &dell_pc_platform_profile_ops);
272
273	return PTR_ERR_OR_ZERO(ppdev);
274}
275
276static const struct faux_device_ops dell_pc_faux_ops = {
277	.probe = dell_pc_faux_probe,
278};
279
280static int __init dell_init(void)
281{
282	if (!dmi_check_system(dell_device_table))
283		return -ENODEV;
284
285	dell_pc_fdev = faux_device_create("dell-pc", NULL, &dell_pc_faux_ops);
286	if (!dell_pc_fdev)
287		return -ENODEV;
288
289	return 0;
290}
291
292static void __exit dell_exit(void)
293{
294	faux_device_destroy(dell_pc_fdev);
295}
296
297module_init(dell_init);
298module_exit(dell_exit);
299
300MODULE_AUTHOR("Lyndon Sanche <lsanche@lyndeno.ca>");
301MODULE_DESCRIPTION("Dell PC driver");
302MODULE_LICENSE("GPL");
~/ Read-only file preview.