aboutsummaryrefslogtreecommitdiff
path: root/drivers/platform/x86/dell/dell-pc.c
blob: 972385ca1990b00738b73331baae484fa6d4b905 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Driver for Dell laptop extras
 *
 *  Copyright (c) Lyndon Sanche <lsanche@lyndeno.ca>
 *
 *  Based on documentation in the libsmbios package:
 *  Copyright (C) 2005-2014 Dell Inc.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_profile.h>
#include <linux/slab.h>

#include "dell-smbios.h"

static const struct dmi_system_id dell_device_table[] __initconst = {
	{
		.ident = "Dell Inc.",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		},
	},
	{
		.ident = "Dell Computer Corporation",
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
		},
	},
	{ }
};
MODULE_DEVICE_TABLE(dmi, dell_device_table);

/* Derived from smbios-thermal-ctl
 *
 * cbClass 17
 * cbSelect 19
 * User Selectable Thermal Tables(USTT)
 * cbArg1 determines the function to be performed
 * cbArg1 0x0 = Get Thermal Information
 *  cbRES1         Standard return codes (0, -1, -2)
 *  cbRES2, byte 0  Bitmap of supported thermal modes. A mode is supported if
 *                  its bit is set to 1
 *     Bit 0 Balanced
 *     Bit 1 Cool Bottom
 *     Bit 2 Quiet
 *     Bit 3 Performance
 *  cbRES2, byte 1 Bitmap of supported Active Acoustic Controller (AAC) modes.
 *                 Each mode corresponds to the supported thermal modes in
 *                  byte 0. A mode is supported if its bit is set to 1.
 *     Bit 0 AAC (Balanced)
 *     Bit 1 AAC (Cool Bottom
 *     Bit 2 AAC (Quiet)
 *     Bit 3 AAC (Performance)
 *  cbRes3, byte 0 Current Thermal Mode
 *     Bit 0 Balanced
 *     Bit 1 Cool Bottom
 *     Bit 2 Quiet
 *     Bit 3 Performanc
 *  cbRes3, byte 1  AAC Configuration type
 *          0       Global (AAC enable/disable applies to all supported USTT modes)
 *          1       USTT mode specific
 *  cbRes3, byte 2  Current Active Acoustic Controller (AAC) Mode
 *     If AAC Configuration Type is Global,
 *          0       AAC mode disabled
 *          1       AAC mode enabled
 *     If AAC Configuration Type is USTT mode specific (multiple bits may be set),
 *          Bit 0 AAC (Balanced)
 *          Bit 1 AAC (Cool Bottom
 *          Bit 2 AAC (Quiet)
 *          Bit 3 AAC (Performance)
 *  cbRes3, byte 3  Current Fan Failure Mode
 *     Bit 0 Minimal Fan Failure (at least one fan has failed, one fan working)
 *     Bit 1 Catastrophic Fan Failure (all fans have failed)
 *
 * cbArg1 0x1   (Set Thermal Information), both desired thermal mode and
 *               desired AAC mode shall be applied
 * cbArg2, byte 0  Desired Thermal Mode to set
 *                  (only one bit may be set for this parameter)
 *     Bit 0 Balanced
 *     Bit 1 Cool Bottom
 *     Bit 2 Quiet
 *     Bit 3 Performance
 * cbArg2, byte 1  Desired Active Acoustic Controller (AAC) Mode to set
 *     If AAC Configuration Type is Global,
 *         0  AAC mode disabled
 *         1  AAC mode enabled
 *     If AAC Configuration Type is USTT mode specific
 *     (multiple bits may be set for this parameter),
 *         Bit 0 AAC (Balanced)
 *         Bit 1 AAC (Cool Bottom
 *         Bit 2 AAC (Quiet)
 *         Bit 3 AAC (Performance)
 */

#define DELL_ACC_GET_FIELD	GENMASK(19, 16)
#define DELL_ACC_SET_FIELD	GENMASK(11, 8)
#define DELL_THERMAL_SUPPORTED	GENMASK(3, 0)

static struct platform_profile_handler *thermal_handler;

enum thermal_mode_bits {
	DELL_BALANCED    = BIT(0),
	DELL_COOL_BOTTOM = BIT(1),
	DELL_QUIET       = BIT(2),
	DELL_PERFORMANCE = BIT(3),
};

static int thermal_get_mode(void)
{
	struct calling_interface_buffer buffer;
	int state;
	int ret;

	dell_fill_request(&buffer, 0x0, 0, 0, 0);
	ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
	if (ret)
		return ret;
	state = buffer.output[2];
	if (state & DELL_BALANCED)
		return DELL_BALANCED;
	else if (state & DELL_COOL_BOTTOM)
		return DELL_COOL_BOTTOM;
	else if (state & DELL_QUIET)
		return DELL_QUIET;
	else if (state & DELL_PERFORMANCE)
		return DELL_PERFORMANCE;
	else
		return -ENXIO;
}

static int thermal_get_supported_modes(int *supported_bits)
{
	struct calling_interface_buffer buffer;
	int ret;

	dell_fill_request(&buffer, 0x0, 0, 0, 0);
	ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
	/* Thermal function not supported */
	if (ret == -ENXIO) {
		*supported_bits = 0;
		return 0;
	}
	if (ret)
		return ret;
	*supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]);
	return 0;
}

static int thermal_get_acc_mode(int *acc_mode)
{
	struct calling_interface_buffer buffer;
	int ret;

	dell_fill_request(&buffer, 0x0, 0, 0, 0);
	ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
	if (ret)
		return ret;
	*acc_mode = FIELD_GET(DELL_ACC_GET_FIELD, buffer.output[3]);
	return 0;
}

static int thermal_set_mode(enum thermal_mode_bits state)
{
	struct calling_interface_buffer buffer;
	int ret;
	int acc_mode;

	ret = thermal_get_acc_mode(&acc_mode);
	if (ret)
		return ret;

	dell_fill_request(&buffer, 0x1, FIELD_PREP(DELL_ACC_SET_FIELD, acc_mode) | state, 0, 0);
	return dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
}

static int thermal_platform_profile_set(struct platform_profile_handler *pprof,
					enum platform_profile_option profile)
{
	switch (profile) {
	case PLATFORM_PROFILE_BALANCED:
		return thermal_set_mode(DELL_BALANCED);
	case PLATFORM_PROFILE_PERFORMANCE:
		return thermal_set_mode(DELL_PERFORMANCE);
	case PLATFORM_PROFILE_QUIET:
		return thermal_set_mode(DELL_QUIET);
	case PLATFORM_PROFILE_COOL:
		return thermal_set_mode(DELL_COOL_BOTTOM);
	default:
		return -EOPNOTSUPP;
	}
}

static int thermal_platform_profile_get(struct platform_profile_handler *pprof,
					enum platform_profile_option *profile)
{
	int ret;

	ret = thermal_get_mode();
	if (ret < 0)
		return ret;

	switch (ret) {
	case DELL_BALANCED:
		*profile = PLATFORM_PROFILE_BALANCED;
		break;
	case DELL_PERFORMANCE:
		*profile = PLATFORM_PROFILE_PERFORMANCE;
		break;
	case DELL_COOL_BOTTOM:
		*profile = PLATFORM_PROFILE_COOL;
		break;
	case DELL_QUIET:
		*profile = PLATFORM_PROFILE_QUIET;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int thermal_init(void)
{
	int ret;
	int supported_modes;

	/* If thermal commands are not supported, exit without error */
	if (!dell_smbios_class_is_supported(CLASS_INFO))
		return 0;

	/* If thermal modes are not supported, exit without error */
	ret = thermal_get_supported_modes(&supported_modes);
	if (ret < 0)
		return ret;
	if (!supported_modes)
		return 0;

	thermal_handler = kzalloc(sizeof(*thermal_handler), GFP_KERNEL);
	if (!thermal_handler)
		return -ENOMEM;
	thermal_handler->profile_get = thermal_platform_profile_get;
	thermal_handler->profile_set = thermal_platform_profile_set;

	if (supported_modes & DELL_QUIET)
		set_bit(PLATFORM_PROFILE_QUIET, thermal_handler->choices);
	if (supported_modes & DELL_COOL_BOTTOM)
		set_bit(PLATFORM_PROFILE_COOL, thermal_handler->choices);
	if (supported_modes & DELL_BALANCED)
		set_bit(PLATFORM_PROFILE_BALANCED, thermal_handler->choices);
	if (supported_modes & DELL_PERFORMANCE)
		set_bit(PLATFORM_PROFILE_PERFORMANCE, thermal_handler->choices);

	/* Clean up if failed */
	ret = platform_profile_register(thermal_handler);
	if (ret) {
		kfree(thermal_handler);
		thermal_handler = NULL;
	}

	return ret;
}

static void thermal_cleanup(void)
{
	if (thermal_handler) {
		platform_profile_remove();
		kfree(thermal_handler);
	}
}

static int __init dell_init(void)
{
	int ret;

	if (!dmi_check_system(dell_device_table))
		return -ENODEV;

	/* Do not fail module if thermal modes not supported, just skip */
	ret = thermal_init();
	if (ret)
		goto fail_thermal;

	return 0;

fail_thermal:
	thermal_cleanup();
	return ret;
}

static void __exit dell_exit(void)
{
	thermal_cleanup();
}

module_init(dell_init);
module_exit(dell_exit);

MODULE_AUTHOR("Lyndon Sanche <lsanche@lyndeno.ca>");
MODULE_DESCRIPTION("Dell PC driver");
MODULE_LICENSE("GPL");