brintos

brintos / linux-shallow public Read only

0
0
Text · 20.3 KiB · d81fd5d Raw
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