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 35 enum 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 59 struct 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 66 static 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 95 static 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 115 static 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 149 err_free: 150 kfree(obj); 151 152 return -EIO; 153 } 154 155 static 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 161 static 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 172 static 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 189 static 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 215 static 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 240 static 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 250 static void dell_wmi_ddv_battery_remove(void *data) 251 { 252 struct acpi_battery_hook *hook = data; 253 254 battery_hook_unregister(hook); 255 } 256 257 static 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 278 static 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 299 static 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 304 static 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 309 static void dell_wmi_ddv_debugfs_remove(void *data) 310 { 311 struct dentry *entry = data; 312 313 debugfs_remove(entry); 314 } 315 316 static 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 332 static 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 358 static const struct wmi_device_id dell_wmi_ddv_id_table[] = { 359 { DELL_DDV_GUID, NULL }, 360 { } 361 }; 362 MODULE_DEVICE_TABLE(wmi, dell_wmi_ddv_id_table); 363 364 static 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 }; 371 module_wmi_driver(dell_wmi_ddv_driver); 372 373 MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>"); 374 MODULE_DESCRIPTION("Dell WMI sensor driver"); 375 MODULE_LICENSE("GPL"); 376