LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver
@ 2019-06-13  3:04 Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver Ayman Bagabas
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

Changes from v1:
* introducing debugfs
* code reformatting

This patch series introduce new features to the driver and also moves the
driver from wmi_driver to platform_driver. This move is necessary because the
driver is no longer only a hotkeys driver and platform_driver offers easier
future extensibility.

The patch series introduces a WMI BIOS interface that brings on new features
and enables controlling micmute LED through this interface on supported models.
It also enables controlling battery charging thresholds and fn-lock state.
These features are controlled through the HWMI WMI device present in most of
these laptops.

Currently, micmute LED is controlled through an ACPI method under EC.
This method ("SPIN", "WPIN") is specific to some models and wouldn't
work on all Huawei laptops. Controlling this LED through the interface provides
a better unified method to control the LED on models that implements this
feature.

The behavior of hotkeys is not the same among all models. Some models
require fn-lock to do things like `Ctrl-Ins` or `Alt-PrtSc`. Fn-lock inverts the
behavior of the top row from special keys to F1-F12 keys.

A debugfs interface is also implemented to support unrepresented features and to
provide debugging feedback from users.

Ayman Bagabas (8):
  platform/x86: huawei-wmi: move to platform driver
  platform/x86: huawei-wmi: implement WMI management interface
  platform/x86: huawei-wmi: use quirks and module parameters
  platform/x86: huawei-wmi: control micmute LED through WMI interface
  platform/x86: huawei-wmi: add battery charging protection support
  platform/x86: huawei-wmi: add fn-lock support
  platform/x86: huawei-wmi: add sysfs interface support
  platform/x86: huawei-wmi: add debugfs files support

 drivers/platform/x86/huawei-wmi.c | 754 ++++++++++++++++++++++++++----
 1 file changed, 665 insertions(+), 89 deletions(-)

-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v1] platform/x86: Huawei laptop extras driver Ayman Bagabas
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

This move is necessary because the driver is no longer only a hotkeys
driver and platform_driver offers easier future extensibility. Along
with that, some renames have been made to identify the WMI device in ASL
code. AMW0 -> HWMI which is the UID of this WMI device found on
supported laptops. WMI0 is the device name and has no UID, therefore,
left as it is.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 231 ++++++++++++++++++++----------
 1 file changed, 154 insertions(+), 77 deletions(-)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 52fcac5b393a..4a9e14d3b705 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- *  Huawei WMI hotkeys
+ *  Huawei WMI laptop extras driver
  *
  *  Copyright (C) 2018	      Ayman Bagabas <ayman.bagabas@gmail.com>
  */
@@ -10,23 +10,27 @@
 #include <linux/input/sparse-keymap.h>
 #include <linux/leds.h>
 #include <linux/module.h>
+#include <linux/platform_device.h>
 #include <linux/wmi.h>
 
 /*
  * Huawei WMI GUIDs
  */
-#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
-#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
+#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
 
+/* Legacy GUIDs */
 #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
+#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
 
-struct huawei_wmi_priv {
-	struct input_dev *idev;
+
+struct huawei_wmi {
 	struct led_classdev cdev;
-	acpi_handle handle;
-	char *acpi_method;
+	struct input_dev *idev[2];
+	struct platform_device *pdev;
 };
 
+struct platform_device *huawei_wmi_pdev;
+
 static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_KEY,    0x281, { KEY_BRIGHTNESSDOWN } },
 	{ KE_KEY,    0x282, { KEY_BRIGHTNESSUP } },
@@ -37,17 +41,20 @@ static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_KEY,    0x289, { KEY_WLAN } },
 	// Huawei |M| key
 	{ KE_KEY,    0x28a, { KEY_CONFIG } },
-	// Keyboard backlight
+	// Keyboard backlit
 	{ KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
 	{ KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
 	{ KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },
 	{ KE_END,	 0 }
 };
 
+/* LEDs */
+
 static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
 		enum led_brightness brightness)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent);
+	char *acpi_method;
+	acpi_handle handle;
 	acpi_status status;
 	union acpi_object args[3];
 	struct acpi_object_list arg_list = {
@@ -55,55 +62,54 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
 		.count = ARRAY_SIZE(args),
 	};
 
+	handle = ec_get_handle();
+	if (!handle) {
+		dev_err(led_cdev->dev->parent, "Failed to get EC handle\n");
+		return -ENODEV;
+	}
+
 	args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
 	args[1].integer.value = 0x04;
 
-	if (strcmp(priv->acpi_method, "SPIN") == 0) {
+	if (acpi_has_method(handle, "SPIN")) {
+		acpi_method = "SPIN";
 		args[0].integer.value = 0;
 		args[2].integer.value = brightness ? 1 : 0;
-	} else if (strcmp(priv->acpi_method, "WPIN") == 0) {
+	} else if (acpi_has_method(handle, "WPIN")) {
+		acpi_method = "WPIN";
 		args[0].integer.value = 1;
 		args[2].integer.value = brightness ? 0 : 1;
 	} else {
-		return -EINVAL;
+		return -ENODEV;
 	}
 
-	status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL);
-	if (ACPI_FAILURE(status))
-		return -ENXIO;
+	status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
+	if (ACPI_FAILURE(status)) {
+		return -ENODEV;
+	}
 
 	return 0;
 }
 
-static int huawei_wmi_leds_setup(struct wmi_device *wdev)
+static int huawei_wmi_leds_setup(struct device *dev)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
 
-	priv->handle = ec_get_handle();
-	if (!priv->handle)
-		return 0;
+	huawei->cdev.name = "platform::micmute";
+	huawei->cdev.max_brightness = 1;
+	huawei->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set;
+	huawei->cdev.default_trigger = "audio-micmute";
+	huawei->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
+	huawei->cdev.dev = dev->parent;
+	huawei->cdev.flags = LED_CORE_SUSPENDRESUME;
 
-	if (acpi_has_method(priv->handle, "SPIN"))
-		priv->acpi_method = "SPIN";
-	else if (acpi_has_method(priv->handle, "WPIN"))
-		priv->acpi_method = "WPIN";
-	else
-		return 0;
-
-	priv->cdev.name = "platform::micmute";
-	priv->cdev.max_brightness = 1;
-	priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set;
-	priv->cdev.default_trigger = "audio-micmute";
-	priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
-	priv->cdev.dev = &wdev->dev;
-	priv->cdev.flags = LED_CORE_SUSPENDRESUME;
-
-	return devm_led_classdev_register(&wdev->dev, &priv->cdev);
+	return devm_led_classdev_register(dev, &huawei->cdev);
 }
 
-static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
+/* Input */
+
+static void huawei_wmi_process_key(struct input_dev *idev, int code)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
 	const struct key_entry *key;
 
 	/*
@@ -117,8 +123,10 @@ static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
 		acpi_status status;
 
 		status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response);
-		if (ACPI_FAILURE(status))
+		if (ACPI_FAILURE(status)) {
+			dev_err(&idev->dev, "Failed to query wmi block\n");
 			return;
+		}
 
 		obj = (union acpi_object *)response.pointer;
 		if (obj && obj->type == ACPI_TYPE_INTEGER)
@@ -127,81 +135,150 @@ static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
 		kfree(response.pointer);
 	}
 
-	key = sparse_keymap_entry_from_scancode(priv->idev, code);
+	key = sparse_keymap_entry_from_scancode(idev, code);
 	if (!key) {
-		dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code);
+		dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code);
 		return;
 	}
 
-	sparse_keymap_report_entry(priv->idev, key, 1, true);
+	sparse_keymap_report_entry(idev, key, 1, true);
 }
 
-static void huawei_wmi_notify(struct wmi_device *wdev,
-		union acpi_object *obj)
+static void huawei_wmi_input_notify(u32 value, void *context)
 {
-	if (obj->type == ACPI_TYPE_INTEGER)
-		huawei_wmi_process_key(wdev, obj->integer.value);
+	struct input_dev *idev = (struct input_dev *)context;
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (ACPI_FAILURE(status)) {
+		dev_err(&idev->dev, "Unable to get event data\n");
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+	if (obj && obj->type == ACPI_TYPE_INTEGER)
+		huawei_wmi_process_key(idev, obj->integer.value);
 	else
-		dev_info(&wdev->dev, "Bad response type %d\n", obj->type);
+		dev_err(&idev->dev, "Bad response type\n");
+
+	kfree(response.pointer);
 }
 
-static int huawei_wmi_input_setup(struct wmi_device *wdev)
+static int huawei_wmi_input_setup(struct platform_device *pdev,
+		struct input_dev **idev)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
 	int err;
 
-	priv->idev = devm_input_allocate_device(&wdev->dev);
-	if (!priv->idev)
+	*idev = devm_input_allocate_device(&pdev->dev);
+	if (!*idev)
 		return -ENOMEM;
 
-	priv->idev->name = "Huawei WMI hotkeys";
-	priv->idev->phys = "wmi/input0";
-	priv->idev->id.bustype = BUS_HOST;
-	priv->idev->dev.parent = &wdev->dev;
+	(*idev)->name = "Huawei WMI hotkeys";
+	(*idev)->phys = "wmi/input0";
+	(*idev)->id.bustype = BUS_HOST;
+	(*idev)->dev.parent = &pdev->dev;
 
-	err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL);
+	err = sparse_keymap_setup(*idev, huawei_wmi_keymap, NULL);
 	if (err)
 		return err;
 
-	return input_register_device(priv->idev);
+	return input_register_device(*idev);
 }
 
-static int huawei_wmi_probe(struct wmi_device *wdev)
+/* Huawei driver */
+
+static int huawei_wmi_probe(struct platform_device *pdev)
 {
-	struct huawei_wmi_priv *priv;
+	struct huawei_wmi *huawei;
 	int err;
 
-	priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL);
-	if (!priv)
+	huawei = devm_kzalloc(&pdev->dev, sizeof(struct huawei_wmi), GFP_KERNEL);
+	if (!huawei)
 		return -ENOMEM;
 
-	dev_set_drvdata(&wdev->dev, priv);
+	huawei->pdev = pdev;
+	dev_set_drvdata(&pdev->dev, huawei);
+
+	if (wmi_has_guid(WMI0_EVENT_GUID)) {
+		err = huawei_wmi_input_setup(pdev, &huawei->idev[0]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup input device\n");
+		err = wmi_install_notify_handler(WMI0_EVENT_GUID,
+				huawei_wmi_input_notify, huawei->idev[0]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to install notify handler\n");
+	}
 
-	err = huawei_wmi_input_setup(wdev);
-	if (err)
-		return err;
+	if (wmi_has_guid(HWMI_EVENT_GUID)) {
+		err = huawei_wmi_input_setup(pdev, &huawei->idev[1]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup input device\n");
+		err = wmi_install_notify_handler(HWMI_EVENT_GUID,
+				huawei_wmi_input_notify, huawei->idev[1]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to install notify handler\n");
+	}
 
-	return huawei_wmi_leds_setup(wdev);
+	if (wmi_has_guid(HWMI_METHOD_GUID)) {
+		err = huawei_wmi_leds_setup(&pdev->dev);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup leds\n");
+	}
+	return 0;
 }
 
-static const struct wmi_device_id huawei_wmi_id_table[] = {
-	{ .guid_string = WMI0_EVENT_GUID },
-	{ .guid_string = AMW0_EVENT_GUID },
-	{  }
-};
+static int huawei_wmi_remove(struct platform_device *pdev)
+{
+	if (wmi_has_guid(WMI0_EVENT_GUID))
+		wmi_remove_notify_handler(WMI0_EVENT_GUID);
 
-static struct wmi_driver huawei_wmi_driver = {
+	if (wmi_has_guid(HWMI_EVENT_GUID))
+		wmi_remove_notify_handler(HWMI_EVENT_GUID);
+
+	return 0;
+}
+
+static struct platform_driver huawei_wmi_driver = {
 	.driver = {
 		.name = "huawei-wmi",
 	},
-	.id_table = huawei_wmi_id_table,
 	.probe = huawei_wmi_probe,
-	.notify = huawei_wmi_notify,
+	.remove = huawei_wmi_remove,
 };
 
-module_wmi_driver(huawei_wmi_driver);
+static __init int huawei_wmi_init(void)
+{
+	int err;
+
+	err = platform_driver_register(&huawei_wmi_driver);
+	if (err) {
+		pr_err("Failed to register platform driver\n");
+		return err;
+	}
+
+	huawei_wmi_pdev = platform_device_register_simple("huawei-wmi", -1, NULL, 0);
+	if (IS_ERR(huawei_wmi_pdev)) {
+		pr_err("Failed to register platform device\n");
+		platform_driver_unregister(&huawei_wmi_driver);
+		return PTR_ERR(huawei_wmi_pdev);
+	}
+
+	return 0;
+}
+
+static __exit void huawei_wmi_exit(void)
+{
+	platform_device_unregister(huawei_wmi_pdev);
+	platform_driver_unregister(&huawei_wmi_driver);
+}
+
+module_init(huawei_wmi_init);
+module_exit(huawei_wmi_exit);
 
-MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table);
+MODULE_ALIAS("wmi:"WMI0_EVENT_GUID);
+MODULE_ALIAS("wmi:"HWMI_EVENT_GUID);
 MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
-MODULE_DESCRIPTION("Huawei WMI hotkeys");
+MODULE_DESCRIPTION("Huawei WMI laptop extras driver");
 MODULE_LICENSE("GPL v2");
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v1] platform/x86: Huawei laptop extras driver
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 2/8] platform/x86: huawei-wmi: implement WMI management interface Ayman Bagabas
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

This patch introduces new features to the driver and also moves the
driver from wmi_driver to platform_driver.

This move is necessary because the driver is no longer only a hotkeys
driver and platform_driver offers easier future extensibility. The patch
introduces a WMI BIOS interface that brings on new features and enables
controlling micmute LED through this interface on supported models. New
features are controlling battery charging thresholds and fn-lock state
among with module parameters and quirks check.

Currently, micmute LED is controlled through an ACPI method under EC.
This method ("SPIN", "WPIN") is specific to some models and wouldn't
work on all Huawei laptops.

Using this interface, controlling this LED should work with any model.
Except `MateBook X` from 2017, this one doesn't provide controlling the
LED through this interface instead it uses another "legacy" interface
that is not "fully" implemented yet due to lack of hardware. Currently,
this "legacy" interface is used for hotkeys on this specific model. A
quirk is set to use ACPI method to control micmute LED on this model.

Some models that implement the new WMI BIOS interface can control
battery charging thresholds where it limits charging the battery once it
reaches certain thresholds.

The behavior of hotkeys is not the same among all models. Some models
require fn-lock to do things like `Ctrl-Ins` or `Alt-PrtSc`. By default,
hotkeys behave as special keys (media keys, Ins, etc), but if a modifier
is used (ctrl, alt, shift) these keys behave as F1-F12 keys. If the Fn
key is toggled on, the hotkeys with or without a modifier, behave as
F1-F12 keys. This makes it impossible to use a modifier and `PrtSc` or
`Ins`.

Now, some models fix this by excluding `PrtSc` and `Ins` keys from
being treated as F11 and F12 keys with the use of a modifier. However,
some models do not, and fixes this by the so called fn-lock.

Fn-lock inverts the behavior of the top row from special keys to F1-F12
keys. So a modifier and a special key would be possible which make
things like `Alt-Ins` possible. Now, with fn-lock we would have 4 modes:
* Fn-key off & fn-lock off - hotkeys treated as special keys using a
  modifier gives F1-F12 keys.
* Fn-key on & fn-lock off - hotkeys treated as F1-F12 keys and using a
  modifier gives F1-F12.
* Fn-key off & fn-lock on - hotkeys are treated as F1-F12 keys and using
  a modifier gives special keys.
* Fn-key on & fn-lock on - hotkeys are treated as special keys and using
  a modifier gives special keys.

The driver introduces two parameters to force reporting brightness keys
and sleeping after setting a threshold value.

All newer models that "fully" implement the new interface report
brightness key events twice, once through WMI and once through
acpi-video. Older models, such as `MateBook X`, don't report brightness
events using WMI. This is implemented as a quirk and can be forced using
module parameters.

Some models don't allow setting thresholds to (0, 100), due to bad ASL
code, which indicates reset values, instead, it only turns off battery
charging protection. This would return the previously set values even
though battery protection is off which doesn't make sense. A sane value
like (0, 100) indicates no charging protection, but since it's not
possible to set such values, (0, 0) is set before turning protection
off with (0, 100). This requires a delay after setting (0, 0) and after
(0, 100) so that these values make their way to EC memory.

These parameters are implemented as quirks along with `ec_micmute` quirk
which controls the micmute LED through ACPI EC interface.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 630 +++++++++++++++++++++++++-----
 1 file changed, 541 insertions(+), 89 deletions(-)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 52fcac5b393a..3f945b4cf115 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -1,32 +1,65 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- *  Huawei WMI hotkeys
+ *  Huawei WMI laptop extras driver
  *
  *  Copyright (C) 2018	      Ayman Bagabas <ayman.bagabas@gmail.com>
  */
 
 #include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
 #include <linux/leds.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
 #include <linux/wmi.h>
 
 /*
  * Huawei WMI GUIDs
  */
-#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
+#define AMW0_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000"
 #define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
 
+/* Legacy GUIDs */
 #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
+#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
+
+/* AMW0_commands */
+
+enum wmaa_cmd {
+	BATTERY_GET, /* \GBTT 0x00001103 */
+	BATTERY_SET, /* \SBTT 0xXXYY1003 */
+	FN_LOCK_GET, /* \GFRS 0x00000604 */
+	FN_LOCK_SET, /* \SFRS 0x000X0704 */
+	MICMUTE_LED, /* \SMLS 0x000X0b04 */
+};
+
+enum fn_state {
+	FN_LOCK_OFF = 0x01,
+	FN_LOCK_ON = 0x02,
+};
+
+struct quirk_entry {
+	bool battery_sleep;
+	bool ec_micmute;
+	bool report_brightness;
+};
 
-struct huawei_wmi_priv {
-	struct input_dev *idev;
+static struct quirk_entry *quirks;
+
+struct huawei_wmi {
 	struct led_classdev cdev;
-	acpi_handle handle;
-	char *acpi_method;
+	struct mutex wmi_lock;
+	struct mutex battery_lock;
+	struct input_dev *idev[2];
+	struct platform_device *pdev;
 };
 
+struct platform_device *huawei_wmi_pdev;
+
 static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_KEY,    0x281, { KEY_BRIGHTNESSDOWN } },
 	{ KE_KEY,    0x282, { KEY_BRIGHTNESSUP } },
@@ -37,73 +70,183 @@ static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_KEY,    0x289, { KEY_WLAN } },
 	// Huawei |M| key
 	{ KE_KEY,    0x28a, { KEY_CONFIG } },
-	// Keyboard backlight
+	// Keyboard backlit
 	{ KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
 	{ KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
 	{ KE_IGNORE, 0x295, { KEY_KBDILLUMUP } },
 	{ KE_END,	 0 }
 };
 
-static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
-		enum led_brightness brightness)
+static bool battery_sleep;
+static bool report_brightness;
+
+module_param(battery_sleep, bool, 0444);
+MODULE_PARM_DESC(battery_sleep,
+		"Delay after setting battery charging thresholds.");
+module_param(report_brightness, bool, 0444);
+MODULE_PARM_DESC(report_brightness,
+		"Report brightness key events.");
+
+/* Quirks */
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent);
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_battery_sleep = {
+	.battery_sleep = true,
+};
+
+static struct quirk_entry quirk_matebook_x = {
+	.ec_micmute = true,
+	.report_brightness = true,
+};
+
+static const struct dmi_system_id huawei_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "Huawei MACH-WX9",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"),
+		},
+		.driver_data = &quirk_battery_sleep
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Huawei MateBook X",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X")
+		},
+		.driver_data = &quirk_matebook_x
+	},
+	{  }
+};
+
+/* Utils */
+
+static int huawei_wmi_eval(struct device *dev, u8 *arg,
+		u8 *buf, size_t buflen)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer in;
+	union acpi_object *obj;
 	acpi_status status;
-	union acpi_object args[3];
-	struct acpi_object_list arg_list = {
-		.pointer = args,
-		.count = ARRAY_SIZE(args),
-	};
-
-	args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
-	args[1].integer.value = 0x04;
-
-	if (strcmp(priv->acpi_method, "SPIN") == 0) {
-		args[0].integer.value = 0;
-		args[2].integer.value = brightness ? 1 : 0;
-	} else if (strcmp(priv->acpi_method, "WPIN") == 0) {
-		args[0].integer.value = 1;
-		args[2].integer.value = brightness ? 0 : 1;
-	} else {
-		return -EINVAL;
+	size_t len;
+	int err = -EIO;
+
+	in.length = sizeof(u8) * 4;
+	in.pointer = (u32 *)arg;
+	mutex_lock(&huawei->wmi_lock);
+	status = wmi_evaluate_method(AMW0_METHOD_GUID, 0, 1, &in, &out);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Failed to evaluate wmi method\n");
+		err = -ENODEV;
+		goto wmi_eval_fail;
+	}
+
+	/* WMAA takes a 4 bytes buffer as an input. It returns a package
+	 * with two buffer elements. The first buffer is 4 bytes long and
+	 * the second is 0x100 (256) bytes long. The first buffer is always
+	 * zeros. The second stores the output from every call. The first
+	 * byte of the second buffer always have the return status of the
+	 * called command.
+	 */
+	obj = out.pointer;
+	if (!obj)
+		goto wmi_eval_fail;
+	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2) {
+		dev_err(dev, "Unknown response type %d\n", obj->type);
+		goto wmi_eval_fail;
 	}
 
-	status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL);
-	if (ACPI_FAILURE(status))
-		return -ENXIO;
+	obj = &(obj->package.elements[1]);
+	if (!obj || obj->type != ACPI_TYPE_BUFFER)
+		goto wmi_eval_fail;
 
-	return 0;
+	if (buf) {
+		len = min(buflen, obj->buffer.length);
+		memcpy(buf, obj->buffer.pointer, len);
+	}
+	err = 0;
+
+wmi_eval_fail:
+	mutex_unlock(&huawei->wmi_lock);
+	kfree(out.pointer);
+	return err;
 }
 
-static int huawei_wmi_leds_setup(struct wmi_device *wdev)
+static int huawei_wmi_cmd(struct device *dev, enum wmaa_cmd cmd, u8 *arg,
+		u8 *out, size_t outlen)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
-
-	priv->handle = ec_get_handle();
-	if (!priv->handle)
-		return 0;
+	u8 parm[4] = { 0 };
+	u8 buf[0x100] = { 0xff };
+	int err;
 
-	if (acpi_has_method(priv->handle, "SPIN"))
-		priv->acpi_method = "SPIN";
-	else if (acpi_has_method(priv->handle, "WPIN"))
-		priv->acpi_method = "WPIN";
-	else
-		return 0;
+	if (!arg)
+		arg = parm;
+
+	switch (cmd) {
+	case BATTERY_SET:
+		arg[0] = 0x03;
+		arg[1] = 0x10;
+		break;
+	case BATTERY_GET:
+		arg[0] = 0x03;
+		arg[1] = 0x11;
+		break;
+	case FN_LOCK_GET:
+		arg[0] = 0x04;
+		arg[1] = 0x06;
+		break;
+	case FN_LOCK_SET:
+		arg[0] = 0x04;
+		arg[1] = 0x07;
+		break;
+	case MICMUTE_LED:
+		arg[0] = 0x04;
+		arg[1] = 0x0b;
+		break;
+	default:
+		dev_err(dev, "Unsupported command, got: 0x%08x\n", *(u32 *)arg);
+		return -EINVAL;
+	}
 
-	priv->cdev.name = "platform::micmute";
-	priv->cdev.max_brightness = 1;
-	priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set;
-	priv->cdev.default_trigger = "audio-micmute";
-	priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
-	priv->cdev.dev = &wdev->dev;
-	priv->cdev.flags = LED_CORE_SUSPENDRESUME;
+	/* Some models require calling WMAA twice to execute
+	 * a command. We call WMAA and if we get a non-zero return
+	 * status we evaluate WMAA again. If we get another non-zero
+	 * return, we return -EIO. This way we don't need to
+	 * check for return status anywhere we call huawei_wmi_cmd.
+	 */
+	err = huawei_wmi_eval(dev, arg, buf, 0x100);
+	if (err)
+		return err;
+	if (buf[0]) {
+		err = huawei_wmi_eval(dev, arg, buf, 0x100);
+		if (err)
+			return err;
+		if (buf[0]) {
+			dev_err(dev, "Invalid response, got: %d\n", buf[0]);
+			return -EIO;
+		}
+	}
+	if (out)
+		memcpy(out, buf, outlen);
 
-	return devm_led_classdev_register(&wdev->dev, &priv->cdev);
+	return 0;
 }
 
-static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
+/* Input */
+
+static void huawei_wmi_process_key(struct input_dev *idev, int code)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
 	const struct key_entry *key;
 
 	/*
@@ -127,81 +270,390 @@ static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
 		kfree(response.pointer);
 	}
 
-	key = sparse_keymap_entry_from_scancode(priv->idev, code);
+	key = sparse_keymap_entry_from_scancode(idev, code);
 	if (!key) {
-		dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code);
+		dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code);
 		return;
 	}
 
-	sparse_keymap_report_entry(priv->idev, key, 1, true);
+	if (quirks && !quirks->report_brightness &&
+			(key->sw.code == KEY_BRIGHTNESSDOWN ||
+			key->sw.code == KEY_BRIGHTNESSUP))
+		return;
+
+	sparse_keymap_report_entry(idev, key, 1, true);
 }
 
-static void huawei_wmi_notify(struct wmi_device *wdev,
-		union acpi_object *obj)
+static void huawei_wmi_input_notify(u32 value, void *context)
 {
-	if (obj->type == ACPI_TYPE_INTEGER)
-		huawei_wmi_process_key(wdev, obj->integer.value);
+	struct input_dev *idev = (struct input_dev *)context;
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+
+	if (!idev) {
+		dev_err(&huawei_wmi_pdev->dev, "No input_dev\n");
+		return;
+	}
+
+	status = wmi_get_event_data(value, &response);
+	if (ACPI_FAILURE(status)) {
+		dev_err(&idev->dev, "Unable to get event data\n");
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+	if (obj && obj->type == ACPI_TYPE_INTEGER)
+		huawei_wmi_process_key(idev, obj->integer.value);
 	else
-		dev_info(&wdev->dev, "Bad response type %d\n", obj->type);
+		dev_err(&idev->dev, "Bad response type\n");
+
+	kfree(response.pointer);
 }
 
-static int huawei_wmi_input_setup(struct wmi_device *wdev)
+static int huawei_wmi_input_setup(struct platform_device *pdev,
+		struct input_dev **idev)
 {
-	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
 	int err;
 
-	priv->idev = devm_input_allocate_device(&wdev->dev);
-	if (!priv->idev)
+	*idev = devm_input_allocate_device(&pdev->dev);
+	if (!*idev)
 		return -ENOMEM;
 
-	priv->idev->name = "Huawei WMI hotkeys";
-	priv->idev->phys = "wmi/input0";
-	priv->idev->id.bustype = BUS_HOST;
-	priv->idev->dev.parent = &wdev->dev;
+	(*idev)->name = "Huawei WMI hotkeys";
+	(*idev)->phys = "wmi/input0";
+	(*idev)->id.bustype = BUS_HOST;
+	(*idev)->dev.parent = &pdev->dev;
+
+	err = sparse_keymap_setup(*idev, huawei_wmi_keymap, NULL);
+	if (err)
+		return err;
+
+	return input_register_device(*idev);
+}
+
+/* LEDs */
+
+static void huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
+		enum led_brightness brightness)
+{
+	/* This is a workaround until the "legacy" interface is implemented. */
+	if (quirks && quirks->ec_micmute) {
+		char *acpi_method;
+		acpi_handle handle;
+		union acpi_object args[3];
+		struct acpi_object_list arg_list = {
+			.pointer = args,
+			.count = ARRAY_SIZE(args),
+		};
+
+		handle = ec_get_handle();
+		if (!handle) {
+			dev_err(led_cdev->dev->parent, "Failed to get EC handle\n");
+			return;
+		}
+
+		args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
+		args[1].integer.value = 0x04;
+
+		if (acpi_has_method(handle, "SPIN")) {
+			acpi_method = "SPIN";
+			args[0].integer.value = 0;
+			args[2].integer.value = brightness ? 1 : 0;
+		} else if (acpi_has_method(handle, "WPIN")) {
+			acpi_method = "WPIN";
+			args[0].integer.value = 1;
+			args[2].integer.value = brightness ? 0 : 1;
+		} else {
+			return;
+		}
+
+		acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
+	} else {
+		u8 arg[] = { 0, 0, brightness, 0 };
+
+		huawei_wmi_cmd(led_cdev->dev->parent, MICMUTE_LED, arg, NULL, NULL);
+	}
+}
+
+static int huawei_wmi_leds_setup(struct device *dev)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
 
-	err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL);
+	huawei->cdev.name = "platform::micmute";
+	huawei->cdev.max_brightness = 1;
+	huawei->cdev.brightness_set = huawei_wmi_micmute_led_set;
+	huawei->cdev.default_trigger = "audio-micmute";
+	huawei->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
+	huawei->cdev.dev = dev->parent;
+	huawei->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+	return devm_led_classdev_register(dev, &huawei->cdev);
+}
+
+/* Battery protection */
+
+static int huawei_wmi_battery_get(struct device *dev, int *low, int *high)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	u8 ret[0x100] = { 0 };
+	int err, i = 0x100;
+
+	mutex_lock(&huawei->battery_lock);
+	err = huawei_wmi_cmd(dev, BATTERY_GET, NULL, ret, 0x100);
+	mutex_unlock(&huawei->battery_lock);
 	if (err)
 		return err;
 
-	return input_register_device(priv->idev);
+	/* Returned buffer positions battery thresholds either in index
+	 * 3 and 2 or in 2 and 1. 0 reserved for return status. We
+	 * find the first non-zero value.
+	 */
+	while (i > 0 && !ret[i--])
+		;
+	*low = ret[i];
+	*high = ret[i+1];
+
+	return 0;
 }
 
-static int huawei_wmi_probe(struct wmi_device *wdev)
+static int huawei_wmi_battery_set(struct device *dev, int low, int high)
 {
-	struct huawei_wmi_priv *priv;
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	u8 arg[] = { 0, 0, low, high };
 	int err;
 
-	priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
+	/* This is an edge case were some models turn battery protection
+	 * off without changing their thresholds values. We clear the
+	 * values before turning off protection. We need a blocking delay to
+	 * make sure these values make their way to EC.
+	 */
+	if (low == 0 && high == 100)
+		huawei_wmi_battery_set(dev, 0, 0);
 
-	dev_set_drvdata(&wdev->dev, priv);
+	mutex_lock(&huawei->battery_lock);
+	err = huawei_wmi_cmd(dev, BATTERY_SET, arg, NULL, NULL);
+	if (quirks && quirks->battery_sleep)
+		msleep(1000);
+	mutex_unlock(&huawei->battery_lock);
 
-	err = huawei_wmi_input_setup(wdev);
+	return err;
+}
+
+/* Fn lock */
+
+static int huawei_wmi_fn_lock_get(struct device *dev, int *on)
+{
+	u8 ret[0x100] = { 0 };
+	int err, i = 0;
+
+	err = huawei_wmi_cmd(dev, FN_LOCK_GET, NULL, ret, 0x100);
 	if (err)
 		return err;
 
-	return huawei_wmi_leds_setup(wdev);
+	/* Find the first non-zero value */
+	while (i <= 0x100 && !ret[i++])
+		;
+	*on = (ret[i-1] == FN_LOCK_OFF) ? 0 : 1;
+
+	return 0;
 }
 
-static const struct wmi_device_id huawei_wmi_id_table[] = {
-	{ .guid_string = WMI0_EVENT_GUID },
-	{ .guid_string = AMW0_EVENT_GUID },
-	{  }
+static int huawei_wmi_fn_lock_set(struct device *dev, int on)
+{
+	u8 arg[] = { 0, 0, (on) ? FN_LOCK_ON : FN_LOCK_OFF, 0 };
+
+	return huawei_wmi_cmd(dev, FN_LOCK_SET, arg, NULL, NULL);
+}
+
+/* sysfs */
+
+static ssize_t charge_thresholds_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	int low, high, err;
+
+	if (sscanf(buf, "%d %d", &low, &high) != 2 ||
+			low < 0 || high > 100 ||
+			low > high)
+		return -EINVAL;
+
+	err = huawei_wmi_battery_set(dev, low, high);
+	if (err)
+		return err;
+
+	return size;
+}
+
+static ssize_t fn_lock_state_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	int on, err;
+
+	if (kstrtoint(buf, 10, &on) ||
+			on < 0 || on > 1)
+		return -EINVAL;
+
+	err = huawei_wmi_fn_lock_set(dev, on);
+	if (err)
+		return err;
+
+	return size;
+}
+
+static ssize_t charge_thresholds_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	int err, low = -1, high = -1;
+
+	err = huawei_wmi_battery_get(dev, &low, &high);
+	if (err)
+		return err;
+
+	return sprintf(buf, "%d %d\n", low, high);
+}
+
+static ssize_t fn_lock_state_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	int err, on = -1;
+
+	err = huawei_wmi_fn_lock_get(dev, &on);
+	if (err)
+		return err;
+
+	return sprintf(buf, "%d\n", on);
+}
+
+static DEVICE_ATTR_RW(charge_thresholds);
+static DEVICE_ATTR_RW(fn_lock_state);
+
+static struct attribute *huawei_wmi_attrs[] = {
+	&dev_attr_charge_thresholds.attr,
+	&dev_attr_fn_lock_state.attr,
+	NULL
+};
+
+static const struct attribute_group huawei_wmi_group = {
+	.attrs = huawei_wmi_attrs
 };
 
-static struct wmi_driver huawei_wmi_driver = {
+/* Huawei driver */
+
+static int huawei_wmi_probe(struct platform_device *pdev)
+{
+	struct huawei_wmi *huawei;
+	int err;
+
+	huawei = devm_kzalloc(&pdev->dev, sizeof(struct huawei_wmi), GFP_KERNEL);
+	if (!huawei)
+		return -ENOMEM;
+
+	huawei->pdev = pdev;
+	dev_set_drvdata(&pdev->dev, huawei);
+
+	if (wmi_has_guid(WMI0_EVENT_GUID)) {
+		err = huawei_wmi_input_setup(pdev, &huawei->idev[0]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup input device\n");
+		err = wmi_install_notify_handler(WMI0_EVENT_GUID,
+				huawei_wmi_input_notify, huawei->idev[0]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to install notify handler\n");
+	}
+
+	if (wmi_has_guid(AMW0_EVENT_GUID)) {
+		err = huawei_wmi_input_setup(pdev, &huawei->idev[1]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup input device\n");
+		err = wmi_install_notify_handler(AMW0_EVENT_GUID,
+				huawei_wmi_input_notify, huawei->idev[1]);
+		if (err)
+			dev_err(&pdev->dev, "Failed to install notify handler\n");
+	}
+
+	if (wmi_has_guid(AMW0_METHOD_GUID)) {
+
+		mutex_init(&huawei->wmi_lock);
+		mutex_init(&huawei->battery_lock);
+
+		err = sysfs_create_group(&pdev->dev.kobj, &huawei_wmi_group);
+		if (err) {
+			dev_err(&pdev->dev, "Failed to create sysfs interface\n");
+			return err;
+		}
+
+		err = huawei_wmi_leds_setup(&pdev->dev);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup leds\n");
+	}
+
+	return 0;
+}
+
+static int huawei_wmi_remove(struct platform_device *pdev)
+{
+	if (wmi_has_guid(WMI0_EVENT_GUID))
+		wmi_remove_notify_handler(WMI0_EVENT_GUID);
+
+	if (wmi_has_guid(AMW0_EVENT_GUID))
+		wmi_remove_notify_handler(AMW0_EVENT_GUID);
+
+	if (wmi_has_guid(AMW0_METHOD_GUID))
+		sysfs_remove_group(&pdev->dev.kobj, &huawei_wmi_group);
+
+	return 0;
+}
+
+static struct platform_driver huawei_wmi_driver = {
 	.driver = {
 		.name = "huawei-wmi",
 	},
-	.id_table = huawei_wmi_id_table,
 	.probe = huawei_wmi_probe,
-	.notify = huawei_wmi_notify,
+	.remove = huawei_wmi_remove,
 };
 
-module_wmi_driver(huawei_wmi_driver);
+static __init int huawei_wmi_init(void)
+{
+	int err;
+
+	quirks = &quirk_unknown;
+	dmi_check_system(huawei_quirks);
+	quirks->battery_sleep |= battery_sleep;
+	quirks->report_brightness |= report_brightness;
+
+	err = platform_driver_register(&huawei_wmi_driver);
+	if (err) {
+		pr_err("Failed to register platform driver\n");
+		return err;
+	}
+
+	huawei_wmi_pdev = platform_device_register_simple("huawei-wmi", -1, NULL, 0);
+	if (IS_ERR(huawei_wmi_pdev)) {
+		pr_err("Failed to register platform device\n");
+		platform_driver_unregister(&huawei_wmi_driver);
+		return PTR_ERR(huawei_wmi_pdev);
+	}
+
+	return 0;
+}
+
+static __exit void huawei_wmi_exit(void)
+{
+	platform_device_unregister(huawei_wmi_pdev);
+	platform_driver_unregister(&huawei_wmi_driver);
+}
+
+module_init(huawei_wmi_init);
+module_exit(huawei_wmi_exit);
 
-MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table);
+MODULE_ALIAS("wmi:"WMI0_EVENT_GUID);
+MODULE_ALIAS("wmi:"AMW0_EVENT_GUID);
+MODULE_ALIAS("wmi:"AMW0_METHOD_GUID);
 MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
-MODULE_DESCRIPTION("Huawei WMI hotkeys");
+MODULE_DESCRIPTION("Huawei WMI laptop extras driver");
 MODULE_LICENSE("GPL v2");
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 2/8] platform/x86: huawei-wmi: implement WMI management interface
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v1] platform/x86: Huawei laptop extras driver Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 3/8] platform/x86: huawei-wmi: use quirks and module parameters Ayman Bagabas
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

The patch introduces a WMI BIOS interface that can control various
device features like micmute LED, battery charging thresholds, and
fn-lock. This interface, with device UID of HWMI, is found on recent and
old models including MateBook X released in 2017. This model is kind of
"special" since it has two WMI interfaces, this interface and what we
call it the "legacy" interface. Due to lack of hardware and testers,
this "legacy" interface is not "fully" implemented yet. This "legacy"
interface supports setting the micmute LED for MateBook X (2017).

This device, HWMI, has only one method that takes a 64 bit argument and
returns a package with two elements, the first is 4 bytes and the second
is 256 bytes. The first 4 bytes are always skipped since they return
zero all the time. MateBook X (2017) is a bit different
where it takes 64 bit argument but returns one 260 byte buffer (265+4).
Right now, this interface doesn't offer any usability for MateBook X
(2017) except for fn-lock and debugfs.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 127 ++++++++++++++++++++++++++++++
 1 file changed, 127 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 4a9e14d3b705..37b09d497f5e 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -10,22 +10,35 @@
 #include <linux/input/sparse-keymap.h>
 #include <linux/leds.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/wmi.h>
 
 /*
  * Huawei WMI GUIDs
  */
+#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000"
 #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"
 
 /* Legacy GUIDs */
 #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100"
 #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
 
+/* HWMI_commands */
+
+enum {
+	BATTERY_THRESH_GET 		= 0x00001103, /* \GBTT */
+	BATTERY_THRESH_SET 		= 0x00001003, /* \SBTT */
+	FAN_SPEED_GET			= 0x00000802, /* \GFNS */
+	FN_LOCK_GET				= 0x00000604, /* \GFRS */
+	FN_LOCK_SET 			= 0x00000704, /* \SFRS */
+	MICMUTE_LED_SET 		= 0x00000b04, /* \SMLS */
+};
 
 struct huawei_wmi {
 	struct led_classdev cdev;
 	struct input_dev *idev[2];
+	struct mutex wmi_lock;
 	struct platform_device *pdev;
 };
 
@@ -48,6 +61,118 @@ static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_END,	 0 }
 };
 
+/* Utils */
+
+static int huawei_wmi_call(struct device *dev, struct acpi_buffer *in,
+		struct acpi_buffer *out)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	acpi_status status;
+
+	mutex_lock(&huawei->wmi_lock);
+	status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out);
+	mutex_unlock(&huawei->wmi_lock);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Failed to evaluate wmi method\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of
+ * 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes.
+ * The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a
+ * package, or skip the first 4 if a buffer of 0x104 is used. The first byte of
+ * the remaining 0x100 sized buffer has the return status of every call. In case
+ * the return status is non-zero, we return -ENODEV but still copy the returned
+ * buffer to the given buffer parameter (buf).
+ */
+static int huawei_wmi_cmd(struct device *dev, u64 arg, u8 *buf, size_t buflen)
+{
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer in;
+	union acpi_object *obj;
+	size_t len;
+	int err, i;
+
+	in.length = sizeof(u64);
+	in.pointer = &arg;
+
+	/* Some models require calling HWMI twice to execute a command. We evaluate
+	 * HWMI and if we get a non-zero return status we evaluate it again.
+	 */
+	for (i = 0; i < 2; i++) {
+		err = huawei_wmi_call(dev,  &in, &out);
+		if (err) {
+			goto fail_cmd;
+		}
+
+		obj = out.pointer;
+		if (!obj) {
+			err = -EIO;
+			goto fail_cmd;
+		}
+
+		switch (obj->type) {
+		/* Models that implement both "legacy" and HWMI tend to return a 0x104
+		 * sized buffer instead of a package of 0x4 and 0x100 buffers.
+		 */
+		case ACPI_TYPE_BUFFER:
+			if (obj->buffer.length == 0x104) {
+				// Skip the first 4 bytes.
+				obj->buffer.pointer += 4;
+				len = 0x100;
+			} else {
+				dev_err(dev, "Bad buffer length, got %d\n", obj->buffer.length);
+				err = -EIO;
+				goto fail_cmd;
+			}
+
+			break;
+		/* HWMI returns a package with 2 buffer elements, one of 4 bytes and the
+		 * other is 256 bytes.
+		 */
+		case ACPI_TYPE_PACKAGE:
+			if (obj->package.count != 2) {
+				dev_err(dev, "Bad package count, got %d\n", obj->package.count);
+				err = -EIO;
+				goto fail_cmd;
+			}
+
+			obj = &obj->package.elements[1];
+			if (obj->type != ACPI_TYPE_BUFFER) {
+				dev_err(dev, "Bad package element type, got %d\n", obj->type);
+				err = -EIO;
+				goto fail_cmd;
+			}
+			len = obj->buffer.length;
+
+			break;
+		/* Shouldn't get here! */
+		default:
+			dev_err(dev, "Unexpected obj type, got: %d\n", obj->type);
+			err = -EIO;
+			goto fail_cmd;
+		}
+
+		if (!*obj->buffer.pointer) {
+			break;
+		}
+	}
+
+	err = (*obj->buffer.pointer) ? -ENODEV : 0;
+
+	if (buf) {
+		len = min(buflen, len);
+		memcpy(buf, obj->buffer.pointer, len);
+	}
+
+fail_cmd:
+	kfree(out.pointer);
+	return err;
+}
+
 /* LEDs */
 
 static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
@@ -222,6 +347,7 @@ static int huawei_wmi_probe(struct platform_device *pdev)
 	}
 
 	if (wmi_has_guid(HWMI_METHOD_GUID)) {
+		mutex_init(&huawei->wmi_lock);
 		err = huawei_wmi_leds_setup(&pdev->dev);
 		if (err)
 			dev_err(&pdev->dev, "Failed to setup leds\n");
@@ -279,6 +405,7 @@ module_exit(huawei_wmi_exit);
 
 MODULE_ALIAS("wmi:"WMI0_EVENT_GUID);
 MODULE_ALIAS("wmi:"HWMI_EVENT_GUID);
+MODULE_ALIAS("wmi:"HWMI_METHOD_GUID);
 MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
 MODULE_DESCRIPTION("Huawei WMI laptop extras driver");
 MODULE_LICENSE("GPL v2");
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 3/8] platform/x86: huawei-wmi: use quirks and module parameters
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (2 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 2/8] platform/x86: huawei-wmi: implement WMI management interface Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 4/8] platform/x86: huawei-wmi: control micmute LED through WMI interface Ayman Bagabas
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

The patch introduces two parameters to force reporting brightness keys
and sleeping after setting battery thresholds value.
These parameters are implemented as quirks along with `ec_micmute` quirk
which controls the micmute LED through ACPI EC interface.

All newer models that "fully" implement the new interface report
brightness key events twice, once through WMI and once through
acpi-video. Older models, such as `MateBook X`, don't report brightness
events using WMI. This is implemented as a quirk and can be forced using
module parameters.

Some models don't allow setting thresholds to (0, 100), due to bad ASL
code, which indicates reset values, instead, it only turns off battery
charging protection. This would return the currently set values even
though battery protection is off which doesn't make sense. A sane value
like (0, 100) indicates no charging protection, but since it's not
possible to set such values, (0, 0) is set before turning protection
off with (0, 100). This requires a delay after setting (0, 0) and after
(0, 100) so that these values make their way to EC memory.

    Method (SBTT, 1, NotSerialized)
    {
        Name (BUFF, Buffer (0x0100){})
        Local0 = Arg0
        CreateByteField (Arg0, 0x02, STCP)
        CreateByteField (Arg0, 0x03, SOCP)
        CreateByteField (BUFF, Zero, STAT)
        If (((STCP == Zero) && (SOCP == 0x64)))
        {
            \_SB.PCI0.LPCB.EC0.ECXT (0xC7, Zero, Zero, Zero, Zero, Zero)
        }
        Else
        {
            \_SB.PCI0.LPCB.EC0.ECXT (0xC7, One, STCP, SOCP, Zero, Zero)
        }                                // ^    ^     ^
                                         // |    |     |
        STAT = Zero                      // on   low   high
        Return (BUFF) /* \SBTT.BUFF */   // bit  thresh  thresh
    }

ASL code taken from MateBook X Pro (MACH-WX9) showing how it turns off
protection without changing values.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 71 +++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 37b09d497f5e..647c5a6c8ab3 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/dmi.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
 #include <linux/leds.h>
@@ -35,6 +36,14 @@ enum {
 	MICMUTE_LED_SET 		= 0x00000b04, /* \SMLS */
 };
 
+struct quirk_entry {
+	bool battery_sleep;
+	bool ec_micmute;
+	bool report_brightness;
+};
+
+static struct quirk_entry *quirks;
+
 struct huawei_wmi {
 	struct led_classdev cdev;
 	struct input_dev *idev[2];
@@ -61,6 +70,58 @@ static const struct key_entry huawei_wmi_keymap[] = {
 	{ KE_END,	 0 }
 };
 
+static bool battery_sleep;
+static bool report_brightness;
+
+module_param(battery_sleep, bool, 0444);
+MODULE_PARM_DESC(battery_sleep,
+		"Delay after setting battery charging thresholds.");
+module_param(report_brightness, bool, 0444);
+MODULE_PARM_DESC(report_brightness,
+		"Report brightness key events.");
+
+/* Quirks */
+
+static int __init dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 1;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_battery_sleep = {
+	.battery_sleep = true,
+};
+
+static struct quirk_entry quirk_matebook_x = {
+	.ec_micmute = true,
+	.report_brightness = true,
+};
+
+static const struct dmi_system_id huawei_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "Huawei MACH-WX9",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"),
+		},
+		.driver_data = &quirk_battery_sleep
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Huawei MateBook X",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X")
+		},
+		.driver_data = &quirk_matebook_x
+	},
+	{  }
+};
+
 /* Utils */
 
 static int huawei_wmi_call(struct device *dev, struct acpi_buffer *in,
@@ -266,6 +327,11 @@ static void huawei_wmi_process_key(struct input_dev *idev, int code)
 		return;
 	}
 
+	if (quirks && !quirks->report_brightness &&
+			(key->sw.code == KEY_BRIGHTNESSDOWN ||
+			key->sw.code == KEY_BRIGHTNESSUP))
+		return;
+
 	sparse_keymap_report_entry(idev, key, 1, true);
 }
 
@@ -378,6 +444,11 @@ static __init int huawei_wmi_init(void)
 {
 	int err;
 
+	quirks = &quirk_unknown;
+	dmi_check_system(huawei_quirks);
+	quirks->battery_sleep |= battery_sleep;
+	quirks->report_brightness |= report_brightness;
+
 	err = platform_driver_register(&huawei_wmi_driver);
 	if (err) {
 		pr_err("Failed to register platform driver\n");
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 4/8] platform/x86: huawei-wmi: control micmute LED through WMI interface
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (3 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 3/8] platform/x86: huawei-wmi: use quirks and module parameters Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 5/8] platform/x86: huawei-wmi: add battery charging protection support Ayman Bagabas
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

Using HWMI interface, micmute LED can be controlled on supported models.
`MateBook X` from 2017, doesn't provide controlling the micmute
LED through this interface instead it uses another "legacy" interface
that is not "fully" implemented yet.
Currently, this "legacy" interface is used for hotkeys on this specific
model. A quirk is set to use ACPI method to control micmute LED on
MateBook X (2017).

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 74 ++++++++++++++++++-------------
 1 file changed, 42 insertions(+), 32 deletions(-)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 647c5a6c8ab3..358d9d168300 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -239,42 +239,52 @@ static int huawei_wmi_cmd(struct device *dev, u64 arg, u8 *buf, size_t buflen)
 static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev,
 		enum led_brightness brightness)
 {
-	char *acpi_method;
-	acpi_handle handle;
-	acpi_status status;
-	union acpi_object args[3];
-	struct acpi_object_list arg_list = {
-		.pointer = args,
-		.count = ARRAY_SIZE(args),
-	};
-
-	handle = ec_get_handle();
-	if (!handle) {
-		dev_err(led_cdev->dev->parent, "Failed to get EC handle\n");
-		return -ENODEV;
-	}
+	/* This is a workaround until the "legacy" interface is implemented. */
+	if (quirks && quirks->ec_micmute) {
+		char *acpi_method;
+		acpi_handle handle;
+		acpi_status status;
+		union acpi_object args[3];
+		struct acpi_object_list arg_list = {
+			.pointer = args,
+			.count = ARRAY_SIZE(args),
+		};
+
+		handle = ec_get_handle();
+		if (!handle) {
+			dev_err(led_cdev->dev->parent, "Failed to get EC handle\n");
+			return -ENODEV;
+		}
 
-	args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
-	args[1].integer.value = 0x04;
-
-	if (acpi_has_method(handle, "SPIN")) {
-		acpi_method = "SPIN";
-		args[0].integer.value = 0;
-		args[2].integer.value = brightness ? 1 : 0;
-	} else if (acpi_has_method(handle, "WPIN")) {
-		acpi_method = "WPIN";
-		args[0].integer.value = 1;
-		args[2].integer.value = brightness ? 0 : 1;
+		args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER;
+		args[1].integer.value = 0x04;
+
+		if (acpi_has_method(handle, "SPIN")) {
+			acpi_method = "SPIN";
+			args[0].integer.value = 0;
+			args[2].integer.value = brightness ? 1 : 0;
+		} else if (acpi_has_method(handle, "WPIN")) {
+			acpi_method = "WPIN";
+			args[0].integer.value = 1;
+			args[2].integer.value = brightness ? 0 : 1;
+		} else {
+			return -ENODEV;
+		}
+
+		status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
+		if (ACPI_FAILURE(status)) {
+			return -ENODEV;
+		}
+
+		return 0;
 	} else {
-		return -ENODEV;
-	}
+		u8 arg[8];
 
-	status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL);
-	if (ACPI_FAILURE(status)) {
-		return -ENODEV;
-	}
+		*(u64 *)arg = MICMUTE_LED_SET;
+		arg[2] = brightness;
 
-	return 0;
+		return huawei_wmi_cmd(led_cdev->dev->parent, *(u64 *)arg, NULL, NULL);
+	}
 }
 
 static int huawei_wmi_leds_setup(struct device *dev)
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 5/8] platform/x86: huawei-wmi: add battery charging protection support
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (4 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 4/8] platform/x86: huawei-wmi: control micmute LED through WMI interface Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 6/8] platform/x86: huawei-wmi: add fn-lock support Ayman Bagabas
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

Some models that implement the new WMI management interface can control
battery charging thresholds where it limits charging the battery once it
reaches certain thresholds. This feature is not present in MateBook X
(2017).

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 56 +++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 358d9d168300..06d83e613504 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/delay.h>
 #include <linux/dmi.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
@@ -48,6 +49,7 @@ struct huawei_wmi {
 	struct led_classdev cdev;
 	struct input_dev *idev[2];
 	struct mutex wmi_lock;
+	struct mutex battery_lock;
 	struct platform_device *pdev;
 };
 
@@ -302,6 +304,59 @@ static int huawei_wmi_leds_setup(struct device *dev)
 	return devm_led_classdev_register(dev, &huawei->cdev);
 }
 
+/* Battery protection */
+
+static int huawei_wmi_battery_get(struct device *dev, int *low, int *high)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	u8 ret[0x100];
+	int err, i;
+
+	mutex_lock(&huawei->battery_lock);
+	err = huawei_wmi_cmd(dev, BATTERY_THRESH_GET, ret, 0x100);
+	mutex_unlock(&huawei->battery_lock);
+	if (err) {
+		return err;
+	}
+
+	/* Find the last two non-zero values. Return status is ignored. */
+	i = 0x100;
+	do {
+		*low = ret[i-1];
+		*high = ret[i];
+	} while (i > 2 && !ret[i--]);
+
+	return 0;
+}
+
+static int huawei_wmi_battery_set(struct device *dev, int low, int high)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	u8 arg[8];
+	int err;
+
+	*(u64 *)arg = BATTERY_THRESH_SET;
+	arg[2] = low;
+	arg[3] = high;
+
+	/* This is an edge case were some models turn battery protection
+	 * off without changing their thresholds values. We clear the
+	 * values before turning off protection. Sometimes we need a sleep delay to
+	 * make sure these values make their way to EC memory.
+	 */
+	if (low == 0 && high == 100) {
+		huawei_wmi_battery_set(dev, 0, 0);
+	}
+
+	mutex_lock(&huawei->battery_lock);
+	err = huawei_wmi_cmd(dev, *(u64 *)arg, NULL, NULL);
+	if (quirks && quirks->battery_sleep)
+		msleep(1000);
+	mutex_unlock(&huawei->battery_lock);
+
+	return err;
+}
+
 /* Input */
 
 static void huawei_wmi_process_key(struct input_dev *idev, int code)
@@ -424,6 +479,7 @@ static int huawei_wmi_probe(struct platform_device *pdev)
 
 	if (wmi_has_guid(HWMI_METHOD_GUID)) {
 		mutex_init(&huawei->wmi_lock);
+		mutex_init(&huawei->battery_lock);
 		err = huawei_wmi_leds_setup(&pdev->dev);
 		if (err)
 			dev_err(&pdev->dev, "Failed to setup leds\n");
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 6/8] platform/x86: huawei-wmi: add fn-lock support
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (5 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 5/8] platform/x86: huawei-wmi: add battery charging protection support Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 7/8] platform/x86: huawei-wmi: add sysfs interface support Ayman Bagabas
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

The behavior of hotkeys is not the same among all models. Some models
require fn-lock to do things like `Ctrl-Ins` or `Alt-PrtSc`. By default,
hotkeys behave as special keys (media keys, Ins, etc), but if a modifier
is used (ctrl, alt, shift) these keys behave as F1-F12 keys. If the Fn
key is toggled on, the hotkeys with or without a modifier, behave as
F1-F12 keys. This makes it impossible to use a modifier and `PrtSc` or
`Ins`.

Now, some models fix this by excluding `PrtSc` and `Ins` keys from
being treated as F11 and F12 keys with the use of a modifier. However,
some models do not, and fixes this by the so called fn-lock.

Fn-lock inverts the behavior of the top row from special keys to F1-F12
keys. So a modifier and a special key would be possible which make
things like `Alt-Ins` possible. Now, with fn-lock we would have 4 modes:
* Fn-key off & fn-lock off - hotkeys treated as special keys using a
  modifier gives F1-F12 keys.
* Fn-key on & fn-lock off - hotkeys treated as F1-F12 keys and using a
  modifier gives F1-F12.
* Fn-key off & fn-lock on - hotkeys are treated as F1-F12 keys and using
  a modifier gives special keys.
* Fn-key on & fn-lock on - hotkeys are treated as special keys and using
  a modifier gives special keys.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index 06d83e613504..aac9b80f9976 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -357,6 +357,37 @@ static int huawei_wmi_battery_set(struct device *dev, int low, int high)
 	return err;
 }
 
+/* Fn lock */
+
+static int huawei_wmi_fn_lock_get(struct device *dev, int *on)
+{
+	u8 ret[0x100] = { 0 };
+	int err, i;
+
+	err = huawei_wmi_cmd(dev, FN_LOCK_GET, ret, 0x100);
+	if (err) {
+		return err;
+	}
+
+	/* Find the first non-zero value. Return status is ignored. */
+	i = 1;
+	do {
+		*on = ret[i] - 1; // -1 undefined, 0 off, 1 on.
+	} while (i < 0x100 && !ret[i++]);
+
+	return 0;
+}
+
+static int huawei_wmi_fn_lock_set(struct device *dev, int on)
+{
+	u8 arg[8];
+
+	*(u64 *)arg = FN_LOCK_SET;
+	arg[2] = on + 1; // 0 undefined, 1 off, 2 on.
+
+	return huawei_wmi_cmd(dev, *(u64 *)arg, NULL, NULL);
+}
+
 /* Input */
 
 static void huawei_wmi_process_key(struct input_dev *idev, int code)
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 7/8] platform/x86: huawei-wmi: add sysfs interface support
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (6 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 6/8] platform/x86: huawei-wmi: add fn-lock support Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-13  3:04 ` [PATCH v2 8/8] platform/x86: huawei-wmi: add debugfs files support Ayman Bagabas
  2019-06-29 14:27 ` [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Andy Shevchenko
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

Battery charging thresholds and fn-lock are implemented as sysfs
attributes. Both have R/W permissions and set with root permission.
Although using Huawei Management Software in Windows gives access to
these features without admin privileges, user could use something like a
udev rule to change writing permissions of these attributes.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 87 +++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index aac9b80f9976..cc6745ff1bad 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -14,6 +14,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
+#include <linux/sysfs.h>
 #include <linux/wmi.h>
 
 /*
@@ -388,6 +389,80 @@ static int huawei_wmi_fn_lock_set(struct device *dev, int on)
 	return huawei_wmi_cmd(dev, *(u64 *)arg, NULL, NULL);
 }
 
+/* sysfs */
+
+static ssize_t charge_thresholds_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	int err, low, high;
+
+	err = huawei_wmi_battery_get(dev, &low, &high);
+	if (err)
+		return err;
+
+	return sprintf(buf, "%d %d\n", low, high);
+}
+
+static ssize_t charge_thresholds_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	int low, high, err;
+
+	if (sscanf(buf, "%d %d", &low, &high) != 2 ||
+			low < 0 || high > 100 ||
+			low > high)
+		return -EINVAL;
+
+	err = huawei_wmi_battery_set(dev, low, high);
+	if (err)
+		return err;
+
+	return size;
+}
+
+static ssize_t fn_lock_state_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	int err, on;
+
+	err = huawei_wmi_fn_lock_get(dev, &on);
+	if (err)
+		return err;
+
+	return sprintf(buf, "%d\n", on);
+}
+
+static ssize_t fn_lock_state_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	int on, err;
+
+	if (kstrtoint(buf, 10, &on) ||
+			on < 0 || on > 1)
+		return -EINVAL;
+
+	err = huawei_wmi_fn_lock_set(dev, on);
+	if (err)
+		return err;
+
+	return size;
+}
+
+static DEVICE_ATTR_RW(charge_thresholds);
+static DEVICE_ATTR_RW(fn_lock_state);
+
+static struct attribute *huawei_wmi_attrs[] = {
+	&dev_attr_charge_thresholds.attr,
+	&dev_attr_fn_lock_state.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(huawei_wmi);
+
 /* Input */
 
 static void huawei_wmi_process_key(struct input_dev *idev, int code)
@@ -509,8 +584,16 @@ static int huawei_wmi_probe(struct platform_device *pdev)
 	}
 
 	if (wmi_has_guid(HWMI_METHOD_GUID)) {
+
 		mutex_init(&huawei->wmi_lock);
 		mutex_init(&huawei->battery_lock);
+
+		err = sysfs_create_group(&pdev->dev.kobj, &huawei_wmi_group);
+		if (err) {
+			dev_err(&pdev->dev, "Failed to create sysfs interface\n");
+			return err;
+		}
+
 		err = huawei_wmi_leds_setup(&pdev->dev);
 		if (err)
 			dev_err(&pdev->dev, "Failed to setup leds\n");
@@ -526,6 +609,10 @@ static int huawei_wmi_remove(struct platform_device *pdev)
 	if (wmi_has_guid(HWMI_EVENT_GUID))
 		wmi_remove_notify_handler(HWMI_EVENT_GUID);
 
+	if (wmi_has_guid(HWMI_METHOD_GUID)) {
+		sysfs_remove_group(&pdev->dev.kobj, &huawei_wmi_group);
+	}
+
 	return 0;
 }
 
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 8/8] platform/x86: huawei-wmi: add debugfs files support
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (7 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 7/8] platform/x86: huawei-wmi: add sysfs interface support Ayman Bagabas
@ 2019-06-13  3:04 ` Ayman Bagabas
  2019-06-29 14:27 ` [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Andy Shevchenko
  9 siblings, 0 replies; 12+ messages in thread
From: Ayman Bagabas @ 2019-06-13  3:04 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, platform-driver-x86, linux-kernel
  Cc: ayman.bagabas

A debugfs interface that creates two attributes `arg` and `call` to set
an argument to be called by the WMI device and show the results
returned. This argument is a 64 bit long which complies the properties
of the HWMI interface.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
---
 drivers/platform/x86/huawei-wmi.c | 117 ++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index cc6745ff1bad..a74ddd9adb47 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/dmi.h>
 #include <linux/input.h>
@@ -46,8 +47,14 @@ struct quirk_entry {
 
 static struct quirk_entry *quirks;
 
+struct huawei_wmi_debug {
+	struct dentry *root;
+	u64 arg;
+};
+
 struct huawei_wmi {
 	struct led_classdev cdev;
+	struct huawei_wmi_debug debug;
 	struct input_dev *idev[2];
 	struct mutex wmi_lock;
 	struct mutex battery_lock;
@@ -463,6 +470,110 @@ static struct attribute *huawei_wmi_attrs[] = {
 
 ATTRIBUTE_GROUPS(huawei_wmi);
 
+/* debugfs */
+
+static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data,
+		union acpi_object *obj)
+{
+	struct huawei_wmi *huawei = m->private;
+	int i;
+
+	switch (obj->type) {
+	case ACPI_TYPE_INTEGER:
+		seq_printf(m, "0x%llx", obj->integer.value);
+		break;
+	case ACPI_TYPE_STRING:
+		seq_printf(m, "\"%*s\"", obj->string.length, obj->string.pointer);
+		break;
+	case ACPI_TYPE_BUFFER:
+		seq_printf(m, "{");
+		for (i = 0; i < obj->buffer.length; i++) {
+			seq_printf(m, "0x%02x", obj->buffer.pointer[i]);
+			if (i < obj->buffer.length - 1)
+				seq_printf(m, ",");
+		}
+		seq_printf(m, "}");
+		break;
+	case ACPI_TYPE_PACKAGE:
+		seq_printf(m, "[");
+		for (i = 0; i < obj->package.count; i++) {
+			huawei_wmi_debugfs_call_dump(m, huawei, &obj->package.elements[i]);
+			if (i < obj->package.count - 1)
+				seq_printf(m, ",");
+		}
+		seq_printf(m, "]");
+		break;
+	default:
+		dev_err(&huawei->pdev->dev, "Unexpected obj type, got %d\n", obj->type);
+		return;
+	}
+}
+
+static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data)
+{
+	struct huawei_wmi *huawei = m->private;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer in;
+	union acpi_object *obj;
+	int err;
+
+	in.length = sizeof(u64);
+	in.pointer = &huawei->debug.arg;
+
+	err = huawei_wmi_call(&huawei->pdev->dev, &in, &out);
+	if (err)
+		return err;
+
+	obj = out.pointer;
+	if (!obj) {
+		err = -EIO;
+		goto fail_debugfs_call;
+	}
+
+	huawei_wmi_debugfs_call_dump(m, huawei, obj);
+
+fail_debugfs_call:
+	kfree(out.pointer);
+	return err;
+}
+
+DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call);
+
+static void huawei_wmi_debugfs_exit(struct device *dev)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+
+	debugfs_remove_recursive(huawei->debug.root);
+}
+
+static int huawei_wmi_debugfs_init(struct device *dev)
+{
+	struct huawei_wmi *huawei = dev_get_drvdata(dev);
+	struct dentry *dent;
+
+	huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL);
+	if (!huawei->debug.root) {
+		dev_err(dev, "Failed to create debugfs directory\n");
+		goto fail_debugfs;
+	}
+
+	dent = debugfs_create_x64("arg", S_IRUGO | S_IWUSR, huawei->debug.root,
+			&huawei->debug.arg);
+	if (!dent)
+		goto fail_debugfs;
+
+	dent = debugfs_create_file("call", S_IFREG | S_IRUSR,
+			huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops);
+	if (!dent)
+		goto fail_debugfs;
+
+	return 0;
+
+fail_debugfs:
+	huawei_wmi_debugfs_exit(dev);
+	return -ENOMEM;
+}
+
 /* Input */
 
 static void huawei_wmi_process_key(struct input_dev *idev, int code)
@@ -597,7 +708,12 @@ static int huawei_wmi_probe(struct platform_device *pdev)
 		err = huawei_wmi_leds_setup(&pdev->dev);
 		if (err)
 			dev_err(&pdev->dev, "Failed to setup leds\n");
+
+		err = huawei_wmi_debugfs_init(&pdev->dev);
+		if (err)
+			dev_err(&pdev->dev, "Failed to setup debugfs\n");
 	}
+
 	return 0;
 }
 
@@ -610,6 +726,7 @@ static int huawei_wmi_remove(struct platform_device *pdev)
 		wmi_remove_notify_handler(HWMI_EVENT_GUID);
 
 	if (wmi_has_guid(HWMI_METHOD_GUID)) {
+		huawei_wmi_debugfs_exit(&pdev->dev);
 		sysfs_remove_group(&pdev->dev.kobj, &huawei_wmi_group);
 	}
 
-- 
2.20.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver
  2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
                   ` (8 preceding siblings ...)
  2019-06-13  3:04 ` [PATCH v2 8/8] platform/x86: huawei-wmi: add debugfs files support Ayman Bagabas
@ 2019-06-29 14:27 ` Andy Shevchenko
  2019-06-30 17:49   ` ayman.bagabas
  9 siblings, 1 reply; 12+ messages in thread
From: Andy Shevchenko @ 2019-06-29 14:27 UTC (permalink / raw)
  To: Ayman Bagabas
  Cc: Darren Hart, Andy Shevchenko, Platform Driver, Linux Kernel Mailing List

On Thu, Jun 13, 2019 at 6:04 AM Ayman Bagabas <ayman.bagabas@gmail.com> wrote:
>
> Changes from v1:
> * introducing debugfs
> * code reformatting
>
> This patch series introduce new features to the driver and also moves the
> driver from wmi_driver to platform_driver. This move is necessary because the
> driver is no longer only a hotkeys driver and platform_driver offers easier
> future extensibility.
>
> The patch series introduces a WMI BIOS interface that brings on new features
> and enables controlling micmute LED through this interface on supported models.
> It also enables controlling battery charging thresholds and fn-lock state.
> These features are controlled through the HWMI WMI device present in most of
> these laptops.
>
> Currently, micmute LED is controlled through an ACPI method under EC.
> This method ("SPIN", "WPIN") is specific to some models and wouldn't
> work on all Huawei laptops. Controlling this LED through the interface provides
> a better unified method to control the LED on models that implements this
> feature.
>
> The behavior of hotkeys is not the same among all models. Some models
> require fn-lock to do things like `Ctrl-Ins` or `Alt-PrtSc`. Fn-lock inverts the
> behavior of the top row from special keys to F1-F12 keys.
>
> A debugfs interface is also implemented to support unrepresented features and to
> provide debugging feedback from users.

You have sent few series regarding Huawei devices.
And from the patch 1 this is not applicable.
So, I dropped all your patches from patchwork queue and will wait for
new versions in order they are applicable.

>
> Ayman Bagabas (8):
>   platform/x86: huawei-wmi: move to platform driver
>   platform/x86: huawei-wmi: implement WMI management interface
>   platform/x86: huawei-wmi: use quirks and module parameters
>   platform/x86: huawei-wmi: control micmute LED through WMI interface
>   platform/x86: huawei-wmi: add battery charging protection support
>   platform/x86: huawei-wmi: add fn-lock support
>   platform/x86: huawei-wmi: add sysfs interface support
>   platform/x86: huawei-wmi: add debugfs files support
>
>  drivers/platform/x86/huawei-wmi.c | 754 ++++++++++++++++++++++++++----
>  1 file changed, 665 insertions(+), 89 deletions(-)
>
> --
> 2.20.1
>


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver
  2019-06-29 14:27 ` [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Andy Shevchenko
@ 2019-06-30 17:49   ` ayman.bagabas
  0 siblings, 0 replies; 12+ messages in thread
From: ayman.bagabas @ 2019-06-30 17:49 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Darren Hart, Andy Shevchenko, Platform Driver, Linux Kernel Mailing List

On Sat, 2019-06-29 at 17:27 +0300, Andy Shevchenko wrote:
> On Thu, Jun 13, 2019 at 6:04 AM Ayman Bagabas <
> ayman.bagabas@gmail.com> wrote:
> > Changes from v1:
> > * introducing debugfs
> > * code reformatting
> > 
> > This patch series introduce new features to the driver and also
> > moves the
> > driver from wmi_driver to platform_driver. This move is necessary
> > because the
> > driver is no longer only a hotkeys driver and platform_driver
> > offers easier
> > future extensibility.
> > 
> > The patch series introduces a WMI BIOS interface that brings on new
> > features
> > and enables controlling micmute LED through this interface on
> > supported models.
> > It also enables controlling battery charging thresholds and fn-lock 
> > state.
> > These features are controlled through the HWMI WMI device present
> > in most of
> > these laptops.
> > 
> > Currently, micmute LED is controlled through an ACPI method under
> > EC.
> > This method ("SPIN", "WPIN") is specific to some models and
> > wouldn't
> > work on all Huawei laptops. Controlling this LED through the
> > interface provides
> > a better unified method to control the LED on models that
> > implements this
> > feature.
> > 
> > The behavior of hotkeys is not the same among all models. Some
> > models
> > require fn-lock to do things like `Ctrl-Ins` or `Alt-PrtSc`. Fn-
> > lock inverts the
> > behavior of the top row from special keys to F1-F12 keys.
> > 
> > A debugfs interface is also implemented to support unrepresented
> > features and to
> > provide debugging feedback from users.
> 
> You have sent few series regarding Huawei devices.
> And from the patch 1 this is not applicable.
> So, I dropped all your patches from patchwork queue and will wait for
> new versions in order they are applicable.

I've sent a newer version and rewrote commit messages and cover page.
I've also changed the tag to RFC.

Thank you,
Ayman

> 
> > Ayman Bagabas (8):
> >   platform/x86: huawei-wmi: move to platform driver
> >   platform/x86: huawei-wmi: implement WMI management interface
> >   platform/x86: huawei-wmi: use quirks and module parameters
> >   platform/x86: huawei-wmi: control micmute LED through WMI
> > interface
> >   platform/x86: huawei-wmi: add battery charging protection support
> >   platform/x86: huawei-wmi: add fn-lock support
> >   platform/x86: huawei-wmi: add sysfs interface support
> >   platform/x86: huawei-wmi: add debugfs files support
> > 
> >  drivers/platform/x86/huawei-wmi.c | 754
> > ++++++++++++++++++++++++++----
> >  1 file changed, 665 insertions(+), 89 deletions(-)
> > 
> > --
> > 2.20.1
> > 
> 
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2019-06-30 17:50 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-13  3:04 [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver Ayman Bagabas
2019-06-13  3:04 ` [PATCH v1] platform/x86: Huawei laptop extras driver Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 2/8] platform/x86: huawei-wmi: implement WMI management interface Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 3/8] platform/x86: huawei-wmi: use quirks and module parameters Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 4/8] platform/x86: huawei-wmi: control micmute LED through WMI interface Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 5/8] platform/x86: huawei-wmi: add battery charging protection support Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 6/8] platform/x86: huawei-wmi: add fn-lock support Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 7/8] platform/x86: huawei-wmi: add sysfs interface support Ayman Bagabas
2019-06-13  3:04 ` [PATCH v2 8/8] platform/x86: huawei-wmi: add debugfs files support Ayman Bagabas
2019-06-29 14:27 ` [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver Andy Shevchenko
2019-06-30 17:49   ` ayman.bagabas

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).