901 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Huawei WMI laptop extras driver4 *5 * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com>6 */7 8#include <linux/acpi.h>9#include <linux/debugfs.h>10#include <linux/delay.h>11#include <linux/dmi.h>12#include <linux/input.h>13#include <linux/input/sparse-keymap.h>14#include <linux/leds.h>15#include <linux/module.h>16#include <linux/mutex.h>17#include <linux/platform_device.h>18#include <linux/power_supply.h>19#include <linux/sysfs.h>20#include <linux/wmi.h>21#include <acpi/battery.h>22 23/*24 * Huawei WMI GUIDs25 */26#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000"27#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"28 29/* Legacy GUIDs */30#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"31#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"32 33/* HWMI commands */34 35enum {36 BATTERY_THRESH_GET = 0x00001103, /* \GBTT */37 BATTERY_THRESH_SET = 0x00001003, /* \SBTT */38 FN_LOCK_GET = 0x00000604, /* \GFRS */39 FN_LOCK_SET = 0x00000704, /* \SFRS */40 MICMUTE_LED_SET = 0x00000b04, /* \SMLS */41};42 43union hwmi_arg {44 u64 cmd;45 u8 args[8];46};47 48struct quirk_entry {49 bool battery_reset;50 bool ec_micmute;51 bool report_brightness;52};53 54static struct quirk_entry *quirks;55 56struct huawei_wmi_debug {57 struct dentry *root;58 u64 arg;59};60 61struct huawei_wmi {62 bool battery_available;63 bool fn_lock_available;64 65 struct huawei_wmi_debug debug;66 struct led_classdev cdev;67 struct device *dev;68 69 struct mutex wmi_lock;70};71 72static struct huawei_wmi *huawei_wmi;73 74static const struct key_entry huawei_wmi_keymap[] = {75 { KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } },76 { KE_KEY, 0x282, { KEY_BRIGHTNESSUP } },77 { KE_KEY, 0x284, { KEY_MUTE } },78 { KE_KEY, 0x285, { KEY_VOLUMEDOWN } },79 { KE_KEY, 0x286, { KEY_VOLUMEUP } },80 { KE_KEY, 0x287, { KEY_MICMUTE } },81 { KE_KEY, 0x289, { KEY_WLAN } },82 // Huawei |M| key83 { KE_KEY, 0x28a, { KEY_CONFIG } },84 // Keyboard backlit85 { KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },86 { KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },87 { KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },88 // Ignore Ambient Light Sensoring89 { KE_KEY, 0x2c1, { KEY_RESERVED } },90 { KE_END, 0 }91};92 93static int battery_reset = -1;94static int report_brightness = -1;95 96module_param(battery_reset, bint, 0444);97MODULE_PARM_DESC(battery_reset,98 "Reset battery charge values to (0-0) before disabling it using (0-100)");99module_param(report_brightness, bint, 0444);100MODULE_PARM_DESC(report_brightness,101 "Report brightness keys.");102 103/* Quirks */104 105static int __init dmi_matched(const struct dmi_system_id *dmi)106{107 quirks = dmi->driver_data;108 return 1;109}110 111static struct quirk_entry quirk_unknown = {112};113 114static struct quirk_entry quirk_battery_reset = {115 .battery_reset = true,116};117 118static struct quirk_entry quirk_matebook_x = {119 .ec_micmute = true,120 .report_brightness = true,121};122 123static const struct dmi_system_id huawei_quirks[] = {124 {125 .callback = dmi_matched,126 .ident = "Huawei MACH-WX9",127 .matches = {128 DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),129 DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"),130 },131 .driver_data = &quirk_battery_reset132 },133 {134 .callback = dmi_matched,135 .ident = "Huawei MateBook X",136 .matches = {137 DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),138 DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X")139 },140 .driver_data = &quirk_matebook_x141 },142 { }143};144 145/* Utils */146 147static int huawei_wmi_call(struct huawei_wmi *huawei,148 struct acpi_buffer *in, struct acpi_buffer *out)149{150 acpi_status status;151 152 mutex_lock(&huawei->wmi_lock);153 status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out);154 mutex_unlock(&huawei->wmi_lock);155 if (ACPI_FAILURE(status)) {156 dev_err(huawei->dev, "Failed to evaluate wmi method\n");157 return -ENODEV;158 }159 160 return 0;161}162 163/* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of164 * 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes.165 * The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a166 * package, or skip the first 4 if a buffer of 0x104 is used. The first byte of167 * the remaining 0x100 sized buffer has the return status of every call. In case168 * the return status is non-zero, we return -ENODEV but still copy the returned169 * buffer to the given buffer parameter (buf).170 */171static int huawei_wmi_cmd(u64 arg, u8 *buf, size_t buflen)172{173 struct huawei_wmi *huawei = huawei_wmi;174 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };175 struct acpi_buffer in;176 union acpi_object *obj;177 size_t len;178 int err, i;179 180 in.length = sizeof(arg);181 in.pointer = &arg;182 183 /* Some models require calling HWMI twice to execute a command. We evaluate184 * HWMI and if we get a non-zero return status we evaluate it again.185 */186 for (i = 0; i < 2; i++) {187 err = huawei_wmi_call(huawei, &in, &out);188 if (err)189 goto fail_cmd;190 191 obj = out.pointer;192 if (!obj) {193 err = -EIO;194 goto fail_cmd;195 }196 197 switch (obj->type) {198 /* Models that implement both "legacy" and HWMI tend to return a 0x104199 * sized buffer instead of a package of 0x4 and 0x100 buffers.200 */201 case ACPI_TYPE_BUFFER:202 if (obj->buffer.length == 0x104) {203 // Skip the first 4 bytes.204 obj->buffer.pointer += 4;205 len = 0x100;206 } else {207 dev_err(huawei->dev, "Bad buffer length, got %d\n", obj->buffer.length);208 err = -EIO;209 goto fail_cmd;210 }211 212 break;213 /* HWMI returns a package with 2 buffer elements, one of 4 bytes and the214 * other is 256 bytes.215 */216 case ACPI_TYPE_PACKAGE:217 if (obj->package.count != 2) {218 dev_err(huawei->dev, "Bad package count, got %d\n", obj->package.count);219 err = -EIO;220 goto fail_cmd;221 }222 223 obj = &obj->package.elements[1];224 if (obj->type != ACPI_TYPE_BUFFER) {225 dev_err(huawei->dev, "Bad package element type, got %d\n", obj->type);226 err = -EIO;227 goto fail_cmd;228 }229 len = obj->buffer.length;230 231 break;232 /* Shouldn't get here! */233 default:234 dev_err(huawei->dev, "Unexpected obj type, got: %d\n", obj->type);235 err = -EIO;236 goto fail_cmd;237 }238 239 if (!*obj->buffer.pointer)240 break;241 }242 243 err = (*obj->buffer.pointer) ? -ENODEV : 0;244 245 if (buf) {246 len = min(buflen, len);247 memcpy(buf, obj->buffer.pointer, len);248 }249 250fail_cmd:251 kfree(out.pointer);252 return err;253}254 255/* LEDs */256 257static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,258 enum led_brightness brightness)259{260 /* This is a workaround until the "legacy" interface is implemented. */261 if (quirks && quirks->ec_micmute) {262 char *acpi_method;263 acpi_handle handle;264 acpi_status status;265 union acpi_object args[3];266 struct acpi_object_list arg_list = {267 .pointer = args,268 .count = ARRAY_SIZE(args),269 };270 271 handle = ec_get_handle();272 if (!handle)273 return -ENODEV;274 275 args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;276 args[1].integer.value = 0x04;277 278 if (acpi_has_method(handle, "SPIN")) {279 acpi_method = "SPIN";280 args[0].integer.value = 0;281 args[2].integer.value = brightness ? 1 : 0;282 } else if (acpi_has_method(handle, "WPIN")) {283 acpi_method = "WPIN";284 args[0].integer.value = 1;285 args[2].integer.value = brightness ? 0 : 1;286 } else {287 return -ENODEV;288 }289 290 status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);291 if (ACPI_FAILURE(status))292 return -ENODEV;293 294 return 0;295 } else {296 union hwmi_arg arg;297 298 arg.cmd = MICMUTE_LED_SET;299 arg.args[2] = brightness;300 301 return huawei_wmi_cmd(arg.cmd, NULL, 0);302 }303}304 305static void huawei_wmi_leds_setup(struct device *dev)306{307 struct huawei_wmi *huawei = dev_get_drvdata(dev);308 309 huawei->cdev.name = "platform::micmute";310 huawei->cdev.max_brightness = 1;311 huawei->cdev.brightness_set_blocking = &huawei_wmi_micmute_led_set;312 huawei->cdev.default_trigger = "audio-micmute";313 huawei->cdev.dev = dev;314 huawei->cdev.flags = LED_CORE_SUSPENDRESUME;315 316 devm_led_classdev_register(dev, &huawei->cdev);317}318 319/* Battery protection */320 321static int huawei_wmi_battery_get(int *start, int *end)322{323 u8 ret[0x100];324 int err, i;325 326 err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, sizeof(ret));327 if (err)328 return err;329 330 /* Find the last two non-zero values. Return status is ignored. */331 i = ARRAY_SIZE(ret) - 1;332 do {333 if (start)334 *start = ret[i-1];335 if (end)336 *end = ret[i];337 } while (i > 2 && !ret[i--]);338 339 return 0;340}341 342static int huawei_wmi_battery_set(int start, int end)343{344 union hwmi_arg arg;345 int err;346 347 if (start < 0 || end < 0 || start > 100 || end > 100)348 return -EINVAL;349 350 arg.cmd = BATTERY_THRESH_SET;351 arg.args[2] = start;352 arg.args[3] = end;353 354 /* This is an edge case were some models turn battery protection355 * off without changing their thresholds values. We clear the356 * values before turning off protection. Sometimes we need a sleep delay to357 * make sure these values make their way to EC memory.358 */359 if (quirks && quirks->battery_reset && start == 0 && end == 100) {360 err = huawei_wmi_battery_set(0, 0);361 if (err)362 return err;363 364 msleep(1000);365 }366 367 err = huawei_wmi_cmd(arg.cmd, NULL, 0);368 369 return err;370}371 372static ssize_t charge_control_start_threshold_show(struct device *dev,373 struct device_attribute *attr,374 char *buf)375{376 int err, start;377 378 err = huawei_wmi_battery_get(&start, NULL);379 if (err)380 return err;381 382 return sysfs_emit(buf, "%d\n", start);383}384 385static ssize_t charge_control_end_threshold_show(struct device *dev,386 struct device_attribute *attr,387 char *buf)388{389 int err, end;390 391 err = huawei_wmi_battery_get(NULL, &end);392 if (err)393 return err;394 395 return sysfs_emit(buf, "%d\n", end);396}397 398static ssize_t charge_control_thresholds_show(struct device *dev,399 struct device_attribute *attr,400 char *buf)401{402 int err, start, end;403 404 err = huawei_wmi_battery_get(&start, &end);405 if (err)406 return err;407 408 return sysfs_emit(buf, "%d %d\n", start, end);409}410 411static ssize_t charge_control_start_threshold_store(struct device *dev,412 struct device_attribute *attr,413 const char *buf, size_t size)414{415 int err, start, end;416 417 err = huawei_wmi_battery_get(NULL, &end);418 if (err)419 return err;420 421 if (sscanf(buf, "%d", &start) != 1)422 return -EINVAL;423 424 err = huawei_wmi_battery_set(start, end);425 if (err)426 return err;427 428 return size;429}430 431static ssize_t charge_control_end_threshold_store(struct device *dev,432 struct device_attribute *attr,433 const char *buf, size_t size)434{435 int err, start, end;436 437 err = huawei_wmi_battery_get(&start, NULL);438 if (err)439 return err;440 441 if (sscanf(buf, "%d", &end) != 1)442 return -EINVAL;443 444 err = huawei_wmi_battery_set(start, end);445 if (err)446 return err;447 448 return size;449}450 451static ssize_t charge_control_thresholds_store(struct device *dev,452 struct device_attribute *attr,453 const char *buf, size_t size)454{455 int err, start, end;456 457 if (sscanf(buf, "%d %d", &start, &end) != 2)458 return -EINVAL;459 460 err = huawei_wmi_battery_set(start, end);461 if (err)462 return err;463 464 return size;465}466 467static DEVICE_ATTR_RW(charge_control_start_threshold);468static DEVICE_ATTR_RW(charge_control_end_threshold);469static DEVICE_ATTR_RW(charge_control_thresholds);470 471static int huawei_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)472{473 int err = 0;474 475 err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);476 if (err)477 return err;478 479 err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);480 if (err)481 device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);482 483 return err;484}485 486static int huawei_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)487{488 device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);489 device_remove_file(&battery->dev, &dev_attr_charge_control_end_threshold);490 491 return 0;492}493 494static struct acpi_battery_hook huawei_wmi_battery_hook = {495 .add_battery = huawei_wmi_battery_add,496 .remove_battery = huawei_wmi_battery_remove,497 .name = "Huawei Battery Extension"498};499 500static void huawei_wmi_battery_setup(struct device *dev)501{502 struct huawei_wmi *huawei = dev_get_drvdata(dev);503 504 huawei->battery_available = true;505 if (huawei_wmi_battery_get(NULL, NULL)) {506 huawei->battery_available = false;507 return;508 }509 510 battery_hook_register(&huawei_wmi_battery_hook);511 device_create_file(dev, &dev_attr_charge_control_thresholds);512}513 514static void huawei_wmi_battery_exit(struct device *dev)515{516 struct huawei_wmi *huawei = dev_get_drvdata(dev);517 518 if (huawei->battery_available) {519 battery_hook_unregister(&huawei_wmi_battery_hook);520 device_remove_file(dev, &dev_attr_charge_control_thresholds);521 }522}523 524/* Fn lock */525 526static int huawei_wmi_fn_lock_get(int *on)527{528 u8 ret[0x100] = { 0 };529 int err, i;530 531 err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);532 if (err)533 return err;534 535 /* Find the first non-zero value. Return status is ignored. */536 i = 1;537 do {538 if (on)539 *on = ret[i] - 1; // -1 undefined, 0 off, 1 on.540 } while (i < 0xff && !ret[i++]);541 542 return 0;543}544 545static int huawei_wmi_fn_lock_set(int on)546{547 union hwmi_arg arg;548 549 arg.cmd = FN_LOCK_SET;550 arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.551 552 return huawei_wmi_cmd(arg.cmd, NULL, 0);553}554 555static ssize_t fn_lock_state_show(struct device *dev,556 struct device_attribute *attr,557 char *buf)558{559 int err, on;560 561 err = huawei_wmi_fn_lock_get(&on);562 if (err)563 return err;564 565 return sysfs_emit(buf, "%d\n", on);566}567 568static ssize_t fn_lock_state_store(struct device *dev,569 struct device_attribute *attr,570 const char *buf, size_t size)571{572 int on, err;573 574 if (kstrtoint(buf, 10, &on) ||575 on < 0 || on > 1)576 return -EINVAL;577 578 err = huawei_wmi_fn_lock_set(on);579 if (err)580 return err;581 582 return size;583}584 585static DEVICE_ATTR_RW(fn_lock_state);586 587static void huawei_wmi_fn_lock_setup(struct device *dev)588{589 struct huawei_wmi *huawei = dev_get_drvdata(dev);590 591 huawei->fn_lock_available = true;592 if (huawei_wmi_fn_lock_get(NULL)) {593 huawei->fn_lock_available = false;594 return;595 }596 597 device_create_file(dev, &dev_attr_fn_lock_state);598}599 600static void huawei_wmi_fn_lock_exit(struct device *dev)601{602 struct huawei_wmi *huawei = dev_get_drvdata(dev);603 604 if (huawei->fn_lock_available)605 device_remove_file(dev, &dev_attr_fn_lock_state);606}607 608/* debugfs */609 610static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data,611 union acpi_object *obj)612{613 struct huawei_wmi *huawei = m->private;614 int i;615 616 switch (obj->type) {617 case ACPI_TYPE_INTEGER:618 seq_printf(m, "0x%llx", obj->integer.value);619 break;620 case ACPI_TYPE_STRING:621 seq_printf(m, "\"%.*s\"", obj->string.length, obj->string.pointer);622 break;623 case ACPI_TYPE_BUFFER:624 seq_puts(m, "{");625 for (i = 0; i < obj->buffer.length; i++) {626 seq_printf(m, "0x%02x", obj->buffer.pointer[i]);627 if (i < obj->buffer.length - 1)628 seq_puts(m, ",");629 }630 seq_puts(m, "}");631 break;632 case ACPI_TYPE_PACKAGE:633 seq_puts(m, "[");634 for (i = 0; i < obj->package.count; i++) {635 huawei_wmi_debugfs_call_dump(m, huawei, &obj->package.elements[i]);636 if (i < obj->package.count - 1)637 seq_puts(m, ",");638 }639 seq_puts(m, "]");640 break;641 default:642 dev_err(huawei->dev, "Unexpected obj type, got %d\n", obj->type);643 return;644 }645}646 647static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data)648{649 struct huawei_wmi *huawei = m->private;650 struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };651 struct acpi_buffer in;652 union acpi_object *obj;653 int err;654 655 in.length = sizeof(u64);656 in.pointer = &huawei->debug.arg;657 658 err = huawei_wmi_call(huawei, &in, &out);659 if (err)660 return err;661 662 obj = out.pointer;663 if (!obj) {664 err = -EIO;665 goto fail_debugfs_call;666 }667 668 huawei_wmi_debugfs_call_dump(m, huawei, obj);669 670fail_debugfs_call:671 kfree(out.pointer);672 return err;673}674 675DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call);676 677static void huawei_wmi_debugfs_setup(struct device *dev)678{679 struct huawei_wmi *huawei = dev_get_drvdata(dev);680 681 huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL);682 683 debugfs_create_x64("arg", 0644, huawei->debug.root,684 &huawei->debug.arg);685 debugfs_create_file("call", 0400,686 huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops);687}688 689static void huawei_wmi_debugfs_exit(struct device *dev)690{691 struct huawei_wmi *huawei = dev_get_drvdata(dev);692 693 debugfs_remove_recursive(huawei->debug.root);694}695 696/* Input */697 698static void huawei_wmi_process_key(struct input_dev *idev, int code)699{700 const struct key_entry *key;701 702 /*703 * WMI0 uses code 0x80 to indicate a hotkey event.704 * The actual key is fetched from the method WQ00705 * using WMI0_EXPENSIVE_GUID.706 */707 if (code == 0x80) {708 struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };709 union acpi_object *obj;710 acpi_status status;711 712 status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response);713 if (ACPI_FAILURE(status))714 return;715 716 obj = (union acpi_object *)response.pointer;717 if (obj && obj->type == ACPI_TYPE_INTEGER)718 code = obj->integer.value;719 720 kfree(response.pointer);721 }722 723 key = sparse_keymap_entry_from_scancode(idev, code);724 if (!key) {725 dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code);726 return;727 }728 729 if (quirks && !quirks->report_brightness &&730 (key->sw.code == KEY_BRIGHTNESSDOWN ||731 key->sw.code == KEY_BRIGHTNESSUP))732 return;733 734 sparse_keymap_report_entry(idev, key, 1, true);735}736 737static void huawei_wmi_input_notify(union acpi_object *obj, void *context)738{739 struct input_dev *idev = (struct input_dev *)context;740 741 if (obj && obj->type == ACPI_TYPE_INTEGER)742 huawei_wmi_process_key(idev, obj->integer.value);743 else744 dev_err(&idev->dev, "Bad response type\n");745}746 747static int huawei_wmi_input_setup(struct device *dev, const char *guid)748{749 struct input_dev *idev;750 acpi_status status;751 int err;752 753 idev = devm_input_allocate_device(dev);754 if (!idev)755 return -ENOMEM;756 757 idev->name = "Huawei WMI hotkeys";758 idev->phys = "wmi/input0";759 idev->id.bustype = BUS_HOST;760 idev->dev.parent = dev;761 762 err = sparse_keymap_setup(idev, huawei_wmi_keymap, NULL);763 if (err)764 return err;765 766 err = input_register_device(idev);767 if (err)768 return err;769 770 status = wmi_install_notify_handler(guid, huawei_wmi_input_notify, idev);771 if (ACPI_FAILURE(status))772 return -EIO;773 774 return 0;775}776 777static void huawei_wmi_input_exit(struct device *dev, const char *guid)778{779 wmi_remove_notify_handler(guid);780}781 782/* Huawei driver */783 784static const struct wmi_device_id huawei_wmi_events_id_table[] = {785 { .guid_string = WMI0_EVENT_GUID },786 { .guid_string = HWMI_EVENT_GUID },787 { }788};789 790static int huawei_wmi_probe(struct platform_device *pdev)791{792 const struct wmi_device_id *guid = huawei_wmi_events_id_table;793 int err;794 795 platform_set_drvdata(pdev, huawei_wmi);796 huawei_wmi->dev = &pdev->dev;797 798 while (*guid->guid_string) {799 if (wmi_has_guid(guid->guid_string)) {800 err = huawei_wmi_input_setup(&pdev->dev, guid->guid_string);801 if (err) {802 dev_err(&pdev->dev, "Failed to setup input on %s\n", guid->guid_string);803 return err;804 }805 }806 807 guid++;808 }809 810 if (wmi_has_guid(HWMI_METHOD_GUID)) {811 mutex_init(&huawei_wmi->wmi_lock);812 813 huawei_wmi_leds_setup(&pdev->dev);814 huawei_wmi_fn_lock_setup(&pdev->dev);815 huawei_wmi_battery_setup(&pdev->dev);816 huawei_wmi_debugfs_setup(&pdev->dev);817 }818 819 return 0;820}821 822static void huawei_wmi_remove(struct platform_device *pdev)823{824 const struct wmi_device_id *guid = huawei_wmi_events_id_table;825 826 while (*guid->guid_string) {827 if (wmi_has_guid(guid->guid_string))828 huawei_wmi_input_exit(&pdev->dev, guid->guid_string);829 830 guid++;831 }832 833 if (wmi_has_guid(HWMI_METHOD_GUID)) {834 huawei_wmi_debugfs_exit(&pdev->dev);835 huawei_wmi_battery_exit(&pdev->dev);836 huawei_wmi_fn_lock_exit(&pdev->dev);837 }838}839 840static struct platform_driver huawei_wmi_driver = {841 .driver = {842 .name = "huawei-wmi",843 },844 .probe = huawei_wmi_probe,845 .remove_new = huawei_wmi_remove,846};847 848static __init int huawei_wmi_init(void)849{850 struct platform_device *pdev;851 int err;852 853 huawei_wmi = kzalloc(sizeof(struct huawei_wmi), GFP_KERNEL);854 if (!huawei_wmi)855 return -ENOMEM;856 857 quirks = &quirk_unknown;858 dmi_check_system(huawei_quirks);859 if (battery_reset != -1)860 quirks->battery_reset = battery_reset;861 if (report_brightness != -1)862 quirks->report_brightness = report_brightness;863 864 err = platform_driver_register(&huawei_wmi_driver);865 if (err)866 goto pdrv_err;867 868 pdev = platform_device_register_simple("huawei-wmi", PLATFORM_DEVID_NONE, NULL, 0);869 if (IS_ERR(pdev)) {870 err = PTR_ERR(pdev);871 goto pdev_err;872 }873 874 return 0;875 876pdev_err:877 platform_driver_unregister(&huawei_wmi_driver);878pdrv_err:879 kfree(huawei_wmi);880 return err;881}882 883static __exit void huawei_wmi_exit(void)884{885 struct platform_device *pdev = to_platform_device(huawei_wmi->dev);886 887 platform_device_unregister(pdev);888 platform_driver_unregister(&huawei_wmi_driver);889 890 kfree(huawei_wmi);891}892 893module_init(huawei_wmi_init);894module_exit(huawei_wmi_exit);895 896MODULE_ALIAS("wmi:"HWMI_METHOD_GUID);897MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table);898MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");899MODULE_DESCRIPTION("Huawei WMI laptop extras driver");900MODULE_LICENSE("GPL v2");901