1bf4fb28cSAyman Bagabas // SPDX-License-Identifier: GPL-2.0
2bf4fb28cSAyman Bagabas /*
31ac9abebSAyman Bagabas * Huawei WMI laptop extras driver
4bf4fb28cSAyman Bagabas *
5bf4fb28cSAyman Bagabas * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com>
6bf4fb28cSAyman Bagabas */
7bf4fb28cSAyman Bagabas
8bf4fb28cSAyman Bagabas #include <linux/acpi.h>
994930d01SAyman Bagabas #include <linux/debugfs.h>
10355a070bSAyman Bagabas #include <linux/delay.h>
118a480c10SAyman Bagabas #include <linux/dmi.h>
12bf4fb28cSAyman Bagabas #include <linux/input.h>
13bf4fb28cSAyman Bagabas #include <linux/input/sparse-keymap.h>
14bf4fb28cSAyman Bagabas #include <linux/leds.h>
15bf4fb28cSAyman Bagabas #include <linux/module.h>
16090ddd77SAyman Bagabas #include <linux/mutex.h>
171ac9abebSAyman Bagabas #include <linux/platform_device.h>
18355a070bSAyman Bagabas #include <linux/power_supply.h>
19355a070bSAyman Bagabas #include <linux/sysfs.h>
20bf4fb28cSAyman Bagabas #include <linux/wmi.h>
21355a070bSAyman Bagabas #include <acpi/battery.h>
22bf4fb28cSAyman Bagabas
23bf4fb28cSAyman Bagabas /*
24bf4fb28cSAyman Bagabas * Huawei WMI GUIDs
25bf4fb28cSAyman Bagabas */
26090ddd77SAyman Bagabas #define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000"
271ac9abebSAyman Bagabas #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
28bf4fb28cSAyman Bagabas
291ac9abebSAyman Bagabas /* Legacy GUIDs */
30bf4fb28cSAyman Bagabas #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
311ac9abebSAyman Bagabas #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
32bf4fb28cSAyman Bagabas
33090ddd77SAyman Bagabas /* HWMI commands */
34090ddd77SAyman Bagabas
35090ddd77SAyman Bagabas enum {
36090ddd77SAyman Bagabas BATTERY_THRESH_GET = 0x00001103, /* \GBTT */
37090ddd77SAyman Bagabas BATTERY_THRESH_SET = 0x00001003, /* \SBTT */
38090ddd77SAyman Bagabas FN_LOCK_GET = 0x00000604, /* \GFRS */
39090ddd77SAyman Bagabas FN_LOCK_SET = 0x00000704, /* \SFRS */
40090ddd77SAyman Bagabas MICMUTE_LED_SET = 0x00000b04, /* \SMLS */
41090ddd77SAyman Bagabas };
42090ddd77SAyman Bagabas
43090ddd77SAyman Bagabas union hwmi_arg {
44090ddd77SAyman Bagabas u64 cmd;
45090ddd77SAyman Bagabas u8 args[8];
46090ddd77SAyman Bagabas };
47090ddd77SAyman Bagabas
488a480c10SAyman Bagabas struct quirk_entry {
498a480c10SAyman Bagabas bool battery_reset;
508a480c10SAyman Bagabas bool ec_micmute;
518a480c10SAyman Bagabas bool report_brightness;
528a480c10SAyman Bagabas };
538a480c10SAyman Bagabas
548a480c10SAyman Bagabas static struct quirk_entry *quirks;
558a480c10SAyman Bagabas
5694930d01SAyman Bagabas struct huawei_wmi_debug {
5794930d01SAyman Bagabas struct dentry *root;
5894930d01SAyman Bagabas u64 arg;
5994930d01SAyman Bagabas };
6094930d01SAyman Bagabas
611ac9abebSAyman Bagabas struct huawei_wmi {
62355a070bSAyman Bagabas bool battery_available;
6332e59d11SAyman Bagabas bool fn_lock_available;
64355a070bSAyman Bagabas
6594930d01SAyman Bagabas struct huawei_wmi_debug debug;
66bf4fb28cSAyman Bagabas struct led_classdev cdev;
67c7a94976SAndy Shevchenko struct device *dev;
68090ddd77SAyman Bagabas
69090ddd77SAyman Bagabas struct mutex wmi_lock;
70bf4fb28cSAyman Bagabas };
71bf4fb28cSAyman Bagabas
729bfc14cbSkbuild test robot static struct huawei_wmi *huawei_wmi;
731ac9abebSAyman Bagabas
74bf4fb28cSAyman Bagabas static const struct key_entry huawei_wmi_keymap[] = {
75bf4fb28cSAyman Bagabas { KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } },
76bf4fb28cSAyman Bagabas { KE_KEY, 0x282, { KEY_BRIGHTNESSUP } },
77bf4fb28cSAyman Bagabas { KE_KEY, 0x284, { KEY_MUTE } },
78bf4fb28cSAyman Bagabas { KE_KEY, 0x285, { KEY_VOLUMEDOWN } },
79bf4fb28cSAyman Bagabas { KE_KEY, 0x286, { KEY_VOLUMEUP } },
80bf4fb28cSAyman Bagabas { KE_KEY, 0x287, { KEY_MICMUTE } },
81bf4fb28cSAyman Bagabas { KE_KEY, 0x289, { KEY_WLAN } },
82bf4fb28cSAyman Bagabas // Huawei |M| key
83bf4fb28cSAyman Bagabas { KE_KEY, 0x28a, { KEY_CONFIG } },
841ac9abebSAyman Bagabas // Keyboard backlit
85bf4fb28cSAyman Bagabas { KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
86bf4fb28cSAyman Bagabas { KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
87bf4fb28cSAyman Bagabas { KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },
88*c2173375SKonstantin Shelekhin // Ignore Ambient Light Sensoring
89*c2173375SKonstantin Shelekhin { KE_KEY, 0x2c1, { KEY_RESERVED } },
90bf4fb28cSAyman Bagabas { KE_END, 0 }
91bf4fb28cSAyman Bagabas };
92bf4fb28cSAyman Bagabas
938a480c10SAyman Bagabas static int battery_reset = -1;
948a480c10SAyman Bagabas static int report_brightness = -1;
958a480c10SAyman Bagabas
968a480c10SAyman Bagabas module_param(battery_reset, bint, 0444);
978a480c10SAyman Bagabas MODULE_PARM_DESC(battery_reset,
988a480c10SAyman Bagabas "Reset battery charge values to (0-0) before disabling it using (0-100)");
998a480c10SAyman Bagabas module_param(report_brightness, bint, 0444);
1008a480c10SAyman Bagabas MODULE_PARM_DESC(report_brightness,
1018a480c10SAyman Bagabas "Report brightness keys.");
1028a480c10SAyman Bagabas
1038a480c10SAyman Bagabas /* Quirks */
1048a480c10SAyman Bagabas
dmi_matched(const struct dmi_system_id * dmi)1058a480c10SAyman Bagabas static int __init dmi_matched(const struct dmi_system_id *dmi)
1068a480c10SAyman Bagabas {
1078a480c10SAyman Bagabas quirks = dmi->driver_data;
1088a480c10SAyman Bagabas return 1;
1098a480c10SAyman Bagabas }
1108a480c10SAyman Bagabas
1118a480c10SAyman Bagabas static struct quirk_entry quirk_unknown = {
1128a480c10SAyman Bagabas };
1138a480c10SAyman Bagabas
1148a480c10SAyman Bagabas static struct quirk_entry quirk_battery_reset = {
1158a480c10SAyman Bagabas .battery_reset = true,
1168a480c10SAyman Bagabas };
1178a480c10SAyman Bagabas
1188a480c10SAyman Bagabas static struct quirk_entry quirk_matebook_x = {
1198a480c10SAyman Bagabas .ec_micmute = true,
1208a480c10SAyman Bagabas .report_brightness = true,
1218a480c10SAyman Bagabas };
1228a480c10SAyman Bagabas
1238a480c10SAyman Bagabas static const struct dmi_system_id huawei_quirks[] = {
1248a480c10SAyman Bagabas {
1258a480c10SAyman Bagabas .callback = dmi_matched,
1268a480c10SAyman Bagabas .ident = "Huawei MACH-WX9",
1278a480c10SAyman Bagabas .matches = {
1288a480c10SAyman Bagabas DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
1298a480c10SAyman Bagabas DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"),
1308a480c10SAyman Bagabas },
1318a480c10SAyman Bagabas .driver_data = &quirk_battery_reset
1328a480c10SAyman Bagabas },
1338a480c10SAyman Bagabas {
1348a480c10SAyman Bagabas .callback = dmi_matched,
1358a480c10SAyman Bagabas .ident = "Huawei MateBook X",
1368a480c10SAyman Bagabas .matches = {
1378a480c10SAyman Bagabas DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
1388a480c10SAyman Bagabas DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X")
1398a480c10SAyman Bagabas },
1408a480c10SAyman Bagabas .driver_data = &quirk_matebook_x
1418a480c10SAyman Bagabas },
1428a480c10SAyman Bagabas { }
1438a480c10SAyman Bagabas };
1448a480c10SAyman Bagabas
145090ddd77SAyman Bagabas /* Utils */
146090ddd77SAyman Bagabas
huawei_wmi_call(struct huawei_wmi * huawei,struct acpi_buffer * in,struct acpi_buffer * out)1473c27f179SAndy Shevchenko static int huawei_wmi_call(struct huawei_wmi *huawei,
1483c27f179SAndy Shevchenko struct acpi_buffer *in, struct acpi_buffer *out)
149090ddd77SAyman Bagabas {
150090ddd77SAyman Bagabas acpi_status status;
151090ddd77SAyman Bagabas
1523c27f179SAndy Shevchenko mutex_lock(&huawei->wmi_lock);
153090ddd77SAyman Bagabas status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out);
1543c27f179SAndy Shevchenko mutex_unlock(&huawei->wmi_lock);
155090ddd77SAyman Bagabas if (ACPI_FAILURE(status)) {
1563c27f179SAndy Shevchenko dev_err(huawei->dev, "Failed to evaluate wmi method\n");
157090ddd77SAyman Bagabas return -ENODEV;
158090ddd77SAyman Bagabas }
159090ddd77SAyman Bagabas
160090ddd77SAyman Bagabas return 0;
161090ddd77SAyman Bagabas }
162090ddd77SAyman Bagabas
163090ddd77SAyman Bagabas /* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of
164090ddd77SAyman Bagabas * 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes.
165090ddd77SAyman Bagabas * The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a
166090ddd77SAyman Bagabas * package, or skip the first 4 if a buffer of 0x104 is used. The first byte of
167090ddd77SAyman Bagabas * the remaining 0x100 sized buffer has the return status of every call. In case
168090ddd77SAyman Bagabas * the return status is non-zero, we return -ENODEV but still copy the returned
169090ddd77SAyman Bagabas * buffer to the given buffer parameter (buf).
170090ddd77SAyman Bagabas */
huawei_wmi_cmd(u64 arg,u8 * buf,size_t buflen)171090ddd77SAyman Bagabas static int huawei_wmi_cmd(u64 arg, u8 *buf, size_t buflen)
172090ddd77SAyman Bagabas {
1733c27f179SAndy Shevchenko struct huawei_wmi *huawei = huawei_wmi;
174090ddd77SAyman Bagabas struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
175090ddd77SAyman Bagabas struct acpi_buffer in;
176090ddd77SAyman Bagabas union acpi_object *obj;
177090ddd77SAyman Bagabas size_t len;
178090ddd77SAyman Bagabas int err, i;
179090ddd77SAyman Bagabas
180090ddd77SAyman Bagabas in.length = sizeof(arg);
181090ddd77SAyman Bagabas in.pointer = &arg;
182090ddd77SAyman Bagabas
183090ddd77SAyman Bagabas /* Some models require calling HWMI twice to execute a command. We evaluate
184090ddd77SAyman Bagabas * HWMI and if we get a non-zero return status we evaluate it again.
185090ddd77SAyman Bagabas */
186090ddd77SAyman Bagabas for (i = 0; i < 2; i++) {
1873c27f179SAndy Shevchenko err = huawei_wmi_call(huawei, &in, &out);
188090ddd77SAyman Bagabas if (err)
189090ddd77SAyman Bagabas goto fail_cmd;
190090ddd77SAyman Bagabas
191090ddd77SAyman Bagabas obj = out.pointer;
192090ddd77SAyman Bagabas if (!obj) {
193090ddd77SAyman Bagabas err = -EIO;
194090ddd77SAyman Bagabas goto fail_cmd;
195090ddd77SAyman Bagabas }
196090ddd77SAyman Bagabas
197090ddd77SAyman Bagabas switch (obj->type) {
198090ddd77SAyman Bagabas /* Models that implement both "legacy" and HWMI tend to return a 0x104
199090ddd77SAyman Bagabas * sized buffer instead of a package of 0x4 and 0x100 buffers.
200090ddd77SAyman Bagabas */
201090ddd77SAyman Bagabas case ACPI_TYPE_BUFFER:
202090ddd77SAyman Bagabas if (obj->buffer.length == 0x104) {
203090ddd77SAyman Bagabas // Skip the first 4 bytes.
204090ddd77SAyman Bagabas obj->buffer.pointer += 4;
205090ddd77SAyman Bagabas len = 0x100;
206090ddd77SAyman Bagabas } else {
2073c27f179SAndy Shevchenko dev_err(huawei->dev, "Bad buffer length, got %d\n", obj->buffer.length);
208090ddd77SAyman Bagabas err = -EIO;
209090ddd77SAyman Bagabas goto fail_cmd;
210090ddd77SAyman Bagabas }
211090ddd77SAyman Bagabas
212090ddd77SAyman Bagabas break;
213090ddd77SAyman Bagabas /* HWMI returns a package with 2 buffer elements, one of 4 bytes and the
214090ddd77SAyman Bagabas * other is 256 bytes.
215090ddd77SAyman Bagabas */
216090ddd77SAyman Bagabas case ACPI_TYPE_PACKAGE:
217090ddd77SAyman Bagabas if (obj->package.count != 2) {
2183c27f179SAndy Shevchenko dev_err(huawei->dev, "Bad package count, got %d\n", obj->package.count);
219090ddd77SAyman Bagabas err = -EIO;
220090ddd77SAyman Bagabas goto fail_cmd;
221090ddd77SAyman Bagabas }
222090ddd77SAyman Bagabas
223090ddd77SAyman Bagabas obj = &obj->package.elements[1];
224090ddd77SAyman Bagabas if (obj->type != ACPI_TYPE_BUFFER) {
2253c27f179SAndy Shevchenko dev_err(huawei->dev, "Bad package element type, got %d\n", obj->type);
226090ddd77SAyman Bagabas err = -EIO;
227090ddd77SAyman Bagabas goto fail_cmd;
228090ddd77SAyman Bagabas }
229090ddd77SAyman Bagabas len = obj->buffer.length;
230090ddd77SAyman Bagabas
231090ddd77SAyman Bagabas break;
232090ddd77SAyman Bagabas /* Shouldn't get here! */
233090ddd77SAyman Bagabas default:
2343c27f179SAndy Shevchenko dev_err(huawei->dev, "Unexpected obj type, got: %d\n", obj->type);
235090ddd77SAyman Bagabas err = -EIO;
236090ddd77SAyman Bagabas goto fail_cmd;
237090ddd77SAyman Bagabas }
238090ddd77SAyman Bagabas
239090ddd77SAyman Bagabas if (!*obj->buffer.pointer)
240090ddd77SAyman Bagabas break;
241090ddd77SAyman Bagabas }
242090ddd77SAyman Bagabas
243090ddd77SAyman Bagabas err = (*obj->buffer.pointer) ? -ENODEV : 0;
244090ddd77SAyman Bagabas
245090ddd77SAyman Bagabas if (buf) {
246090ddd77SAyman Bagabas len = min(buflen, len);
247090ddd77SAyman Bagabas memcpy(buf, obj->buffer.pointer, len);
248090ddd77SAyman Bagabas }
249090ddd77SAyman Bagabas
250090ddd77SAyman Bagabas fail_cmd:
251090ddd77SAyman Bagabas kfree(out.pointer);
252090ddd77SAyman Bagabas return err;
253090ddd77SAyman Bagabas }
254090ddd77SAyman Bagabas
255090ddd77SAyman Bagabas /* LEDs */
256090ddd77SAyman Bagabas
huawei_wmi_micmute_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)257bf4fb28cSAyman Bagabas static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
258bf4fb28cSAyman Bagabas enum led_brightness brightness)
259bf4fb28cSAyman Bagabas {
260090ddd77SAyman Bagabas /* This is a workaround until the "legacy" interface is implemented. */
261090ddd77SAyman Bagabas if (quirks && quirks->ec_micmute) {
262090ddd77SAyman Bagabas char *acpi_method;
263090ddd77SAyman Bagabas acpi_handle handle;
264bf4fb28cSAyman Bagabas acpi_status status;
265bf4fb28cSAyman Bagabas union acpi_object args[3];
266bf4fb28cSAyman Bagabas struct acpi_object_list arg_list = {
267bf4fb28cSAyman Bagabas .pointer = args,
268bf4fb28cSAyman Bagabas .count = ARRAY_SIZE(args),
269bf4fb28cSAyman Bagabas };
270bf4fb28cSAyman Bagabas
271090ddd77SAyman Bagabas handle = ec_get_handle();
272090ddd77SAyman Bagabas if (!handle)
273090ddd77SAyman Bagabas return -ENODEV;
274090ddd77SAyman Bagabas
275bf4fb28cSAyman Bagabas args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
276bf4fb28cSAyman Bagabas args[1].integer.value = 0x04;
277bf4fb28cSAyman Bagabas
278090ddd77SAyman Bagabas if (acpi_has_method(handle, "SPIN")) {
279090ddd77SAyman Bagabas acpi_method = "SPIN";
280bf4fb28cSAyman Bagabas args[0].integer.value = 0;
281bf4fb28cSAyman Bagabas args[2].integer.value = brightness ? 1 : 0;
282090ddd77SAyman Bagabas } else if (acpi_has_method(handle, "WPIN")) {
283090ddd77SAyman Bagabas acpi_method = "WPIN";
284bf4fb28cSAyman Bagabas args[0].integer.value = 1;
285bf4fb28cSAyman Bagabas args[2].integer.value = brightness ? 0 : 1;
286bf4fb28cSAyman Bagabas } else {
287090ddd77SAyman Bagabas return -ENODEV;
288bf4fb28cSAyman Bagabas }
289bf4fb28cSAyman Bagabas
290090ddd77SAyman Bagabas status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
291bf4fb28cSAyman Bagabas if (ACPI_FAILURE(status))
292090ddd77SAyman Bagabas return -ENODEV;
293bf4fb28cSAyman Bagabas
294bf4fb28cSAyman Bagabas return 0;
295090ddd77SAyman Bagabas } else {
296090ddd77SAyman Bagabas union hwmi_arg arg;
297090ddd77SAyman Bagabas
298090ddd77SAyman Bagabas arg.cmd = MICMUTE_LED_SET;
299090ddd77SAyman Bagabas arg.args[2] = brightness;
300090ddd77SAyman Bagabas
301090ddd77SAyman Bagabas return huawei_wmi_cmd(arg.cmd, NULL, 0);
302090ddd77SAyman Bagabas }
303bf4fb28cSAyman Bagabas }
304bf4fb28cSAyman Bagabas
huawei_wmi_leds_setup(struct device * dev)3051ac9abebSAyman Bagabas static void huawei_wmi_leds_setup(struct device *dev)
306bf4fb28cSAyman Bagabas {
3071ac9abebSAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
308bf4fb28cSAyman Bagabas
3091ac9abebSAyman Bagabas huawei->cdev.name = "platform::micmute";
3101ac9abebSAyman Bagabas huawei->cdev.max_brightness = 1;
3111ac9abebSAyman Bagabas huawei->cdev.brightness_set_blocking = &huawei_wmi_micmute_led_set;
3121ac9abebSAyman Bagabas huawei->cdev.default_trigger = "audio-micmute";
3131ac9abebSAyman Bagabas huawei->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
3141ac9abebSAyman Bagabas huawei->cdev.dev = dev;
3151ac9abebSAyman Bagabas huawei->cdev.flags = LED_CORE_SUSPENDRESUME;
316bf4fb28cSAyman Bagabas
3171ac9abebSAyman Bagabas devm_led_classdev_register(dev, &huawei->cdev);
318bf4fb28cSAyman Bagabas }
319bf4fb28cSAyman Bagabas
320355a070bSAyman Bagabas /* Battery protection */
321355a070bSAyman Bagabas
huawei_wmi_battery_get(int * start,int * end)322355a070bSAyman Bagabas static int huawei_wmi_battery_get(int *start, int *end)
323355a070bSAyman Bagabas {
324355a070bSAyman Bagabas u8 ret[0x100];
325355a070bSAyman Bagabas int err, i;
326355a070bSAyman Bagabas
327d6fef932SBarnabás Pőcze err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, sizeof(ret));
328355a070bSAyman Bagabas if (err)
329355a070bSAyman Bagabas return err;
330355a070bSAyman Bagabas
331355a070bSAyman Bagabas /* Find the last two non-zero values. Return status is ignored. */
332d6fef932SBarnabás Pőcze i = ARRAY_SIZE(ret) - 1;
333355a070bSAyman Bagabas do {
334355a070bSAyman Bagabas if (start)
335355a070bSAyman Bagabas *start = ret[i-1];
336355a070bSAyman Bagabas if (end)
337355a070bSAyman Bagabas *end = ret[i];
338355a070bSAyman Bagabas } while (i > 2 && !ret[i--]);
339355a070bSAyman Bagabas
340355a070bSAyman Bagabas return 0;
341355a070bSAyman Bagabas }
342355a070bSAyman Bagabas
huawei_wmi_battery_set(int start,int end)343355a070bSAyman Bagabas static int huawei_wmi_battery_set(int start, int end)
344355a070bSAyman Bagabas {
345355a070bSAyman Bagabas union hwmi_arg arg;
346355a070bSAyman Bagabas int err;
347355a070bSAyman Bagabas
3487c675486SAyman Bagabas if (start < 0 || end < 0 || start > 100 || end > 100)
349355a070bSAyman Bagabas return -EINVAL;
350355a070bSAyman Bagabas
351355a070bSAyman Bagabas arg.cmd = BATTERY_THRESH_SET;
352355a070bSAyman Bagabas arg.args[2] = start;
353355a070bSAyman Bagabas arg.args[3] = end;
354355a070bSAyman Bagabas
355355a070bSAyman Bagabas /* This is an edge case were some models turn battery protection
356355a070bSAyman Bagabas * off without changing their thresholds values. We clear the
357355a070bSAyman Bagabas * values before turning off protection. Sometimes we need a sleep delay to
358355a070bSAyman Bagabas * make sure these values make their way to EC memory.
359355a070bSAyman Bagabas */
360355a070bSAyman Bagabas if (quirks && quirks->battery_reset && start == 0 && end == 100) {
361355a070bSAyman Bagabas err = huawei_wmi_battery_set(0, 0);
362355a070bSAyman Bagabas if (err)
363355a070bSAyman Bagabas return err;
364355a070bSAyman Bagabas
365355a070bSAyman Bagabas msleep(1000);
366355a070bSAyman Bagabas }
367355a070bSAyman Bagabas
368355a070bSAyman Bagabas err = huawei_wmi_cmd(arg.cmd, NULL, 0);
369355a070bSAyman Bagabas
370355a070bSAyman Bagabas return err;
371355a070bSAyman Bagabas }
372355a070bSAyman Bagabas
charge_control_start_threshold_show(struct device * dev,struct device_attribute * attr,char * buf)373355a070bSAyman Bagabas static ssize_t charge_control_start_threshold_show(struct device *dev,
374355a070bSAyman Bagabas struct device_attribute *attr,
375355a070bSAyman Bagabas char *buf)
376355a070bSAyman Bagabas {
377355a070bSAyman Bagabas int err, start;
378355a070bSAyman Bagabas
379355a070bSAyman Bagabas err = huawei_wmi_battery_get(&start, NULL);
380355a070bSAyman Bagabas if (err)
381355a070bSAyman Bagabas return err;
382355a070bSAyman Bagabas
383355a070bSAyman Bagabas return sprintf(buf, "%d\n", start);
384355a070bSAyman Bagabas }
385355a070bSAyman Bagabas
charge_control_end_threshold_show(struct device * dev,struct device_attribute * attr,char * buf)386355a070bSAyman Bagabas static ssize_t charge_control_end_threshold_show(struct device *dev,
387355a070bSAyman Bagabas struct device_attribute *attr,
388355a070bSAyman Bagabas char *buf)
389355a070bSAyman Bagabas {
390355a070bSAyman Bagabas int err, end;
391355a070bSAyman Bagabas
392355a070bSAyman Bagabas err = huawei_wmi_battery_get(NULL, &end);
393355a070bSAyman Bagabas if (err)
394355a070bSAyman Bagabas return err;
395355a070bSAyman Bagabas
396355a070bSAyman Bagabas return sprintf(buf, "%d\n", end);
397355a070bSAyman Bagabas }
398355a070bSAyman Bagabas
charge_control_thresholds_show(struct device * dev,struct device_attribute * attr,char * buf)399355a070bSAyman Bagabas static ssize_t charge_control_thresholds_show(struct device *dev,
400355a070bSAyman Bagabas struct device_attribute *attr,
401355a070bSAyman Bagabas char *buf)
402355a070bSAyman Bagabas {
403355a070bSAyman Bagabas int err, start, end;
404355a070bSAyman Bagabas
405355a070bSAyman Bagabas err = huawei_wmi_battery_get(&start, &end);
406355a070bSAyman Bagabas if (err)
407355a070bSAyman Bagabas return err;
408355a070bSAyman Bagabas
409355a070bSAyman Bagabas return sprintf(buf, "%d %d\n", start, end);
410355a070bSAyman Bagabas }
411355a070bSAyman Bagabas
charge_control_start_threshold_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)412355a070bSAyman Bagabas static ssize_t charge_control_start_threshold_store(struct device *dev,
413355a070bSAyman Bagabas struct device_attribute *attr,
414355a070bSAyman Bagabas const char *buf, size_t size)
415355a070bSAyman Bagabas {
416355a070bSAyman Bagabas int err, start, end;
417355a070bSAyman Bagabas
418355a070bSAyman Bagabas err = huawei_wmi_battery_get(NULL, &end);
419355a070bSAyman Bagabas if (err)
420355a070bSAyman Bagabas return err;
421355a070bSAyman Bagabas
422355a070bSAyman Bagabas if (sscanf(buf, "%d", &start) != 1)
423355a070bSAyman Bagabas return -EINVAL;
424355a070bSAyman Bagabas
425355a070bSAyman Bagabas err = huawei_wmi_battery_set(start, end);
426355a070bSAyman Bagabas if (err)
427355a070bSAyman Bagabas return err;
428355a070bSAyman Bagabas
429355a070bSAyman Bagabas return size;
430355a070bSAyman Bagabas }
431355a070bSAyman Bagabas
charge_control_end_threshold_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)432355a070bSAyman Bagabas static ssize_t charge_control_end_threshold_store(struct device *dev,
433355a070bSAyman Bagabas struct device_attribute *attr,
434355a070bSAyman Bagabas const char *buf, size_t size)
435355a070bSAyman Bagabas {
436355a070bSAyman Bagabas int err, start, end;
437355a070bSAyman Bagabas
438355a070bSAyman Bagabas err = huawei_wmi_battery_get(&start, NULL);
439355a070bSAyman Bagabas if (err)
440355a070bSAyman Bagabas return err;
441355a070bSAyman Bagabas
442355a070bSAyman Bagabas if (sscanf(buf, "%d", &end) != 1)
443355a070bSAyman Bagabas return -EINVAL;
444355a070bSAyman Bagabas
445355a070bSAyman Bagabas err = huawei_wmi_battery_set(start, end);
446355a070bSAyman Bagabas if (err)
447355a070bSAyman Bagabas return err;
448355a070bSAyman Bagabas
449355a070bSAyman Bagabas return size;
450355a070bSAyman Bagabas }
451355a070bSAyman Bagabas
charge_control_thresholds_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)452355a070bSAyman Bagabas static ssize_t charge_control_thresholds_store(struct device *dev,
453355a070bSAyman Bagabas struct device_attribute *attr,
454355a070bSAyman Bagabas const char *buf, size_t size)
455355a070bSAyman Bagabas {
456355a070bSAyman Bagabas int err, start, end;
457355a070bSAyman Bagabas
458355a070bSAyman Bagabas if (sscanf(buf, "%d %d", &start, &end) != 2)
459355a070bSAyman Bagabas return -EINVAL;
460355a070bSAyman Bagabas
461355a070bSAyman Bagabas err = huawei_wmi_battery_set(start, end);
462355a070bSAyman Bagabas if (err)
463355a070bSAyman Bagabas return err;
464355a070bSAyman Bagabas
465355a070bSAyman Bagabas return size;
466355a070bSAyman Bagabas }
467355a070bSAyman Bagabas
468355a070bSAyman Bagabas static DEVICE_ATTR_RW(charge_control_start_threshold);
469355a070bSAyman Bagabas static DEVICE_ATTR_RW(charge_control_end_threshold);
470355a070bSAyman Bagabas static DEVICE_ATTR_RW(charge_control_thresholds);
471355a070bSAyman Bagabas
huawei_wmi_battery_add(struct power_supply * battery,struct acpi_battery_hook * hook)472878a82c2SArmin Wolf static int huawei_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
473355a070bSAyman Bagabas {
474c91a5b1cSJia-Ju Bai int err = 0;
475355a070bSAyman Bagabas
476c91a5b1cSJia-Ju Bai err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
477c91a5b1cSJia-Ju Bai if (err)
478c91a5b1cSJia-Ju Bai return err;
479c91a5b1cSJia-Ju Bai
480c91a5b1cSJia-Ju Bai err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
481c91a5b1cSJia-Ju Bai if (err)
482c91a5b1cSJia-Ju Bai device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
483c91a5b1cSJia-Ju Bai
484c91a5b1cSJia-Ju Bai return err;
485355a070bSAyman Bagabas }
486355a070bSAyman Bagabas
huawei_wmi_battery_remove(struct power_supply * battery,struct acpi_battery_hook * hook)487878a82c2SArmin Wolf static int huawei_wmi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
488355a070bSAyman Bagabas {
489355a070bSAyman Bagabas device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
490355a070bSAyman Bagabas device_remove_file(&battery->dev, &dev_attr_charge_control_end_threshold);
491355a070bSAyman Bagabas
492355a070bSAyman Bagabas return 0;
493355a070bSAyman Bagabas }
494355a070bSAyman Bagabas
495355a070bSAyman Bagabas static struct acpi_battery_hook huawei_wmi_battery_hook = {
496355a070bSAyman Bagabas .add_battery = huawei_wmi_battery_add,
497355a070bSAyman Bagabas .remove_battery = huawei_wmi_battery_remove,
498355a070bSAyman Bagabas .name = "Huawei Battery Extension"
499355a070bSAyman Bagabas };
500355a070bSAyman Bagabas
huawei_wmi_battery_setup(struct device * dev)501355a070bSAyman Bagabas static void huawei_wmi_battery_setup(struct device *dev)
502355a070bSAyman Bagabas {
503355a070bSAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
504355a070bSAyman Bagabas
505355a070bSAyman Bagabas huawei->battery_available = true;
506355a070bSAyman Bagabas if (huawei_wmi_battery_get(NULL, NULL)) {
507355a070bSAyman Bagabas huawei->battery_available = false;
508355a070bSAyman Bagabas return;
509355a070bSAyman Bagabas }
510355a070bSAyman Bagabas
511355a070bSAyman Bagabas battery_hook_register(&huawei_wmi_battery_hook);
512355a070bSAyman Bagabas device_create_file(dev, &dev_attr_charge_control_thresholds);
513355a070bSAyman Bagabas }
514355a070bSAyman Bagabas
huawei_wmi_battery_exit(struct device * dev)515355a070bSAyman Bagabas static void huawei_wmi_battery_exit(struct device *dev)
516355a070bSAyman Bagabas {
517355a070bSAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
518355a070bSAyman Bagabas
519355a070bSAyman Bagabas if (huawei->battery_available) {
520355a070bSAyman Bagabas battery_hook_unregister(&huawei_wmi_battery_hook);
521355a070bSAyman Bagabas device_remove_file(dev, &dev_attr_charge_control_thresholds);
522355a070bSAyman Bagabas }
523355a070bSAyman Bagabas }
524355a070bSAyman Bagabas
52532e59d11SAyman Bagabas /* Fn lock */
52632e59d11SAyman Bagabas
huawei_wmi_fn_lock_get(int * on)52732e59d11SAyman Bagabas static int huawei_wmi_fn_lock_get(int *on)
52832e59d11SAyman Bagabas {
52932e59d11SAyman Bagabas u8 ret[0x100] = { 0 };
53032e59d11SAyman Bagabas int err, i;
53132e59d11SAyman Bagabas
53232e59d11SAyman Bagabas err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);
53332e59d11SAyman Bagabas if (err)
53432e59d11SAyman Bagabas return err;
53532e59d11SAyman Bagabas
53632e59d11SAyman Bagabas /* Find the first non-zero value. Return status is ignored. */
53732e59d11SAyman Bagabas i = 1;
53832e59d11SAyman Bagabas do {
53932e59d11SAyman Bagabas if (on)
54032e59d11SAyman Bagabas *on = ret[i] - 1; // -1 undefined, 0 off, 1 on.
54132e59d11SAyman Bagabas } while (i < 0xff && !ret[i++]);
54232e59d11SAyman Bagabas
54332e59d11SAyman Bagabas return 0;
54432e59d11SAyman Bagabas }
54532e59d11SAyman Bagabas
huawei_wmi_fn_lock_set(int on)54632e59d11SAyman Bagabas static int huawei_wmi_fn_lock_set(int on)
54732e59d11SAyman Bagabas {
54832e59d11SAyman Bagabas union hwmi_arg arg;
54932e59d11SAyman Bagabas
55032e59d11SAyman Bagabas arg.cmd = FN_LOCK_SET;
55132e59d11SAyman Bagabas arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.
55232e59d11SAyman Bagabas
55332e59d11SAyman Bagabas return huawei_wmi_cmd(arg.cmd, NULL, 0);
55432e59d11SAyman Bagabas }
55532e59d11SAyman Bagabas
fn_lock_state_show(struct device * dev,struct device_attribute * attr,char * buf)55632e59d11SAyman Bagabas static ssize_t fn_lock_state_show(struct device *dev,
55732e59d11SAyman Bagabas struct device_attribute *attr,
55832e59d11SAyman Bagabas char *buf)
55932e59d11SAyman Bagabas {
56032e59d11SAyman Bagabas int err, on;
56132e59d11SAyman Bagabas
56232e59d11SAyman Bagabas err = huawei_wmi_fn_lock_get(&on);
56332e59d11SAyman Bagabas if (err)
56432e59d11SAyman Bagabas return err;
56532e59d11SAyman Bagabas
56632e59d11SAyman Bagabas return sprintf(buf, "%d\n", on);
56732e59d11SAyman Bagabas }
56832e59d11SAyman Bagabas
fn_lock_state_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)56932e59d11SAyman Bagabas static ssize_t fn_lock_state_store(struct device *dev,
57032e59d11SAyman Bagabas struct device_attribute *attr,
57132e59d11SAyman Bagabas const char *buf, size_t size)
57232e59d11SAyman Bagabas {
57332e59d11SAyman Bagabas int on, err;
57432e59d11SAyman Bagabas
57532e59d11SAyman Bagabas if (kstrtoint(buf, 10, &on) ||
57632e59d11SAyman Bagabas on < 0 || on > 1)
57732e59d11SAyman Bagabas return -EINVAL;
57832e59d11SAyman Bagabas
57932e59d11SAyman Bagabas err = huawei_wmi_fn_lock_set(on);
58032e59d11SAyman Bagabas if (err)
58132e59d11SAyman Bagabas return err;
58232e59d11SAyman Bagabas
58332e59d11SAyman Bagabas return size;
58432e59d11SAyman Bagabas }
58532e59d11SAyman Bagabas
58632e59d11SAyman Bagabas static DEVICE_ATTR_RW(fn_lock_state);
58732e59d11SAyman Bagabas
huawei_wmi_fn_lock_setup(struct device * dev)58832e59d11SAyman Bagabas static void huawei_wmi_fn_lock_setup(struct device *dev)
58932e59d11SAyman Bagabas {
59032e59d11SAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
59132e59d11SAyman Bagabas
59232e59d11SAyman Bagabas huawei->fn_lock_available = true;
59332e59d11SAyman Bagabas if (huawei_wmi_fn_lock_get(NULL)) {
59432e59d11SAyman Bagabas huawei->fn_lock_available = false;
59532e59d11SAyman Bagabas return;
59632e59d11SAyman Bagabas }
59732e59d11SAyman Bagabas
59832e59d11SAyman Bagabas device_create_file(dev, &dev_attr_fn_lock_state);
59932e59d11SAyman Bagabas }
60032e59d11SAyman Bagabas
huawei_wmi_fn_lock_exit(struct device * dev)60132e59d11SAyman Bagabas static void huawei_wmi_fn_lock_exit(struct device *dev)
60232e59d11SAyman Bagabas {
6033c27f179SAndy Shevchenko struct huawei_wmi *huawei = dev_get_drvdata(dev);
6043c27f179SAndy Shevchenko
6053c27f179SAndy Shevchenko if (huawei->fn_lock_available)
60632e59d11SAyman Bagabas device_remove_file(dev, &dev_attr_fn_lock_state);
60732e59d11SAyman Bagabas }
60832e59d11SAyman Bagabas
60994930d01SAyman Bagabas /* debugfs */
61094930d01SAyman Bagabas
huawei_wmi_debugfs_call_dump(struct seq_file * m,void * data,union acpi_object * obj)61194930d01SAyman Bagabas static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data,
61294930d01SAyman Bagabas union acpi_object *obj)
61394930d01SAyman Bagabas {
61494930d01SAyman Bagabas struct huawei_wmi *huawei = m->private;
61594930d01SAyman Bagabas int i;
61694930d01SAyman Bagabas
61794930d01SAyman Bagabas switch (obj->type) {
61894930d01SAyman Bagabas case ACPI_TYPE_INTEGER:
61994930d01SAyman Bagabas seq_printf(m, "0x%llx", obj->integer.value);
62094930d01SAyman Bagabas break;
62194930d01SAyman Bagabas case ACPI_TYPE_STRING:
6222a43c9fcSDan Carpenter seq_printf(m, "\"%.*s\"", obj->string.length, obj->string.pointer);
62394930d01SAyman Bagabas break;
62494930d01SAyman Bagabas case ACPI_TYPE_BUFFER:
62594930d01SAyman Bagabas seq_puts(m, "{");
62694930d01SAyman Bagabas for (i = 0; i < obj->buffer.length; i++) {
62794930d01SAyman Bagabas seq_printf(m, "0x%02x", obj->buffer.pointer[i]);
62894930d01SAyman Bagabas if (i < obj->buffer.length - 1)
62994930d01SAyman Bagabas seq_puts(m, ",");
63094930d01SAyman Bagabas }
63194930d01SAyman Bagabas seq_puts(m, "}");
63294930d01SAyman Bagabas break;
63394930d01SAyman Bagabas case ACPI_TYPE_PACKAGE:
63494930d01SAyman Bagabas seq_puts(m, "[");
63594930d01SAyman Bagabas for (i = 0; i < obj->package.count; i++) {
63694930d01SAyman Bagabas huawei_wmi_debugfs_call_dump(m, huawei, &obj->package.elements[i]);
63794930d01SAyman Bagabas if (i < obj->package.count - 1)
63894930d01SAyman Bagabas seq_puts(m, ",");
63994930d01SAyman Bagabas }
64094930d01SAyman Bagabas seq_puts(m, "]");
64194930d01SAyman Bagabas break;
64294930d01SAyman Bagabas default:
643c7a94976SAndy Shevchenko dev_err(huawei->dev, "Unexpected obj type, got %d\n", obj->type);
64494930d01SAyman Bagabas return;
64594930d01SAyman Bagabas }
64694930d01SAyman Bagabas }
64794930d01SAyman Bagabas
huawei_wmi_debugfs_call_show(struct seq_file * m,void * data)64894930d01SAyman Bagabas static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data)
64994930d01SAyman Bagabas {
65094930d01SAyman Bagabas struct huawei_wmi *huawei = m->private;
65194930d01SAyman Bagabas struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
65294930d01SAyman Bagabas struct acpi_buffer in;
65394930d01SAyman Bagabas union acpi_object *obj;
65494930d01SAyman Bagabas int err;
65594930d01SAyman Bagabas
65694930d01SAyman Bagabas in.length = sizeof(u64);
65794930d01SAyman Bagabas in.pointer = &huawei->debug.arg;
65894930d01SAyman Bagabas
6593c27f179SAndy Shevchenko err = huawei_wmi_call(huawei, &in, &out);
66094930d01SAyman Bagabas if (err)
66194930d01SAyman Bagabas return err;
66294930d01SAyman Bagabas
66394930d01SAyman Bagabas obj = out.pointer;
66494930d01SAyman Bagabas if (!obj) {
66594930d01SAyman Bagabas err = -EIO;
66694930d01SAyman Bagabas goto fail_debugfs_call;
66794930d01SAyman Bagabas }
66894930d01SAyman Bagabas
66994930d01SAyman Bagabas huawei_wmi_debugfs_call_dump(m, huawei, obj);
67094930d01SAyman Bagabas
67194930d01SAyman Bagabas fail_debugfs_call:
67294930d01SAyman Bagabas kfree(out.pointer);
67394930d01SAyman Bagabas return err;
67494930d01SAyman Bagabas }
67594930d01SAyman Bagabas
67694930d01SAyman Bagabas DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call);
67794930d01SAyman Bagabas
huawei_wmi_debugfs_setup(struct device * dev)67894930d01SAyman Bagabas static void huawei_wmi_debugfs_setup(struct device *dev)
67994930d01SAyman Bagabas {
68094930d01SAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
68194930d01SAyman Bagabas
68294930d01SAyman Bagabas huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL);
68394930d01SAyman Bagabas
68494930d01SAyman Bagabas debugfs_create_x64("arg", 0644, huawei->debug.root,
68594930d01SAyman Bagabas &huawei->debug.arg);
68694930d01SAyman Bagabas debugfs_create_file("call", 0400,
68794930d01SAyman Bagabas huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops);
68894930d01SAyman Bagabas }
68994930d01SAyman Bagabas
huawei_wmi_debugfs_exit(struct device * dev)69094930d01SAyman Bagabas static void huawei_wmi_debugfs_exit(struct device *dev)
69194930d01SAyman Bagabas {
69294930d01SAyman Bagabas struct huawei_wmi *huawei = dev_get_drvdata(dev);
69394930d01SAyman Bagabas
69494930d01SAyman Bagabas debugfs_remove_recursive(huawei->debug.root);
69594930d01SAyman Bagabas }
69694930d01SAyman Bagabas
6971ac9abebSAyman Bagabas /* Input */
6981ac9abebSAyman Bagabas
huawei_wmi_process_key(struct input_dev * idev,int code)6991ac9abebSAyman Bagabas static void huawei_wmi_process_key(struct input_dev *idev, int code)
700bf4fb28cSAyman Bagabas {
701bf4fb28cSAyman Bagabas const struct key_entry *key;
702bf4fb28cSAyman Bagabas
703bf4fb28cSAyman Bagabas /*
704bf4fb28cSAyman Bagabas * WMI0 uses code 0x80 to indicate a hotkey event.
705bf4fb28cSAyman Bagabas * The actual key is fetched from the method WQ00
706bf4fb28cSAyman Bagabas * using WMI0_EXPENSIVE_GUID.
707bf4fb28cSAyman Bagabas */
708bf4fb28cSAyman Bagabas if (code == 0x80) {
709bf4fb28cSAyman Bagabas struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
710bf4fb28cSAyman Bagabas union acpi_object *obj;
711bf4fb28cSAyman Bagabas acpi_status status;
712bf4fb28cSAyman Bagabas
713bf4fb28cSAyman Bagabas status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response);
714bf4fb28cSAyman Bagabas if (ACPI_FAILURE(status))
715bf4fb28cSAyman Bagabas return;
716bf4fb28cSAyman Bagabas
717bf4fb28cSAyman Bagabas obj = (union acpi_object *)response.pointer;
718bf4fb28cSAyman Bagabas if (obj && obj->type == ACPI_TYPE_INTEGER)
719bf4fb28cSAyman Bagabas code = obj->integer.value;
720bf4fb28cSAyman Bagabas
721bf4fb28cSAyman Bagabas kfree(response.pointer);
722bf4fb28cSAyman Bagabas }
723bf4fb28cSAyman Bagabas
7241ac9abebSAyman Bagabas key = sparse_keymap_entry_from_scancode(idev, code);
725bf4fb28cSAyman Bagabas if (!key) {
7261ac9abebSAyman Bagabas dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code);
727bf4fb28cSAyman Bagabas return;
728bf4fb28cSAyman Bagabas }
729bf4fb28cSAyman Bagabas
7308a480c10SAyman Bagabas if (quirks && !quirks->report_brightness &&
7318a480c10SAyman Bagabas (key->sw.code == KEY_BRIGHTNESSDOWN ||
7328a480c10SAyman Bagabas key->sw.code == KEY_BRIGHTNESSUP))
7338a480c10SAyman Bagabas return;
7348a480c10SAyman Bagabas
7351ac9abebSAyman Bagabas sparse_keymap_report_entry(idev, key, 1, true);
736bf4fb28cSAyman Bagabas }
737bf4fb28cSAyman Bagabas
huawei_wmi_input_notify(u32 value,void * context)7381ac9abebSAyman Bagabas static void huawei_wmi_input_notify(u32 value, void *context)
739bf4fb28cSAyman Bagabas {
7401ac9abebSAyman Bagabas struct input_dev *idev = (struct input_dev *)context;
7411ac9abebSAyman Bagabas struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
7421ac9abebSAyman Bagabas union acpi_object *obj;
7431ac9abebSAyman Bagabas acpi_status status;
7441ac9abebSAyman Bagabas
7451ac9abebSAyman Bagabas status = wmi_get_event_data(value, &response);
7461ac9abebSAyman Bagabas if (ACPI_FAILURE(status)) {
7471ac9abebSAyman Bagabas dev_err(&idev->dev, "Unable to get event data\n");
7481ac9abebSAyman Bagabas return;
7491ac9abebSAyman Bagabas }
7501ac9abebSAyman Bagabas
7511ac9abebSAyman Bagabas obj = (union acpi_object *)response.pointer;
7521ac9abebSAyman Bagabas if (obj && obj->type == ACPI_TYPE_INTEGER)
7531ac9abebSAyman Bagabas huawei_wmi_process_key(idev, obj->integer.value);
754bf4fb28cSAyman Bagabas else
7551ac9abebSAyman Bagabas dev_err(&idev->dev, "Bad response type\n");
7561ac9abebSAyman Bagabas
7571ac9abebSAyman Bagabas kfree(response.pointer);
758bf4fb28cSAyman Bagabas }
759bf4fb28cSAyman Bagabas
huawei_wmi_input_setup(struct device * dev,const char * guid)760e57d58eeSBarnabás Pőcze static int huawei_wmi_input_setup(struct device *dev, const char *guid)
761bf4fb28cSAyman Bagabas {
762e57d58eeSBarnabás Pőcze struct input_dev *idev;
7630b9a1dcdSBarnabás Pőcze acpi_status status;
7640b9a1dcdSBarnabás Pőcze int err;
7650b9a1dcdSBarnabás Pőcze
766e57d58eeSBarnabás Pőcze idev = devm_input_allocate_device(dev);
767e57d58eeSBarnabás Pőcze if (!idev)
768bf4fb28cSAyman Bagabas return -ENOMEM;
769bf4fb28cSAyman Bagabas
770e57d58eeSBarnabás Pőcze idev->name = "Huawei WMI hotkeys";
771e57d58eeSBarnabás Pőcze idev->phys = "wmi/input0";
772e57d58eeSBarnabás Pőcze idev->id.bustype = BUS_HOST;
773e57d58eeSBarnabás Pőcze idev->dev.parent = dev;
774bf4fb28cSAyman Bagabas
775e57d58eeSBarnabás Pőcze err = sparse_keymap_setup(idev, huawei_wmi_keymap, NULL);
7760b9a1dcdSBarnabás Pőcze if (err)
7770b9a1dcdSBarnabás Pőcze return err;
7780b9a1dcdSBarnabás Pőcze
779e57d58eeSBarnabás Pőcze err = input_register_device(idev);
7800b9a1dcdSBarnabás Pőcze if (err)
7810b9a1dcdSBarnabás Pőcze return err;
7820b9a1dcdSBarnabás Pőcze
783e57d58eeSBarnabás Pőcze status = wmi_install_notify_handler(guid, huawei_wmi_input_notify, idev);
7840b9a1dcdSBarnabás Pőcze if (ACPI_FAILURE(status))
7850b9a1dcdSBarnabás Pőcze return -EIO;
7860b9a1dcdSBarnabás Pőcze
7870b9a1dcdSBarnabás Pőcze return 0;
788bf4fb28cSAyman Bagabas }
789bf4fb28cSAyman Bagabas
huawei_wmi_input_exit(struct device * dev,const char * guid)7901ac9abebSAyman Bagabas static void huawei_wmi_input_exit(struct device *dev, const char *guid)
791bf4fb28cSAyman Bagabas {
7921ac9abebSAyman Bagabas wmi_remove_notify_handler(guid);
793bf4fb28cSAyman Bagabas }
794bf4fb28cSAyman Bagabas
7951ac9abebSAyman Bagabas /* Huawei driver */
7961ac9abebSAyman Bagabas
7971ac9abebSAyman Bagabas static const struct wmi_device_id huawei_wmi_events_id_table[] = {
798bf4fb28cSAyman Bagabas { .guid_string = WMI0_EVENT_GUID },
799090ddd77SAyman Bagabas { .guid_string = HWMI_EVENT_GUID },
800bf4fb28cSAyman Bagabas { }
801bf4fb28cSAyman Bagabas };
802bf4fb28cSAyman Bagabas
huawei_wmi_probe(struct platform_device * pdev)8031ac9abebSAyman Bagabas static int huawei_wmi_probe(struct platform_device *pdev)
8041ac9abebSAyman Bagabas {
8051ac9abebSAyman Bagabas const struct wmi_device_id *guid = huawei_wmi_events_id_table;
8061ac9abebSAyman Bagabas int err;
8071ac9abebSAyman Bagabas
8081ac9abebSAyman Bagabas platform_set_drvdata(pdev, huawei_wmi);
809c7a94976SAndy Shevchenko huawei_wmi->dev = &pdev->dev;
8101ac9abebSAyman Bagabas
8111ac9abebSAyman Bagabas while (*guid->guid_string) {
8121ac9abebSAyman Bagabas if (wmi_has_guid(guid->guid_string)) {
813e57d58eeSBarnabás Pőcze err = huawei_wmi_input_setup(&pdev->dev, guid->guid_string);
8141ac9abebSAyman Bagabas if (err) {
8151ac9abebSAyman Bagabas dev_err(&pdev->dev, "Failed to setup input on %s\n", guid->guid_string);
8161ac9abebSAyman Bagabas return err;
8171ac9abebSAyman Bagabas }
8181ac9abebSAyman Bagabas }
8191ac9abebSAyman Bagabas
8201ac9abebSAyman Bagabas guid++;
8211ac9abebSAyman Bagabas }
8221ac9abebSAyman Bagabas
823090ddd77SAyman Bagabas if (wmi_has_guid(HWMI_METHOD_GUID)) {
824090ddd77SAyman Bagabas mutex_init(&huawei_wmi->wmi_lock);
825090ddd77SAyman Bagabas
8261ac9abebSAyman Bagabas huawei_wmi_leds_setup(&pdev->dev);
82732e59d11SAyman Bagabas huawei_wmi_fn_lock_setup(&pdev->dev);
828355a070bSAyman Bagabas huawei_wmi_battery_setup(&pdev->dev);
82994930d01SAyman Bagabas huawei_wmi_debugfs_setup(&pdev->dev);
830090ddd77SAyman Bagabas }
831090ddd77SAyman Bagabas
8321ac9abebSAyman Bagabas return 0;
8331ac9abebSAyman Bagabas }
8341ac9abebSAyman Bagabas
huawei_wmi_remove(struct platform_device * pdev)8357836f70aSUwe Kleine-König static void huawei_wmi_remove(struct platform_device *pdev)
8361ac9abebSAyman Bagabas {
8371ac9abebSAyman Bagabas const struct wmi_device_id *guid = huawei_wmi_events_id_table;
8381ac9abebSAyman Bagabas
8391ac9abebSAyman Bagabas while (*guid->guid_string) {
8401ac9abebSAyman Bagabas if (wmi_has_guid(guid->guid_string))
8411ac9abebSAyman Bagabas huawei_wmi_input_exit(&pdev->dev, guid->guid_string);
8421ac9abebSAyman Bagabas
8431ac9abebSAyman Bagabas guid++;
8441ac9abebSAyman Bagabas }
8451ac9abebSAyman Bagabas
846090ddd77SAyman Bagabas if (wmi_has_guid(HWMI_METHOD_GUID)) {
84794930d01SAyman Bagabas huawei_wmi_debugfs_exit(&pdev->dev);
848355a070bSAyman Bagabas huawei_wmi_battery_exit(&pdev->dev);
84932e59d11SAyman Bagabas huawei_wmi_fn_lock_exit(&pdev->dev);
850090ddd77SAyman Bagabas }
8511ac9abebSAyman Bagabas }
8521ac9abebSAyman Bagabas
8531ac9abebSAyman Bagabas static struct platform_driver huawei_wmi_driver = {
854bf4fb28cSAyman Bagabas .driver = {
855bf4fb28cSAyman Bagabas .name = "huawei-wmi",
856bf4fb28cSAyman Bagabas },
857bf4fb28cSAyman Bagabas .probe = huawei_wmi_probe,
8587836f70aSUwe Kleine-König .remove_new = huawei_wmi_remove,
859bf4fb28cSAyman Bagabas };
860bf4fb28cSAyman Bagabas
huawei_wmi_init(void)8611ac9abebSAyman Bagabas static __init int huawei_wmi_init(void)
8621ac9abebSAyman Bagabas {
8631ac9abebSAyman Bagabas struct platform_device *pdev;
8641ac9abebSAyman Bagabas int err;
865bf4fb28cSAyman Bagabas
8661ac9abebSAyman Bagabas huawei_wmi = kzalloc(sizeof(struct huawei_wmi), GFP_KERNEL);
8671ac9abebSAyman Bagabas if (!huawei_wmi)
8681ac9abebSAyman Bagabas return -ENOMEM;
8691ac9abebSAyman Bagabas
8708a480c10SAyman Bagabas quirks = &quirk_unknown;
8718a480c10SAyman Bagabas dmi_check_system(huawei_quirks);
8728a480c10SAyman Bagabas if (battery_reset != -1)
8738a480c10SAyman Bagabas quirks->battery_reset = battery_reset;
8748a480c10SAyman Bagabas if (report_brightness != -1)
8758a480c10SAyman Bagabas quirks->report_brightness = report_brightness;
8768a480c10SAyman Bagabas
8771ac9abebSAyman Bagabas err = platform_driver_register(&huawei_wmi_driver);
8781ac9abebSAyman Bagabas if (err)
8791ac9abebSAyman Bagabas goto pdrv_err;
8801ac9abebSAyman Bagabas
8818d05fc03SBarnabás Pőcze pdev = platform_device_register_simple("huawei-wmi", PLATFORM_DEVID_NONE, NULL, 0);
8821ac9abebSAyman Bagabas if (IS_ERR(pdev)) {
8831ac9abebSAyman Bagabas err = PTR_ERR(pdev);
8841ac9abebSAyman Bagabas goto pdev_err;
8851ac9abebSAyman Bagabas }
8861ac9abebSAyman Bagabas
8871ac9abebSAyman Bagabas return 0;
8881ac9abebSAyman Bagabas
8891ac9abebSAyman Bagabas pdev_err:
8901ac9abebSAyman Bagabas platform_driver_unregister(&huawei_wmi_driver);
8911ac9abebSAyman Bagabas pdrv_err:
8921ac9abebSAyman Bagabas kfree(huawei_wmi);
8931ac9abebSAyman Bagabas return err;
8941ac9abebSAyman Bagabas }
8951ac9abebSAyman Bagabas
huawei_wmi_exit(void)8961ac9abebSAyman Bagabas static __exit void huawei_wmi_exit(void)
8971ac9abebSAyman Bagabas {
898c7a94976SAndy Shevchenko struct platform_device *pdev = to_platform_device(huawei_wmi->dev);
899c7a94976SAndy Shevchenko
900c7a94976SAndy Shevchenko platform_device_unregister(pdev);
9011ac9abebSAyman Bagabas platform_driver_unregister(&huawei_wmi_driver);
9028c7d9ec8SAndy Shevchenko
9038c7d9ec8SAndy Shevchenko kfree(huawei_wmi);
9041ac9abebSAyman Bagabas }
9051ac9abebSAyman Bagabas
9061ac9abebSAyman Bagabas module_init(huawei_wmi_init);
9071ac9abebSAyman Bagabas module_exit(huawei_wmi_exit);
9081ac9abebSAyman Bagabas
909090ddd77SAyman Bagabas MODULE_ALIAS("wmi:"HWMI_METHOD_GUID);
9101ac9abebSAyman Bagabas MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table);
911bf4fb28cSAyman Bagabas MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
9121ac9abebSAyman Bagabas MODULE_DESCRIPTION("Huawei WMI laptop extras driver");
913bf4fb28cSAyman Bagabas MODULE_LICENSE("GPL v2");
914