Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Linux driver for WMI sensor information on Dell notebooks.
  4 *
  5 * Copyright (C) 2022 Armin Wolf <W_Armin@gmx.de>
  6 */
  7
  8#define pr_format(fmt) KBUILD_MODNAME ": " fmt
  9
 10#include <linux/acpi.h>
 11#include <linux/debugfs.h>
 12#include <linux/device.h>
 13#include <linux/dev_printk.h>
 14#include <linux/kernel.h>
 15#include <linux/kstrtox.h>
 16#include <linux/math.h>
 17#include <linux/module.h>
 18#include <linux/limits.h>
 19#include <linux/power_supply.h>
 20#include <linux/printk.h>
 21#include <linux/seq_file.h>
 22#include <linux/sysfs.h>
 23#include <linux/wmi.h>
 24
 25#include <acpi/battery.h>
 26
 27#define DRIVER_NAME	"dell-wmi-ddv"
 28
 29#define DELL_DDV_SUPPORTED_INTERFACE 2
 30#define DELL_DDV_GUID	"8A42EA14-4F2A-FD45-6422-0087F7A7E608"
 31
 32#define DELL_EPPID_LENGTH	20
 33#define DELL_EPPID_EXT_LENGTH	23
 34
 35enum dell_ddv_method {
 36	DELL_DDV_BATTERY_DESIGN_CAPACITY	= 0x01,
 37	DELL_DDV_BATTERY_FULL_CHARGE_CAPACITY	= 0x02,
 38	DELL_DDV_BATTERY_MANUFACTURE_NAME	= 0x03,
 39	DELL_DDV_BATTERY_MANUFACTURE_DATE	= 0x04,
 40	DELL_DDV_BATTERY_SERIAL_NUMBER		= 0x05,
 41	DELL_DDV_BATTERY_CHEMISTRY_VALUE	= 0x06,
 42	DELL_DDV_BATTERY_TEMPERATURE		= 0x07,
 43	DELL_DDV_BATTERY_CURRENT		= 0x08,
 44	DELL_DDV_BATTERY_VOLTAGE		= 0x09,
 45	DELL_DDV_BATTERY_MANUFACTURER_ACCESS	= 0x0A,
 46	DELL_DDV_BATTERY_RELATIVE_CHARGE_STATE	= 0x0B,
 47	DELL_DDV_BATTERY_CYCLE_COUNT		= 0x0C,
 48	DELL_DDV_BATTERY_EPPID			= 0x0D,
 49	DELL_DDV_BATTERY_RAW_ANALYTICS_START	= 0x0E,
 50	DELL_DDV_BATTERY_RAW_ANALYTICS		= 0x0F,
 51	DELL_DDV_BATTERY_DESIGN_VOLTAGE		= 0x10,
 52
 53	DELL_DDV_INTERFACE_VERSION		= 0x12,
 54
 55	DELL_DDV_FAN_SENSOR_INFORMATION		= 0x20,
 56	DELL_DDV_THERMAL_SENSOR_INFORMATION	= 0x22,
 57};
 58
 59struct dell_wmi_ddv_data {
 60	struct acpi_battery_hook hook;
 61	struct device_attribute temp_attr;
 62	struct device_attribute eppid_attr;
 63	struct wmi_device *wdev;
 64};
 65
 66static int dell_wmi_ddv_query_type(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg,
 67				   union acpi_object **result, acpi_object_type type)
 68{
 69	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
 70	const struct acpi_buffer in = {
 71		.length = sizeof(arg),
 72		.pointer = &arg,
 73	};
 74	union acpi_object *obj;
 75	acpi_status ret;
 76
 77	ret = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
 78	if (ACPI_FAILURE(ret))
 79		return -EIO;
 80
 81	obj = out.pointer;
 82	if (!obj)
 83		return -ENODATA;
 84
 85	if (obj->type != type) {
 86		kfree(obj);
 87		return -EIO;
 88	}
 89
 90	*result = obj;
 91
 92	return 0;
 93}
 94
 95static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_method method,
 96				      u32 arg, u32 *res)
 97{
 98	union acpi_object *obj;
 99	int ret;
100
101	ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_INTEGER);
102	if (ret < 0)
103		return ret;
104
105	if (obj->integer.value <= U32_MAX)
106		*res = (u32)obj->integer.value;
107	else
108		ret = -ERANGE;
109
110	kfree(obj);
111
112	return ret;
113}
114
115static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method,
116				     u32 arg, union acpi_object **result)
117{
118	union acpi_object *obj;
119	u64 buffer_size;
120	int ret;
121
122	ret = dell_wmi_ddv_query_type(wdev, method, arg, &obj, ACPI_TYPE_PACKAGE);
123	if (ret < 0)
124		return ret;
125
126	if (obj->package.count != 2)
127		goto err_free;
128
129	if (obj->package.elements[0].type != ACPI_TYPE_INTEGER)
130		goto err_free;
131
132	buffer_size = obj->package.elements[0].integer.value;
133
134	if (obj->package.elements[1].type != ACPI_TYPE_BUFFER)
135		goto err_free;
136
137	if (buffer_size > obj->package.elements[1].buffer.length) {
138		dev_warn(&wdev->dev,
139			 FW_WARN "WMI buffer size (%llu) exceeds ACPI buffer size (%d)\n",
140			 buffer_size, obj->package.elements[1].buffer.length);
141
142		goto err_free;
143	}
144
145	*result = obj;
146
147	return 0;
148
149err_free:
150	kfree(obj);
151
152	return -EIO;
153}
154
155static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method,
156				     u32 arg, union acpi_object **result)
157{
158	return dell_wmi_ddv_query_type(wdev, method, arg, result, ACPI_TYPE_STRING);
159}
160
161static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index)
162{
163	const char *uid_str;
164
165	uid_str = acpi_device_uid(acpi_dev);
166	if (!uid_str)
167		return -ENODEV;
168
169	return kstrtou32(uid_str, 10, index);
170}
171
172static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
173{
174	struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, temp_attr);
175	u32 index, value;
176	int ret;
177
178	ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index);
179	if (ret < 0)
180		return ret;
181
182	ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index, &value);
183	if (ret < 0)
184		return ret;
185
186	return sysfs_emit(buf, "%d\n", DIV_ROUND_CLOSEST(value, 10));
187}
188
189static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf)
190{
191	struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, eppid_attr);
192	union acpi_object *obj;
193	u32 index;
194	int ret;
195
196	ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index);
197	if (ret < 0)
198		return ret;
199
200	ret = dell_wmi_ddv_query_string(data->wdev, DELL_DDV_BATTERY_EPPID, index, &obj);
201	if (ret < 0)
202		return ret;
203
204	if (obj->string.length != DELL_EPPID_LENGTH && obj->string.length != DELL_EPPID_EXT_LENGTH)
205		dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%d)\n",
206			      obj->string.length);
207
208	ret = sysfs_emit(buf, "%s\n", obj->string.pointer);
209
210	kfree(obj);
211
212	return ret;
213}
214
215static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
216{
217	struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook);
218	u32 index;
219	int ret;
220
221	/* Return 0 instead of error to avoid being unloaded */
222	ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), &index);
223	if (ret < 0)
224		return 0;
225
226	ret = device_create_file(&battery->dev, &data->temp_attr);
227	if (ret < 0)
228		return ret;
229
230	ret = device_create_file(&battery->dev, &data->eppid_attr);
231	if (ret < 0) {
232		device_remove_file(&battery->dev, &data->temp_attr);
233
234		return ret;
235	}
236
237	return 0;
238}
239
240static int dell_wmi_ddv_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
241{
242	struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook);
243
244	device_remove_file(&battery->dev, &data->temp_attr);
245	device_remove_file(&battery->dev, &data->eppid_attr);
246
247	return 0;
248}
249
250static void dell_wmi_ddv_battery_remove(void *data)
251{
252	struct acpi_battery_hook *hook = data;
253
254	battery_hook_unregister(hook);
255}
256
257static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data)
258{
259	data->hook.name = "Dell DDV Battery Extension";
260	data->hook.add_battery = dell_wmi_ddv_add_battery;
261	data->hook.remove_battery = dell_wmi_ddv_remove_battery;
262
263	sysfs_attr_init(&data->temp_attr.attr);
264	data->temp_attr.attr.name = "temp";
265	data->temp_attr.attr.mode = 0444;
266	data->temp_attr.show = temp_show;
267
268	sysfs_attr_init(&data->eppid_attr.attr);
269	data->eppid_attr.attr.name = "eppid";
270	data->eppid_attr.attr.mode = 0444;
271	data->eppid_attr.show = eppid_show;
272
273	battery_hook_register(&data->hook);
274
275	return devm_add_action_or_reset(&data->wdev->dev, dell_wmi_ddv_battery_remove, &data->hook);
276}
277
278static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method)
279{
280	struct device *dev = seq->private;
281	struct dell_wmi_ddv_data *data = dev_get_drvdata(dev);
282	union acpi_object *obj;
283	u64 size;
284	u8 *buf;
285	int ret;
286
287	ret = dell_wmi_ddv_query_buffer(data->wdev, method, 0, &obj);
288	if (ret < 0)
289		return ret;
290
291	size = obj->package.elements[0].integer.value;
292	buf = obj->package.elements[1].buffer.pointer;
293	ret = seq_write(seq, buf, size);
294	kfree(obj);
295
296	return ret;
297}
298
299static int dell_wmi_ddv_fan_read(struct seq_file *seq, void *offset)
300{
301	return dell_wmi_ddv_buffer_read(seq, DELL_DDV_FAN_SENSOR_INFORMATION);
302}
303
304static int dell_wmi_ddv_temp_read(struct seq_file *seq, void *offset)
305{
306	return dell_wmi_ddv_buffer_read(seq, DELL_DDV_THERMAL_SENSOR_INFORMATION);
307}
308
309static void dell_wmi_ddv_debugfs_remove(void *data)
310{
311	struct dentry *entry = data;
312
313	debugfs_remove(entry);
314}
315
316static void dell_wmi_ddv_debugfs_init(struct wmi_device *wdev)
317{
318	struct dentry *entry;
319	char name[64];
320
321	scnprintf(name, ARRAY_SIZE(name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
322	entry = debugfs_create_dir(name, NULL);
323
324	debugfs_create_devm_seqfile(&wdev->dev, "fan_sensor_information", entry,
325				    dell_wmi_ddv_fan_read);
326	debugfs_create_devm_seqfile(&wdev->dev, "thermal_sensor_information", entry,
327				    dell_wmi_ddv_temp_read);
328
329	devm_add_action_or_reset(&wdev->dev, dell_wmi_ddv_debugfs_remove, entry);
330}
331
332static int dell_wmi_ddv_probe(struct wmi_device *wdev, const void *context)
333{
334	struct dell_wmi_ddv_data *data;
335	u32 version;
336	int ret;
337
338	ret = dell_wmi_ddv_query_integer(wdev, DELL_DDV_INTERFACE_VERSION, 0, &version);
339	if (ret < 0)
340		return ret;
341
342	dev_dbg(&wdev->dev, "WMI interface version: %d\n", version);
343	if (version != DELL_DDV_SUPPORTED_INTERFACE)
344		return -ENODEV;
345
346	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
347	if (!data)
348		return -ENOMEM;
349
350	dev_set_drvdata(&wdev->dev, data);
351	data->wdev = wdev;
352
353	dell_wmi_ddv_debugfs_init(wdev);
354
355	return dell_wmi_ddv_battery_add(data);
356}
357
358static const struct wmi_device_id dell_wmi_ddv_id_table[] = {
359	{ DELL_DDV_GUID, NULL },
360	{ }
361};
362MODULE_DEVICE_TABLE(wmi, dell_wmi_ddv_id_table);
363
364static struct wmi_driver dell_wmi_ddv_driver = {
365	.driver = {
366		.name = DRIVER_NAME,
367	},
368	.id_table = dell_wmi_ddv_id_table,
369	.probe = dell_wmi_ddv_probe,
370};
371module_wmi_driver(dell_wmi_ddv_driver);
372
373MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
374MODULE_DESCRIPTION("Dell WMI sensor driver");
375MODULE_LICENSE("GPL");