[PATCH 3/3] platform/x86: huawei-wmi: Remove unnecessary battery mutex
battery_lock mutex is never used and not needed. Fixes: 355a070b09ab ("platform/x86: huawei-wmi: Add battery charging thresholds") Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 7373a65a61d3..a2d846c4a7ee 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -67,7 +67,6 @@ struct huawei_wmi { struct led_classdev cdev; struct device *dev; - struct mutex battery_lock; struct mutex wmi_lock; }; @@ -807,7 +806,6 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&huawei_wmi->wmi_lock); - mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); -- 2.21.0
[PATCH 2/3] platform/x86: huawei-wmi: No need to check for battery name
No need to check for battery name, we already check if the WMI function is available in huawei_wmi_battery_setup. Fixes: 355a070b09ab ("platform/x86: huawei-wmi: Add battery charging thresholds") Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 4 1 file changed, 4 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 26041d44286a..7373a65a61d3 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -471,10 +471,6 @@ static DEVICE_ATTR_RW(charge_control_thresholds); static int huawei_wmi_battery_add(struct power_supply *battery) { - /* Huawei laptops come with one battery only */ - if (strcmp(battery->desc->name, "BAT") != 1) - return -ENODEV; - device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); -- 2.21.0
[PATCH 1/3] platform/x86: huawei-wmi: Stricter battery thresholds set
Check if battery thresholds are within 0 and 100. Fixes: 355a070b09ab ("platform/x86: huawei-wmi: Add battery charging thresholds") Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 5837d1b8693d..26041d44286a 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -345,7 +345,7 @@ static int huawei_wmi_battery_set(int start, int end) union hwmi_arg arg; int err; - if (start < 0 || end > 100) + if (start < 0 || end < 0 || start > 100 || end > 100) return -EINVAL; arg.cmd = BATTERY_THRESH_SET; base-commit: c4b9850b3676869ac0def5885d781d17f64b3a86 -- 2.21.0
[PATCH] platform/x86: huawei-wmi: Remove unnecessary battery mutex
battery_lock mutex is never used and not needed. Fixes: 355a070b09ab ("platform/x86: huawei-wmi: Add battery charging thresholds") Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 7373a65a61d3..a2d846c4a7ee 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -67,7 +67,6 @@ struct huawei_wmi { struct led_classdev cdev; struct device *dev; - struct mutex battery_lock; struct mutex wmi_lock; }; @@ -807,7 +806,6 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&huawei_wmi->wmi_lock); - mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); base-commit: fd13c8622a5ad4f7317b64de4f6aa2de1962220e prerequisite-patch-id: 7639f479e344d2a1be074f93dc5e5530611928f9 prerequisite-patch-id: ad5932635d0c0d531a99e15e239f81ecc69b47d6 -- 2.21.0
Re: [PATCH 2/2] platform/x86: huawei-wmi: No need to check for battery name
On Fri, 2019-10-18 at 18:42 -0400, Ayman Bagabas wrote: > No need to check for battery name, we already check if the WMI > function is > available in huawei_wmi_battery_setup. Signed-off-by: Ayman Bagabas Thank you, Ayman > --- > drivers/platform/x86/huawei-wmi.c | 4 > 1 file changed, 4 deletions(-) > > diff --git a/drivers/platform/x86/huawei-wmi.c > b/drivers/platform/x86/huawei-wmi.c > index 26041d44286a..7373a65a61d3 100644 > --- a/drivers/platform/x86/huawei-wmi.c > +++ b/drivers/platform/x86/huawei-wmi.c > @@ -471,10 +471,6 @@ static > DEVICE_ATTR_RW(charge_control_thresholds); > > static int huawei_wmi_battery_add(struct power_supply *battery) > { > - /* Huawei laptops come with one battery only */ > - if (strcmp(battery->desc->name, "BAT") != 1) > - return -ENODEV; > - > device_create_file(&battery->dev, > &dev_attr_charge_control_start_threshold); > device_create_file(&battery->dev, > &dev_attr_charge_control_end_threshold); >
Re: [PATCH 1/2] platform/x86: huawei-wmi: Stricter battery thresholds set
On Sat, 2019-10-19 at 11:31 +0300, Dan Carpenter wrote: > On Fri, Oct 18, 2019 at 06:42:13PM -0400, Ayman Bagabas wrote: > > Check if battery thresholds are within 0 and 100. > > --- > > Thanks! > > Don't forget to add your Signed-off-by: though. Signed-off-by: Ayman Bagabas Thank you, Ayman > > regards, > dan carpenter >
[PATCH 1/2] platform/x86: huawei-wmi: Stricter battery thresholds set
Check if battery thresholds are within 0 and 100. --- drivers/platform/x86/huawei-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 5837d1b8693d..26041d44286a 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -345,7 +345,7 @@ static int huawei_wmi_battery_set(int start, int end) union hwmi_arg arg; int err; - if (start < 0 || end > 100) + if (start < 0 || end < 0 || start > 100 || end > 100) return -EINVAL; arg.cmd = BATTERY_THRESH_SET; base-commit: fd13c8622a5ad4f7317b64de4f6aa2de1962220e -- 2.21.0
[PATCH 2/2] platform/x86: huawei-wmi: No need to check for battery name
No need to check for battery name, we already check if the WMI function is available in huawei_wmi_battery_setup. --- drivers/platform/x86/huawei-wmi.c | 4 1 file changed, 4 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 26041d44286a..7373a65a61d3 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -471,10 +471,6 @@ static DEVICE_ATTR_RW(charge_control_thresholds); static int huawei_wmi_battery_add(struct power_supply *battery) { - /* Huawei laptops come with one battery only */ - if (strcmp(battery->desc->name, "BAT") != 1) - return -ENODEV; - device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); -- 2.21.0
Re: [PATCH] platform/x86: huawei-wmi: Fix misuse of strcmp() function
Hi Gustavo, On Tue, 2019-10-15 at 13:25 -0500, Gustavo A. R. Silva wrote: > Comparing the result of strcmp directly with 1 may cause it to be > misinterpreted. Note that strcmp may return an integer besides -1, > 0, or 1. > > Fix this by returning -ENODEV only when strcmp() returns a value > other than 0. > > Addresses-Coverity-ID: 1487035 ("Misuse of memcmp-style function") > Fixes: b7527d0f4502 ("platform/x86: huawei-wmi: Add battery charging > thresholds") > Signed-off-by: Gustavo A. R. Silva > --- > drivers/platform/x86/huawei-wmi.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/drivers/platform/x86/huawei-wmi.c > b/drivers/platform/x86/huawei-wmi.c > index 6720f78c60c2..b43f76acbfea 100644 > --- a/drivers/platform/x86/huawei-wmi.c > +++ b/drivers/platform/x86/huawei-wmi.c > @@ -470,7 +470,7 @@ static DEVICE_ATTR_RW(charge_control_thresholds); > static int huawei_wmi_battery_add(struct power_supply *battery) > { > /* Huawei laptops come with one battery only */ > - if (strcmp(battery->desc->name, "BAT") != 1) Note we don't have a battery number in BATx, strcmp would return 1 if battery->desc->name is "BAT0" or any one digit. This is a desired behavior where some Huawei laptops identify the first battery as "BAT1" and this would match if name is greater than "BAT" by one digit. Maybe strcmp(battery->desc->name, "BAT") < 0 is a better way to go. > + if (strcmp(battery->desc->name, "BAT")) > return -ENODEV; Now this would always return ENODEV. Thank you, Ayman > > device_create_file(&battery->dev, > &dev_attr_charge_control_start_threshold);
Re: [PATCH] platform/x86: huawei-wmi: make validation stricter in huawei_wmi_battery_set()
On Tue, 2019-10-15 at 15:46 +0300, Dan Carpenter wrote: > On Tue, Oct 15, 2019 at 08:21:59AM -0400, Ayman Bagabas wrote: > > Hi Dan > > > > On Tue, Oct 15, 2019, 4:39 AM Dan Carpenter < > > dan.carpen...@oracle.com> > > wrote: > > > > > I don't think it makes sense for "end" to be negative or for even > > > for it > > > to be less than "start". That also means that "start" can't be > > > more > > > than 100 which is good. > > > > > > > While this makes sense, you run into issues where you cannot set > > "start" > > before "end" and vice versa. > > > > Take this scenario, you have start=70 and end=90, now you want to > > set these > > to start=40 and end=60, you would have to set "start" first before > > you can > > change the value of "end" otherwise you will run into EINVAL. Now > > imagine > > you wanna go the opposite direction, you would have to set "end" > > before you > > can change "start". > > I think having a little wiggle room is fine for such scenarios. > > > > I haven't tested this code... What you're describing sounds really > very weird to me, but I will accept that you know more about your > use cases than I do. > > My other concern is that right now you can set start > 100 or end < > 0. We should check for these cases. > > regards, > dan carpenter >
[PATCH v5 5/6] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key and the F1-F12 row. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 85 +++ 1 file changed, 85 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 6e791f9ffc26..d550c63fcba7 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -54,6 +54,7 @@ static struct quirk_entry *quirks; struct huawei_wmi { bool battery_available; + bool fn_lock_available; struct input_dev *idev[2]; struct led_classdev cdev; @@ -509,6 +510,88 @@ static void huawei_wmi_battery_exit(struct device *dev) } } +/* Fn lock */ + +static int huawei_wmi_fn_lock_get(int *on) +{ + u8 ret[0x100] = { 0 }; + int err, i; + + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; + + /* Find the first non-zero value. Return status is ignored. */ + i = 1; + do { + if (on) + *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. + } while (i < 0xff && !ret[i++]); + + return 0; +} + +static int huawei_wmi_fn_lock_set(int on) +{ + union hwmi_arg arg; + + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + + return huawei_wmi_cmd(arg.cmd, NULL, 0); +} + +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(&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(on); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(fn_lock_state); + +static void huawei_wmi_fn_lock_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->fn_lock_available = true; + if (huawei_wmi_fn_lock_get(NULL)) { + huawei->fn_lock_available = false; + return; + } + + device_create_file(dev, &dev_attr_fn_lock_state); +} + +static void huawei_wmi_fn_lock_exit(struct device *dev) +{ + if (huawei_wmi->fn_lock_available) + device_remove_file(dev, &dev_attr_fn_lock_state); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -632,6 +715,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); + huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); } @@ -651,6 +735,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { huawei_wmi_battery_exit(&pdev->dev); + huawei_wmi_fn_lock_exit(&pdev->dev); } return 0; -- 2.21.0
[PATCH v5 6/6] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function if available. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 97 +++ 1 file changed, 97 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index d550c63fcba7..6720f78c60c2 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -52,10 +53,16 @@ struct quirk_entry { static struct quirk_entry *quirks; +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi { bool battery_available; bool fn_lock_available; + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; @@ -592,6 +599,94 @@ static void huawei_wmi_fn_lock_exit(struct device *dev) device_remove_file(dev, &dev_attr_fn_lock_state); } +/* 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(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_puts(m, ","); + } + seq_puts(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(&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_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, huawei->debug.root, + &huawei->debug.arg); + debugfs_create_file("call", 0400, + huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops); +} + +static void huawei_wmi_debugfs_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + debugfs_remove_recursive(huawei->debug.root); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -717,6 +812,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -734,6 +830,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) } if (wmi_has_guid(HWMI_METHOD_GUID)) { + huawei_wmi_debugfs_exit(&pdev->dev); huawei_wmi_battery_exit(&pdev->dev); huawei_wmi_fn_lock_exit(&pdev->dev); } -- 2.21.0
[PATCH v5 4/6] platform/x86: huawei-wmi: Add battery charging thresholds
Control battery charge thresholds through the battery API and driver's attributes. Setting battery charging thresholds can introduce a race condition with MACH-WX9 where two or more threads are trying to read/write values from/to EC memory. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/huawei-wmi.c | 212 ++ 2 files changed, 213 insertions(+) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 754405e19ef1..e64103789885 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1306,6 +1306,7 @@ config INTEL_ATOMISP2_PM config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 41904b1cc284..6e791f9ffc26 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -13,7 +14,10 @@ #include #include #include +#include +#include #include +#include /* * Huawei WMI GUIDs @@ -49,10 +53,13 @@ struct quirk_entry { static struct quirk_entry *quirks; struct huawei_wmi { + bool battery_available; + struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; + struct mutex battery_lock; struct mutex wmi_lock; }; @@ -300,6 +307,208 @@ static void huawei_wmi_leds_setup(struct device *dev) devm_led_classdev_register(dev, &huawei->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(int *start, int *end) +{ + u8 ret[0x100]; + int err, i; + + err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100); + if (err) + return err; + + /* Find the last two non-zero values. Return status is ignored. */ + i = 0xff; + do { + if (start) + *start = ret[i-1]; + if (end) + *end = ret[i]; + } while (i > 2 && !ret[i--]); + + return 0; +} + +static int huawei_wmi_battery_set(int start, int end) +{ + union hwmi_arg arg; + int err; + + if (start < 0 || end > 100) + return -EINVAL; + + arg.cmd = BATTERY_THRESH_SET; + arg.args[2] = start; + arg.args[3] = end; + + /* 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 (quirks && quirks->battery_reset && start == 0 && end == 100) { + err = huawei_wmi_battery_set(0, 0); + if (err) + return err; + + msleep(1000); + } + + err = huawei_wmi_cmd(arg.cmd, NULL, 0); + + return err; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + return sprintf(buf, "%d\n", start); +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + return sprintf(buf, "%d\n", end); +} + +static ssize_t charge_control_thresholds_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, &end); + if (err) + return err; + + return sprintf(buf, "%d %d\n", start, end); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + if (sscanf(buf, "%d", &start) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, NULL);
[PATCH v5 1/6] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 7 +- drivers/platform/x86/huawei-wmi.c | 226 -- 2 files changed, 156 insertions(+), 77 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ae21d08c65e8..754405e19ef1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1305,7 +1305,7 @@ config INTEL_ATOMISP2_PM will be called intel_atomisp2_pm. config HUAWEI_WMI - tristate "Huawei WMI hotkeys driver" + tristate "Huawei WMI laptop extras driver" depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1314,9 +1314,8 @@ config HUAWEI_WMI select LEDS_TRIGGER_AUDIO select NEW_LEDS help - This driver provides support for Huawei WMI hotkeys. - It enables the missing keys and adds support to the micmute - LED found on some of these laptops. + This driver provides support for Huawei WMI hotkeys, battery charge + control, fn-lock, mic-mute LED, and other extra features. To compile this driver as a module, choose M here: the module will be called huawei-wmi. diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 195a7f3638cb..9496ea3c78b5 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 */ @@ -10,23 +10,28 @@ #include #include #include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 input_dev *idev[2]; struct led_classdev cdev; + struct platform_device *pdev; acpi_handle handle; char *acpi_method; }; +struct huawei_wmi *huawei_wmi; + static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, @@ -37,7 +42,7 @@ 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 } }, @@ -47,7 +52,7 @@ static const struct key_entry huawei_wmi_keymap[] = { 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); + struct huawei_wmi *huawei = dev_get_drvdata(led_cdev->dev->parent); acpi_status status; union acpi_object args[3]; struct acpi_object_list arg_list = { @@ -58,52 +63,53 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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 (strcmp(huawei->acpi_method, "SPIN") == 0) { args[0].integer.value = 0; args[2].integer.value = brightness ? 1 : 0; - } else if (strcmp(priv->acpi_method, "WPIN") == 0) { + } else if (strcmp(huawei->acpi_method, "WPIN") == 0) { args[0].integer.value = 1; args[2].integer.value = brightness ? 0 : 1; } else { return -EINVAL; } - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); + status = acpi_evaluate_object(huawei->handle, huawei->acpi_method, &arg_list, NULL); if (ACPI_FAILURE(status)) return -ENXIO; return 0; } -static int huawei_wmi_leds_setup(struct wmi_device *wdev) +static void huawei_wmi_leds_setup(struct device *dev) { - struct huaw
[PATCH v5 0/6] platform/x86: Huawei WMI laptop extras driver
Changes in v5: * Fix a possible buffer overflow error. Changes in v4: * Use int and bint for module params. Changes in v3: * Kconfig changes * Fix NULL cast to int warning. * Add ACPI_BATTERY as a dependency. Changes in v2: * Use battery charge control API. This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement driver quirks and parameters * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (6): platform/x86: huawei-wmi: Move to platform driver platform/x86: huawei-wmi: Add quirks and module parameters platform/x86: huawei-wmi: Implement huawei wmi management platform/x86: huawei-wmi: Add battery charging thresholds platform/x86: huawei-wmi: Add fn-lock support platform/x86: huawei-wmi: Add debugfs support drivers/platform/x86/Kconfig | 8 +- drivers/platform/x86/huawei-wmi.c | 874 ++ 2 files changed, 786 insertions(+), 96 deletions(-) base-commit: b5b3bd898ba99fb0fb6aed3b23ec6353a1724d6f -- 2.21.0
[PATCH v5 2/6] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. (will be used later) 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. (will be used later) 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 73 +++ 1 file changed, 73 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9496ea3c78b5..0e919bf56ad1 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -22,6 +23,14 @@ #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +struct quirk_entry { + bool battery_reset; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; @@ -49,6 +58,58 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +static int battery_reset = -1; +static int report_brightness = -1; + +module_param(battery_reset, bint, 0444); +MODULE_PARM_DESC(battery_reset, + "Reset battery charge values to (0-0) before disabling it using (0-100)"); +module_param(report_brightness, bint, 0444); +MODULE_PARM_DESC(report_brightness, + "Report brightness keys."); + +/* 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_reset = { + .battery_reset = 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_reset + }, + { + .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 + }, + { } +}; + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -139,6 +200,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); } @@ -253,6 +319,13 @@ static __init int huawei_wmi_init(void) if (!huawei_wmi) return -ENOMEM; + quirks = &quirk_unknown; + dmi_check_system(huawei_quirks); + if (battery_reset != -1) + quirks->battery_reset = battery_reset; + if (report_brightness != -1) + quirks->report_brightness = report_brightness; + err = platform_driver_register(&huawei_wmi_driver); if (err) goto pdrv_err; -- 2.21.0
[PATCH v5 3/6] platform/x86: huawei-wmi: Implement huawei wmi management
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Use that to control the mic mute LED. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 217 +- 1 file changed, 180 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 0e919bf56ad1..41904b1cc284 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -11,18 +11,35 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; + +union hwmi_arg { + u64 cmd; + u8 args[8]; +}; + struct quirk_entry { bool battery_reset; bool ec_micmute; @@ -35,8 +52,8 @@ struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; - acpi_handle handle; - char *acpi_method; + + struct mutex wmi_lock; }; struct huawei_wmi *huawei_wmi; @@ -110,52 +127,168 @@ static const struct dmi_system_id huawei_quirks[] = { { } }; +/* Utils */ + +static int huawei_wmi_call(struct acpi_buffer *in, struct acpi_buffer *out) +{ + acpi_status status; + + mutex_lock(&huawei_wmi->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&huawei_wmi->wmi_lock); + if (ACPI_FAILURE(status)) { + dev_err(&huawei_wmi->pdev->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(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(arg); + 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(&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(&huawei_wmi->pdev->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(&huawei_wmi->pdev->dev, "Bad package count, got %d\n", obj->package.count); + err = -EIO; +
[PATCH v4 6/6] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function if available. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 97 +++ 1 file changed, 97 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 56697d49377d..5efb8d1bbda7 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -52,10 +53,16 @@ struct quirk_entry { static struct quirk_entry *quirks; +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi { bool battery_available; bool fn_lock_available; + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; @@ -592,6 +599,94 @@ static void huawei_wmi_fn_lock_exit(struct device *dev) device_remove_file(dev, &dev_attr_fn_lock_state); } +/* 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(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_puts(m, ","); + } + seq_puts(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(&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_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, huawei->debug.root, + &huawei->debug.arg); + debugfs_create_file("call", 0400, + huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops); +} + +static void huawei_wmi_debugfs_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + debugfs_remove_recursive(huawei->debug.root); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -717,6 +812,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -734,6 +830,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) } if (wmi_has_guid(HWMI_METHOD_GUID)) { + huawei_wmi_debugfs_exit(&pdev->dev); huawei_wmi_battery_exit(&pdev->dev); huawei_wmi_fn_lock_exit(&pdev->dev); } -- 2.21.0
[PATCH v4 4/6] platform/x86: huawei-wmi: Add battery charging thresholds
Control battery charge thresholds through the battery API and driver's attributes. Setting battery charging thresholds can introduce a race condition with MACH-WX9 where two or more threads are trying to read/write values from/to EC memory. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/huawei-wmi.c | 212 ++ 2 files changed, 213 insertions(+) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 61bf180d25c7..0659589e46bb 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1305,6 +1305,7 @@ config INTEL_ATOMISP2_PM config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 41904b1cc284..e577786e6306 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -13,7 +14,10 @@ #include #include #include +#include +#include #include +#include /* * Huawei WMI GUIDs @@ -49,10 +53,13 @@ struct quirk_entry { static struct quirk_entry *quirks; struct huawei_wmi { + bool battery_available; + struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; + struct mutex battery_lock; struct mutex wmi_lock; }; @@ -300,6 +307,208 @@ static void huawei_wmi_leds_setup(struct device *dev) devm_led_classdev_register(dev, &huawei->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(int *start, int *end) +{ + u8 ret[0x100]; + int err, i; + + err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100); + if (err) + return err; + + /* Find the last two non-zero values. Return status is ignored. */ + i = 0x100; + do { + if (start) + *start = ret[i-1]; + if (end) + *end = ret[i]; + } while (i > 2 && !ret[i--]); + + return 0; +} + +static int huawei_wmi_battery_set(int start, int end) +{ + union hwmi_arg arg; + int err; + + if (start < 0 || end > 100) + return -EINVAL; + + arg.cmd = BATTERY_THRESH_SET; + arg.args[2] = start; + arg.args[3] = end; + + /* 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 (quirks && quirks->battery_reset && start == 0 && end == 100) { + err = huawei_wmi_battery_set(0, 0); + if (err) + return err; + + msleep(1000); + } + + err = huawei_wmi_cmd(arg.cmd, NULL, 0); + + return err; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + return sprintf(buf, "%d\n", start); +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + return sprintf(buf, "%d\n", end); +} + +static ssize_t charge_control_thresholds_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, &end); + if (err) + return err; + + return sprintf(buf, "%d %d\n", start, end); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + if (sscanf(buf, "%d", &start) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, NULL);
[PATCH v4 5/6] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key and the F1-F12 row. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 85 +++ 1 file changed, 85 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index e577786e6306..56697d49377d 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -54,6 +54,7 @@ static struct quirk_entry *quirks; struct huawei_wmi { bool battery_available; + bool fn_lock_available; struct input_dev *idev[2]; struct led_classdev cdev; @@ -509,6 +510,88 @@ static void huawei_wmi_battery_exit(struct device *dev) } } +/* Fn lock */ + +static int huawei_wmi_fn_lock_get(int *on) +{ + u8 ret[0x100] = { 0 }; + int err, i; + + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; + + /* Find the first non-zero value. Return status is ignored. */ + i = 1; + do { + if (on) + *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. + } while (i < 0x100 && !ret[i++]); + + return 0; +} + +static int huawei_wmi_fn_lock_set(int on) +{ + union hwmi_arg arg; + + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + + return huawei_wmi_cmd(arg.cmd, NULL, 0); +} + +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(&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(on); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(fn_lock_state); + +static void huawei_wmi_fn_lock_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->fn_lock_available = true; + if (huawei_wmi_fn_lock_get(NULL)) { + huawei->fn_lock_available = false; + return; + } + + device_create_file(dev, &dev_attr_fn_lock_state); +} + +static void huawei_wmi_fn_lock_exit(struct device *dev) +{ + if (huawei_wmi->fn_lock_available) + device_remove_file(dev, &dev_attr_fn_lock_state); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -632,6 +715,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); + huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); } @@ -651,6 +735,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { huawei_wmi_battery_exit(&pdev->dev); + huawei_wmi_fn_lock_exit(&pdev->dev); } return 0; -- 2.21.0
[PATCH v4 3/6] platform/x86: huawei-wmi: Implement huawei wmi management
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Use that to control the mic mute LED. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 217 +- 1 file changed, 180 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 0e919bf56ad1..41904b1cc284 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -11,18 +11,35 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; + +union hwmi_arg { + u64 cmd; + u8 args[8]; +}; + struct quirk_entry { bool battery_reset; bool ec_micmute; @@ -35,8 +52,8 @@ struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; - acpi_handle handle; - char *acpi_method; + + struct mutex wmi_lock; }; struct huawei_wmi *huawei_wmi; @@ -110,52 +127,168 @@ static const struct dmi_system_id huawei_quirks[] = { { } }; +/* Utils */ + +static int huawei_wmi_call(struct acpi_buffer *in, struct acpi_buffer *out) +{ + acpi_status status; + + mutex_lock(&huawei_wmi->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&huawei_wmi->wmi_lock); + if (ACPI_FAILURE(status)) { + dev_err(&huawei_wmi->pdev->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(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(arg); + 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(&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(&huawei_wmi->pdev->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(&huawei_wmi->pdev->dev, "Bad package count, got %d\n", obj->package.count); + err = -EIO; +
[PATCH v4 2/6] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. (will be used later) 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. (will be used later) 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 73 +++ 1 file changed, 73 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9496ea3c78b5..0e919bf56ad1 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -22,6 +23,14 @@ #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +struct quirk_entry { + bool battery_reset; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; @@ -49,6 +58,58 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +static int battery_reset = -1; +static int report_brightness = -1; + +module_param(battery_reset, bint, 0444); +MODULE_PARM_DESC(battery_reset, + "Reset battery charge values to (0-0) before disabling it using (0-100)"); +module_param(report_brightness, bint, 0444); +MODULE_PARM_DESC(report_brightness, + "Report brightness keys."); + +/* 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_reset = { + .battery_reset = 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_reset + }, + { + .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 + }, + { } +}; + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -139,6 +200,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); } @@ -253,6 +319,13 @@ static __init int huawei_wmi_init(void) if (!huawei_wmi) return -ENOMEM; + quirks = &quirk_unknown; + dmi_check_system(huawei_quirks); + if (battery_reset != -1) + quirks->battery_reset = battery_reset; + if (report_brightness != -1) + quirks->report_brightness = report_brightness; + err = platform_driver_register(&huawei_wmi_driver); if (err) goto pdrv_err; -- 2.21.0
[PATCH v4 1/6] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 7 +- drivers/platform/x86/huawei-wmi.c | 226 -- 2 files changed, 156 insertions(+), 77 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 1b67bb578f9f..61bf180d25c7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1304,7 +1304,7 @@ config INTEL_ATOMISP2_PM will be called intel_atomisp2_pm. config HUAWEI_WMI - tristate "Huawei WMI hotkeys driver" + tristate "Huawei WMI laptop extras driver" depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1313,9 +1313,8 @@ config HUAWEI_WMI select LEDS_TRIGGER_AUDIO select NEW_LEDS help - This driver provides support for Huawei WMI hotkeys. - It enables the missing keys and adds support to the micmute - LED found on some of these laptops. + This driver provides support for Huawei WMI hotkeys, battery charge + control, fn-lock, mic-mute LED, and other extra features. To compile this driver as a module, choose M here: the module will be called huawei-wmi. diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 195a7f3638cb..9496ea3c78b5 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 */ @@ -10,23 +10,28 @@ #include #include #include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 input_dev *idev[2]; struct led_classdev cdev; + struct platform_device *pdev; acpi_handle handle; char *acpi_method; }; +struct huawei_wmi *huawei_wmi; + static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, @@ -37,7 +42,7 @@ 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 } }, @@ -47,7 +52,7 @@ static const struct key_entry huawei_wmi_keymap[] = { 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); + struct huawei_wmi *huawei = dev_get_drvdata(led_cdev->dev->parent); acpi_status status; union acpi_object args[3]; struct acpi_object_list arg_list = { @@ -58,52 +63,53 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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 (strcmp(huawei->acpi_method, "SPIN") == 0) { args[0].integer.value = 0; args[2].integer.value = brightness ? 1 : 0; - } else if (strcmp(priv->acpi_method, "WPIN") == 0) { + } else if (strcmp(huawei->acpi_method, "WPIN") == 0) { args[0].integer.value = 1; args[2].integer.value = brightness ? 0 : 1; } else { return -EINVAL; } - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); + status = acpi_evaluate_object(huawei->handle, huawei->acpi_method, &arg_list, NULL); if (ACPI_FAILURE(status)) return -ENXIO; return 0; } -static int huawei_wmi_leds_setup(struct wmi_device *wdev) +static void huawei_wmi_leds_setup(struct device *dev) { - struct huaw
[PATCH v4 0/6] platform/x86: Huawei WMI laptop extras driver
Changes in v4: * Use int and bint for module params. Changes in v3: * Kconfig changes * Fix NULL cast to int warning. * Add ACPI_BATTERY as a dependency. Changes in v2: * Use battery charge control API. This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement driver quirks and parameters * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (6): platform/x86: huawei-wmi: Move to platform driver platform/x86: huawei-wmi: Add quirks and module parameters platform/x86: huawei-wmi: Implement huawei wmi management platform/x86: huawei-wmi: Add battery charging thresholds platform/x86: huawei-wmi: Add fn-lock support platform/x86: huawei-wmi: Add debugfs support drivers/platform/x86/Kconfig | 8 +- drivers/platform/x86/huawei-wmi.c | 874 ++ 2 files changed, 786 insertions(+), 96 deletions(-) base-commit: 288b9117de5cc1b7fb80f54b7c17deed6f018641 -- 2.21.0
[PATCH v3 4/6] platform/x86: huawei-wmi: Add battery charging thresholds
Control battery charge thresholds through the battery API and driver's attributes. Setting battery charging thresholds can introduce a race condition with MACH-WX9 where two or more threads are trying to read/write values from/to EC memory. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/huawei-wmi.c | 212 ++ 2 files changed, 213 insertions(+) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 61bf180d25c7..0659589e46bb 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1305,6 +1305,7 @@ config INTEL_ATOMISP2_PM config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 63e79b5f8282..4ca1a6896766 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -13,7 +14,10 @@ #include #include #include +#include +#include #include +#include /* * Huawei WMI GUIDs @@ -54,11 +58,14 @@ struct huawei_wmi_debug { }; struct huawei_wmi { + bool battery_available; + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; + struct mutex battery_lock; struct mutex wmi_lock; }; @@ -306,6 +313,208 @@ static void huawei_wmi_leds_setup(struct device *dev) devm_led_classdev_register(dev, &huawei->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(int *start, int *end) +{ + u8 ret[0x100]; + int err, i; + + err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100); + if (err) + return err; + + /* Find the last two non-zero values. Return status is ignored. */ + i = 0x100; + do { + if (start) + *start = ret[i-1]; + if (end) + *end = ret[i]; + } while (i > 2 && !ret[i--]); + + return 0; +} + +static int huawei_wmi_battery_set(int start, int end) +{ + union hwmi_arg arg; + int err; + + if (start < 0 || end > 100) + return -EINVAL; + + arg.cmd = BATTERY_THRESH_SET; + arg.args[2] = start; + arg.args[3] = end; + + /* 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 (quirks && quirks->battery_reset && start == 0 && end == 100) { + err = huawei_wmi_battery_set(0, 0); + if (err) + return err; + + msleep(1000); + } + + err = huawei_wmi_cmd(arg.cmd, NULL, 0); + + return err; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + return sprintf(buf, "%d\n", start); +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + return sprintf(buf, "%d\n", end); +} + +static ssize_t charge_control_thresholds_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, &end); + if (err) + return err; + + return sprintf(buf, "%d %d\n", start, end); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + if (sscanf(buf, "%d", &start) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, NULL);
[PATCH v3 6/6] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function if available. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 91 +++ 1 file changed, 91 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 8fc11a296357..c8f41121160c 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -598,6 +599,94 @@ static void huawei_wmi_fn_lock_exit(struct device *dev) device_remove_file(dev, &dev_attr_fn_lock_state); } +/* 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(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_puts(m, ","); + } + seq_puts(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(&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_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, huawei->debug.root, + &huawei->debug.arg); + debugfs_create_file("call", 0400, + huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops); +} + +static void huawei_wmi_debugfs_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + debugfs_remove_recursive(huawei->debug.root); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -723,6 +812,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -740,6 +830,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) } if (wmi_has_guid(HWMI_METHOD_GUID)) { + huawei_wmi_debugfs_exit(&pdev->dev); huawei_wmi_battery_exit(&pdev->dev); huawei_wmi_fn_lock_exit(&pdev->dev); } -- 2.21.0
[PATCH v3 2/6] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. (will be used later) 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. (will be used later) 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 77 +++ 1 file changed, 77 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9496ea3c78b5..97ff3d868765 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -22,7 +23,21 @@ #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +struct quirk_entry { + bool battery_reset; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi { + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; @@ -49,6 +64,58 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +static bool battery_reset; +static bool report_brightness; + +module_param(battery_reset, bool, 0444); +MODULE_PARM_DESC(battery_reset, + "Reset battery charge values to (0-0) before disabling it using (0-100)"); +module_param(report_brightness, bool, 0444); +MODULE_PARM_DESC(report_brightness, + "Report brightness keys."); + +/* 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_reset = { + .battery_reset = 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_reset + }, + { + .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 + }, + { } +}; + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -139,6 +206,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); } @@ -253,6 +325,11 @@ static __init int huawei_wmi_init(void) if (!huawei_wmi) return -ENOMEM; + quirks = &quirk_unknown; + dmi_check_system(huawei_quirks); + quirks->battery_reset |= battery_reset; + quirks->report_brightness |= report_brightness; + err = platform_driver_register(&huawei_wmi_driver); if (err) goto pdrv_err; -- 2.21.0
[PATCH v3 3/6] platform/x86: huawei-wmi: Implement huawei wmi management
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Use that to control the mic mute LED. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 217 +- 1 file changed, 180 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 97ff3d868765..63e79b5f8282 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -11,18 +11,35 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; + +union hwmi_arg { + u64 cmd; + u8 args[8]; +}; + struct quirk_entry { bool battery_reset; bool ec_micmute; @@ -41,8 +58,8 @@ struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; - acpi_handle handle; - char *acpi_method; + + struct mutex wmi_lock; }; struct huawei_wmi *huawei_wmi; @@ -116,52 +133,168 @@ static const struct dmi_system_id huawei_quirks[] = { { } }; +/* Utils */ + +static int huawei_wmi_call(struct acpi_buffer *in, struct acpi_buffer *out) +{ + acpi_status status; + + mutex_lock(&huawei_wmi->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&huawei_wmi->wmi_lock); + if (ACPI_FAILURE(status)) { + dev_err(&huawei_wmi->pdev->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(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(arg); + 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(&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(&huawei_wmi->pdev->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(&huawei_wmi->pdev->dev, "Bad package count, got %d\n", obj->package.count); + err = -EIO; +
[PATCH v3 5/6] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key and the F1-F12 row. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 85 +++ 1 file changed, 85 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 4ca1a6896766..8fc11a296357 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -59,6 +59,7 @@ struct huawei_wmi_debug { struct huawei_wmi { bool battery_available; + bool fn_lock_available; struct huawei_wmi_debug debug; struct input_dev *idev[2]; @@ -515,6 +516,88 @@ static void huawei_wmi_battery_exit(struct device *dev) } } +/* Fn lock */ + +static int huawei_wmi_fn_lock_get(int *on) +{ + u8 ret[0x100] = { 0 }; + int err, i; + + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; + + /* Find the first non-zero value. Return status is ignored. */ + i = 1; + do { + if (on) + *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. + } while (i < 0x100 && !ret[i++]); + + return 0; +} + +static int huawei_wmi_fn_lock_set(int on) +{ + union hwmi_arg arg; + + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + + return huawei_wmi_cmd(arg.cmd, NULL, 0); +} + +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(&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(on); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(fn_lock_state); + +static void huawei_wmi_fn_lock_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->fn_lock_available = true; + if (huawei_wmi_fn_lock_get(NULL)) { + huawei->fn_lock_available = false; + return; + } + + device_create_file(dev, &dev_attr_fn_lock_state); +} + +static void huawei_wmi_fn_lock_exit(struct device *dev) +{ + if (huawei_wmi->fn_lock_available) + device_remove_file(dev, &dev_attr_fn_lock_state); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -638,6 +721,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); + huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); } @@ -657,6 +741,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { huawei_wmi_battery_exit(&pdev->dev); + huawei_wmi_fn_lock_exit(&pdev->dev); } return 0; -- 2.21.0
[PATCH v3 1/6] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 7 +- drivers/platform/x86/huawei-wmi.c | 226 -- 2 files changed, 156 insertions(+), 77 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 1b67bb578f9f..61bf180d25c7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1304,7 +1304,7 @@ config INTEL_ATOMISP2_PM will be called intel_atomisp2_pm. config HUAWEI_WMI - tristate "Huawei WMI hotkeys driver" + tristate "Huawei WMI laptop extras driver" depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1313,9 +1313,8 @@ config HUAWEI_WMI select LEDS_TRIGGER_AUDIO select NEW_LEDS help - This driver provides support for Huawei WMI hotkeys. - It enables the missing keys and adds support to the micmute - LED found on some of these laptops. + This driver provides support for Huawei WMI hotkeys, battery charge + control, fn-lock, mic-mute LED, and other extra features. To compile this driver as a module, choose M here: the module will be called huawei-wmi. diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 195a7f3638cb..9496ea3c78b5 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 */ @@ -10,23 +10,28 @@ #include #include #include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 input_dev *idev[2]; struct led_classdev cdev; + struct platform_device *pdev; acpi_handle handle; char *acpi_method; }; +struct huawei_wmi *huawei_wmi; + static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, @@ -37,7 +42,7 @@ 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 } }, @@ -47,7 +52,7 @@ static const struct key_entry huawei_wmi_keymap[] = { 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); + struct huawei_wmi *huawei = dev_get_drvdata(led_cdev->dev->parent); acpi_status status; union acpi_object args[3]; struct acpi_object_list arg_list = { @@ -58,52 +63,53 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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 (strcmp(huawei->acpi_method, "SPIN") == 0) { args[0].integer.value = 0; args[2].integer.value = brightness ? 1 : 0; - } else if (strcmp(priv->acpi_method, "WPIN") == 0) { + } else if (strcmp(huawei->acpi_method, "WPIN") == 0) { args[0].integer.value = 1; args[2].integer.value = brightness ? 0 : 1; } else { return -EINVAL; } - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); + status = acpi_evaluate_object(huawei->handle, huawei->acpi_method, &arg_list, NULL); if (ACPI_FAILURE(status)) return -ENXIO; return 0; } -static int huawei_wmi_leds_setup(struct wmi_device *wdev) +static void huawei_wmi_leds_setup(struct device *dev) { - struct huaw
[PATCH v3 0/6] platform/x86: Huawei WMI laptop extras driver
Changes in v3: * Kconfig changes * Fix NULL cast to int warning. * Add ACPI_BATTERY as a dependency. Changes in v2: * Use battery charge control API. This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement driver quirks and parameters * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (6): platform/x86: huawei-wmi: Move to platform driver platform/x86: huawei-wmi: Add quirks and module parameters platform/x86: huawei-wmi: Implement huawei wmi management platform/x86: huawei-wmi: Add battery charging thresholds platform/x86: huawei-wmi: Add fn-lock support platform/x86: huawei-wmi: Add debugfs support drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/huawei-wmi.c | 872 ++ 2 files changed, 781 insertions(+), 92 deletions(-) base-commit: 288b9117de5cc1b7fb80f54b7c17deed6f018641 -- 2.21.0
[PATCH v2 5/6] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key and the F1-F12 row. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 85 +++ 1 file changed, 85 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 231b5cce00db..48be55c6027e 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -59,6 +59,7 @@ struct huawei_wmi_debug { struct huawei_wmi { bool battery_available; + bool fn_lock_available; struct huawei_wmi_debug debug; struct input_dev *idev[2]; @@ -515,6 +516,88 @@ static void huawei_wmi_battery_exit(struct device *dev) } } +/* Fn lock */ + +static int huawei_wmi_fn_lock_get(int *on) +{ + u8 ret[0x100] = { 0 }; + int err, i; + + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; + + /* Find the first non-zero value. Return status is ignored. */ + i = 1; + do { + if (on) + *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. + } while (i < 0x100 && !ret[i++]); + + return 0; +} + +static int huawei_wmi_fn_lock_set(int on) +{ + union hwmi_arg arg; + + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + + return huawei_wmi_cmd(arg.cmd, NULL, NULL); +} + +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(&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(on); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(fn_lock_state); + +static void huawei_wmi_fn_lock_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->fn_lock_available = true; + if (huawei_wmi_fn_lock_get(NULL)) { + huawei->fn_lock_available = false; + return; + } + + device_create_file(dev, &dev_attr_fn_lock_state); +} + +static void huawei_wmi_fn_lock_exit(struct device *dev) +{ + if (huawei_wmi->fn_lock_available) + device_remove_file(dev, &dev_attr_fn_lock_state); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -638,6 +721,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) mutex_init(&huawei_wmi->battery_lock); huawei_wmi_leds_setup(&pdev->dev); + huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); } @@ -657,6 +741,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { huawei_wmi_battery_exit(&pdev->dev); + huawei_wmi_fn_lock_exit(&pdev->dev); } return 0; -- 2.21.0
[PATCH v2 4/6] platform/x86: huawei-wmi: Add battery charging thresholds
Controll battery charge thresholds through the battery API and driver's attributes. Setting battery charging thresholds can introduce a race condition with MACH-WX9 where two or more threads are trying to read/write values from/to EC memory. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 212 ++ 1 file changed, 212 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 904ef38944b6..231b5cce00db 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -13,7 +14,10 @@ #include #include #include +#include +#include #include +#include /* * Huawei WMI GUIDs @@ -54,11 +58,14 @@ struct huawei_wmi_debug { }; struct huawei_wmi { + bool battery_available; + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; + struct mutex battery_lock; struct mutex wmi_lock; }; @@ -306,6 +313,208 @@ static void huawei_wmi_leds_setup(struct device *dev) devm_led_classdev_register(dev, &huawei->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(int *start, int *end) +{ + u8 ret[0x100]; + int err, i; + + err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100); + if (err) + return err; + + /* Find the last two non-zero values. Return status is ignored. */ + i = 0x100; + do { + if (start) + *start = ret[i-1]; + if (end) + *end = ret[i]; + } while (i > 2 && !ret[i--]); + + return 0; +} + +static int huawei_wmi_battery_set(int start, int end) +{ + union hwmi_arg arg; + int err; + + if (start < 0 || end > 100) + return -EINVAL; + + arg.cmd = BATTERY_THRESH_SET; + arg.args[2] = start; + arg.args[3] = end; + + /* 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 (quirks && quirks->battery_reset && start == 0 && end == 100) { + err = huawei_wmi_battery_set(0, 0); + if (err) + return err; + + msleep(1000); + } + + err = huawei_wmi_cmd(arg.cmd, NULL, NULL); + + return err; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + return sprintf(buf, "%d\n", start); +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + return sprintf(buf, "%d\n", end); +} + +static ssize_t charge_control_thresholds_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, &end); + if (err) + return err; + + return sprintf(buf, "%d %d\n", start, end); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + if (sscanf(buf, "%d", &start) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + if (sscanf(buf, "%d", &end) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_thresholds_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + if (ss
[PATCH v2 6/6] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function if available. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 91 +++ 1 file changed, 91 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 48be55c6027e..9ae2ecadeb10 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -598,6 +599,94 @@ static void huawei_wmi_fn_lock_exit(struct device *dev) device_remove_file(dev, &dev_attr_fn_lock_state); } +/* 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(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_puts(m, ","); + } + seq_puts(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(&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_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, huawei->debug.root, + &huawei->debug.arg); + debugfs_create_file("call", 0400, + huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops); +} + +static void huawei_wmi_debugfs_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + debugfs_remove_recursive(huawei->debug.root); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -723,6 +812,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) huawei_wmi_leds_setup(&pdev->dev); huawei_wmi_fn_lock_setup(&pdev->dev); huawei_wmi_battery_setup(&pdev->dev); + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -740,6 +830,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) } if (wmi_has_guid(HWMI_METHOD_GUID)) { + huawei_wmi_debugfs_exit(&pdev->dev); huawei_wmi_battery_exit(&pdev->dev); huawei_wmi_fn_lock_exit(&pdev->dev); } -- 2.21.0
[PATCH v2 3/6] platform/x86: huawei-wmi: Implement huawei wmi management
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Use that to control the mic mute LED. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 217 +- 1 file changed, 180 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 97ff3d868765..904ef38944b6 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -11,18 +11,35 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; + +union hwmi_arg { + u64 cmd; + u8 args[8]; +}; + struct quirk_entry { bool battery_reset; bool ec_micmute; @@ -41,8 +58,8 @@ struct huawei_wmi { struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; - acpi_handle handle; - char *acpi_method; + + struct mutex wmi_lock; }; struct huawei_wmi *huawei_wmi; @@ -116,52 +133,168 @@ static const struct dmi_system_id huawei_quirks[] = { { } }; +/* Utils */ + +static int huawei_wmi_call(struct acpi_buffer *in, struct acpi_buffer *out) +{ + acpi_status status; + + mutex_lock(&huawei_wmi->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&huawei_wmi->wmi_lock); + if (ACPI_FAILURE(status)) { + dev_err(&huawei_wmi->pdev->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(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(arg); + 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(&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(&huawei_wmi->pdev->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(&huawei_wmi->pdev->dev, "Bad package count, got %d\n", obj->package.count); + err = -EIO; +
[PATCH v2 2/6] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. (will be used later) 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. (will be used later) 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 77 +++ 1 file changed, 77 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9496ea3c78b5..97ff3d868765 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -22,7 +23,21 @@ #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" #define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +struct quirk_entry { + bool battery_reset; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi { + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct platform_device *pdev; @@ -49,6 +64,58 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +static bool battery_reset; +static bool report_brightness; + +module_param(battery_reset, bool, 0444); +MODULE_PARM_DESC(battery_reset, + "Reset battery charge values to (0-0) before disabling it using (0-100)"); +module_param(report_brightness, bool, 0444); +MODULE_PARM_DESC(report_brightness, + "Report brightness keys."); + +/* 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_reset = { + .battery_reset = 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_reset + }, + { + .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 + }, + { } +}; + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -139,6 +206,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); } @@ -253,6 +325,11 @@ static __init int huawei_wmi_init(void) if (!huawei_wmi) return -ENOMEM; + quirks = &quirk_unknown; + dmi_check_system(huawei_quirks); + quirks->battery_reset |= battery_reset; + quirks->report_brightness |= report_brightness; + err = platform_driver_register(&huawei_wmi_driver); if (err) goto pdrv_err; -- 2.21.0
[PATCH v2 1/6] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 226 -- 1 file changed, 153 insertions(+), 73 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 195a7f3638cb..9496ea3c78b5 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 */ @@ -10,23 +10,28 @@ #include #include #include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 input_dev *idev[2]; struct led_classdev cdev; + struct platform_device *pdev; acpi_handle handle; char *acpi_method; }; +struct huawei_wmi *huawei_wmi; + static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, @@ -37,7 +42,7 @@ 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 } }, @@ -47,7 +52,7 @@ static const struct key_entry huawei_wmi_keymap[] = { 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); + struct huawei_wmi *huawei = dev_get_drvdata(led_cdev->dev->parent); acpi_status status; union acpi_object args[3]; struct acpi_object_list arg_list = { @@ -58,52 +63,53 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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 (strcmp(huawei->acpi_method, "SPIN") == 0) { args[0].integer.value = 0; args[2].integer.value = brightness ? 1 : 0; - } else if (strcmp(priv->acpi_method, "WPIN") == 0) { + } else if (strcmp(huawei->acpi_method, "WPIN") == 0) { args[0].integer.value = 1; args[2].integer.value = brightness ? 0 : 1; } else { return -EINVAL; } - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); + status = acpi_evaluate_object(huawei->handle, huawei->acpi_method, &arg_list, NULL); if (ACPI_FAILURE(status)) return -ENXIO; return 0; } -static int huawei_wmi_leds_setup(struct wmi_device *wdev) +static void 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->handle = ec_get_handle(); + if (!huawei->handle) + return; - if (acpi_has_method(priv->handle, "SPIN")) - priv->acpi_method = "SPIN"; - else if (acpi_has_method(priv->handle, "WPIN")) - priv->acpi_method = "WPIN"; + if (acpi_has_method(huawei->handle, "SPIN")) + huawei->acpi_method = "SPIN"; + else if (acpi_has_method(huawei->handle, "WPIN")) + huawei->acpi_method = "WPIN"; else - return 0; + return; - priv->cdev.name = "platform::micmute"; - priv->cdev.max_brightness = 1; - priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set; - priv->cd
[PATCH v2 0/6] platform/x86: Huawei WMI laptop extras driver
Changes in v2: * Use battery charge control API. This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement driver quirks and parameters * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (6): platform/x86: huawei-wmi: Move to platform driver platform/x86: huawei-wmi: Add quirks and module parameters platform/x86: huawei-wmi: Implement huawei wmi management platform/x86: huawei-wmi: Add battery charging thresholds platform/x86: huawei-wmi: Add fn-lock support platform/x86: huawei-wmi: Add debugfs support drivers/platform/x86/huawei-wmi.c | 872 ++ 1 file changed, 780 insertions(+), 92 deletions(-) -- 2.21.0
[RFC 1/9] platform/x86: huawei-wmi: Rename guid and driver name
Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Change module description to reflect the upcoming changes. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 14 -- 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..a1159850a16c 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 */ @@ -15,10 +15,12 @@ /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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; @@ -37,7 +39,7 @@ 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 } }, @@ -186,7 +188,7 @@ static int huawei_wmi_probe(struct wmi_device *wdev) static const struct wmi_device_id huawei_wmi_id_table[] = { { .guid_string = WMI0_EVENT_GUID }, - { .guid_string = AMW0_EVENT_GUID }, + { .guid_string = HWMI_EVENT_GUID }, { } }; @@ -203,5 +205,5 @@ module_wmi_driver(huawei_wmi_driver); MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table); MODULE_AUTHOR("Ayman Bagabas "); -MODULE_DESCRIPTION("Huawei WMI hotkeys"); +MODULE_DESCRIPTION("Huawei WMI laptop extras driver"); MODULE_LICENSE("GPL v2"); -- 2.20.1
[RFC 1/9] platform/x86: huawei-wmi: Rename guid and driver name
Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Change module description to reflect the upcoming changes. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 14 -- 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..a1159850a16c 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 */ @@ -15,10 +15,12 @@ /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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; @@ -37,7 +39,7 @@ 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 } }, @@ -186,7 +188,7 @@ static int huawei_wmi_probe(struct wmi_device *wdev) static const struct wmi_device_id huawei_wmi_id_table[] = { { .guid_string = WMI0_EVENT_GUID }, - { .guid_string = AMW0_EVENT_GUID }, + { .guid_string = HWMI_EVENT_GUID }, { } }; @@ -203,5 +205,5 @@ module_wmi_driver(huawei_wmi_driver); MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table); MODULE_AUTHOR("Ayman Bagabas "); -MODULE_DESCRIPTION("Huawei WMI hotkeys"); +MODULE_DESCRIPTION("Huawei WMI laptop extras driver"); MODULE_LICENSE("GPL v2"); -- 2.20.1
[RFC 7/9] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key. Implement the basic functionality of this feature to be used later by sysfs interface support introduced in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 30 ++ 1 file changed, 30 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index da3986cd0428..4159e10bda26 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -351,6 +351,36 @@ 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
[RFC 4/9] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- 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 27520b0f8956..8f918138053a 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -34,6 +35,14 @@ enum { MICMUTE_LED_SET = 0x0b04, /* \SMLS */ }; +struct quirk_entry { + bool battery_sleep; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; @@ -62,6 +71,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 keys."); + +/* 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, @@ -264,6 +325,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); } @@ -377,6 +443,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) return err; -- 2.20.1
[RFC 6/9] platform/x86: huawei-wmi: Add battery charging thresholds
Implement battery charging thresholds functionality to be used with sysfs interface support that is implemented in this series. Setting battery charging thresholds can introduce a race condition where two are trying to read/write values from/to EC memory. Even though a mutex is used, this doesn't guarantee that these values will make it to EC so a blocking sleep is necessary. This sleep is enforced on the MACH-WX9 model since turning off battery protection using (0,100) doesn't change the values in the EC memory, instead it only disables the protection. So a workaround is to set the thresholds to (0,0) before setting it to (0,100). This will ensure that we update these values in EC memory and then turning it off with (0,100). Thus, the msleep(1000). Signed-off-by: Ayman Bagabas --- In huawei_wmi_battery_get and later in huawei_wmi_fn_lock_get, 256 bytes buffer is allocated dynamically on the stack. Now I know this is discouraged, should it be changed to use kmalloc? drivers/platform/x86/huawei-wmi.c | 54 +++ 1 file changed, 54 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9013a05d2832..da3986cd0428 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -46,6 +47,7 @@ static struct quirk_entry *quirks; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; + struct mutex battery_lock; struct mutex wmi_lock; struct platform_device *pdev; }; @@ -298,6 +300,57 @@ static int huawei_wmi_leds_setup(struct device *dev) return devm_led_classdev_register(dev, &priv->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(struct device *dev, int *low, int *high) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(dev); + u8 ret[0x100]; + int err, i; + + mutex_lock(&priv->battery_lock); + err = huawei_wmi_cmd(dev, BATTERY_THRESH_GET, ret, 0x100); + mutex_unlock(&priv->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_priv *priv = 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(&priv->battery_lock); + err = huawei_wmi_cmd(dev, *(u64 *)arg, NULL, NULL); + if (quirks && quirks->battery_sleep) + msleep(1000); + mutex_unlock(&priv->battery_lock); + + return err; +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -418,6 +471,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&priv->wmi_lock); + mutex_init(&priv->battery_lock); err = huawei_wmi_leds_setup(&pdev->dev); if (err) { -- 2.20.1
[RFC 8/9] platform/x86: huawei-wmi: Add sysfs interface support
Add sysfs interface to enable the use of battery charging thresholds and fn-lock support introduced in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 82 +++ 1 file changed, 82 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 4159e10bda26..f7041fb71026 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -14,6 +14,7 @@ #include #include #include +#include #include /* @@ -381,6 +382,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) @@ -508,6 +583,12 @@ static int huawei_wmi_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to setup leds\n"); return err; } + + err = sysfs_create_groups(&pdev->dev.kobj, huawei_wmi_groups); + if (err) { + dev_err(&pdev->dev, "Failed to create sysfs interface\n"); + return err; + } } return 0; @@ -522,6 +603,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) wmi_remove_notify_handler(HWMI_EVENT_GUID); if (wmi_has_guid(HWMI_METHOD_GUID)) { + sysfs_remove_groups(&pdev->dev.kobj, huawei_wmi_groups); } return 0; -- 2.20.1
[RFC 9/9] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function. The WMI interface takes a 64 bit integer and returns 256-260 bytes buffer. This debugfs interface creates two files, one stores a 64 bit int and the other calls the WMI interface and dumps out the returned buffer. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 98 +++ 1 file changed, 98 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index f7041fb71026..bdca8bd76c8c 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -45,7 +46,13 @@ struct quirk_entry { static struct quirk_entry *quirks; +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi_priv { + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct mutex battery_lock; @@ -456,6 +463,94 @@ 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_priv *priv = 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(m, "["); + for (i = 0; i < obj->package.count; i++) { + huawei_wmi_debugfs_call_dump(m, priv, &obj->package.elements[i]); + if (i < obj->package.count - 1) + seq_puts(m, ","); + } + seq_puts(m, "]"); + break; + default: + dev_err(&priv->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_priv *priv = 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 = &priv->debug.arg; + + err = huawei_wmi_call(&priv->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, priv, 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_priv *priv = dev_get_drvdata(dev); + + debugfs_remove_recursive(priv->debug.root); +} + +static void huawei_wmi_debugfs_setup(struct device *dev) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(dev); + + priv->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, priv->debug.root, + &priv->debug.arg); + debugfs_create_file("call", 0400, + priv->debug.root, priv, &huawei_wmi_debugfs_call_fops); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -589,6 +684,8 @@ static int huawei_wmi_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to create sysfs interface\n"); return err; } + + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -603,6 +700,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_groups(&pdev->dev.kobj, huawei_wmi_groups); } -- 2.20.1
[RFC 3/9] platform/x86: huawei-wmi: Implement huawei wmi management interface
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Implement basic functionality of this interface along with supported command IDs that are implemented in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 130 ++ 1 file changed, 130 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 12cae450fc1f..27520b0f8956 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -10,24 +10,36 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; acpi_handle handle; char *acpi_method; + struct mutex wmi_lock; struct platform_device *pdev; }; @@ -50,6 +62,116 @@ 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_priv *priv = dev_get_drvdata(dev); + acpi_status status; + + mutex_lock(&priv->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&priv->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(arg); + 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; +
[RFC 2/9] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Signed-off-by: Ayman Bagabas --- Do you think we should stick to wmi driver especially after "platform/x86: wmi: add context pointer field to struct wmi_device_id" https://patchwork.kernel.org/patch/10963421/ which is basically was my issue with wmi driver where there wasn't a way to distinguish what wmi device is associated with which guid. I believe that platform driver is more suitable for this since it has grown from just a hotkeys driver and users can access sysfs of this easily with platform driver. What do you think? drivers/platform/x86/huawei-wmi.c | 152 +- 1 file changed, 110 insertions(+), 42 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index a1159850a16c..12cae450fc1f 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -10,6 +10,7 @@ #include #include #include +#include #include /* @@ -23,12 +24,15 @@ struct huawei_wmi_priv { - struct input_dev *idev; + struct input_dev *idev[2]; struct led_classdev cdev; acpi_handle handle; char *acpi_method; + 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 } }, @@ -46,6 +50,8 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +/* LEDs */ + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -77,9 +83,9 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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_priv *priv = dev_get_drvdata(dev); priv->handle = ec_get_handle(); if (!priv->handle) @@ -97,15 +103,16 @@ static int huawei_wmi_leds_setup(struct wmi_device *wdev) 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.dev = dev; priv->cdev.flags = LED_CORE_SUSPENDRESUME; - return devm_led_classdev_register(&wdev->dev, &priv->cdev); + return devm_led_classdev_register(dev, &priv->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; /* @@ -129,81 +136,142 @@ 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 hua
[RFC 5/9] platform/x86: huawei-wmi: Control micmute led through wmi interface
Now that huawei WMI management interface is implemented, micmute LED can be controlled easily through this interface. Exception is the Matebook X (2017) which continue to uses ACPI EC method to control the LED. This model can control the LED through the legacy WMI interface which is not implemented ATM. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 86 ++- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 8f918138053a..9013a05d2832 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -46,8 +46,6 @@ static struct quirk_entry *quirks; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; - acpi_handle handle; - char *acpi_method; struct mutex wmi_lock; struct platform_device *pdev; }; @@ -238,49 +236,57 @@ 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) { - struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); - 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; + /* 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; + } else { + return -ENODEV; + } + + status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; } else { - return -EINVAL; - } + u8 arg[8]; - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); - if (ACPI_FAILURE(status)) - return -ENXIO; + *(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) { struct huawei_wmi_priv *priv = dev_get_drvdata(dev); - priv->handle = ec_get_handle(); - if (!priv->handle) - return 0; - - 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; @@ -412,9 +418,15 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&priv->wmi_lock); + + err = huawei_wmi_leds_setup(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "Failed to setup leds\n"); + return err; + } } - return huawei_wmi_leds_setup(&pdev->dev); + return 0; } static int huawei_wmi_remove(struct platform_device *pdev) -- 2.20.1
[RFC 0/9] platform/x86: Huawei WMI laptop extras driver
This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Implement driver quirks and parameters * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (9): platform/x86: huawei-wmi: rename guid and driver name platform/x86: huawei-wmi: move to platform driver platform/x86: huawei-wmi: implement huawei wmi management interface platform/x86: huawei-wmi: add quirks and module parameters platform/x86: huawei-wmi: control micmute led through wmi interface platform/x86: huawei-wmi: add battery charging thresholds platform/x86: huawei-wmi: add fn-lock support platform/x86: huawei-wmi: add sysfs interface support platform/x86: huawei-wmi: add debugfs support drivers/platform/x86/huawei-wmi.c | 710 ++ 1 file changed, 629 insertions(+), 81 deletions(-) -- 2.20.1
Re: [RFC 0/9] platform/x86: Huawei WMI laptop extras driver
On 19/07/25 04:05PM, Ayman Bagabas wrote: > On 19/07/25 08:33PM, Andy Shevchenko wrote: > > On Sun, Jun 30, 2019 at 8:41 AM Ayman Bagabas > > wrote: > > > > > > This patch series introduce changes to huawei-wmi driver that includes: > > > * Move to platform driver > > > * Implement WMI management interface > > > * Add micmute LED support through WMI > > > * Add battery charging protection support through WMI > > > * Add fn-lock support through WMI > > > * Implement driver quirks and parameters > > > * Add a debugfs interface to WMI > > > > > > # Move to platform driver > > > > > > The current driver offers hotkeys and micmute led support only. With > > > these changes, a platform driver makes more sense since it handles these > > > changes pretty nicely. > > > > > > # Implement WMI management interface > > > > > > Huawei Matebook laptops come with two WMI interfaces. The first being > > > WMI0 which is considered "legacy" and AFAIK only found on the Matebook X > > > released in 2017. The second has a UID of "HWMI" and is found in pretty > > > much all models with a slight difference in implementation except for > > > the Matebook X (2017). Since this model has two interfaces, some aspects > > > are controlled through the legacy interface and some through the other > > > interface. Currently, the legacy interface is not fully implemented and > > > is only used for hotkeys and further debugging has to be done. > > > > > > The WMI interface takes a 64 bit integer, although uses 32 bits most of > > > the time, and returns a 256-260 bytes buffer consists of either one ACPI > > > buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI > > > package of two buffers, one with 4 bytes, and the other with 256 bytes. > > > We only care about the latter 256 buffer in both cases since the 4 bytes > > > always return zeros. The first byte of this 256 buffer always has the > > > return status where 1 indicated error. Some models require calling the > > > WMI interface twice to execute a command. > > > > > > # Add micmute LED support through WMI > > > > > > After implementing the WMI interface, micmute LED can be controlled > > > easily. Models with the legacy interface fall back to ACPI EC method > > > control since the legacy interface is not implemented. > > > > > > # Add battery charging protection support through WMI > > > > > > Most models, that has the WMI interface, are capable of battery > > > protection where it can control battery charging thresholds and limits > > > charging the battery to certain values. > > > > > > # Add fn-lock support through WMI > > > > > > 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. > > > > > > # Implement driver quirks and parameters > > > > > > The driver introduces 3 quirks and 2 parameters that can change the > > > driver's behavior. These quirks being as: > > > 1. Fixes reportin
Re: [RFC 0/9] platform/x86: Huawei WMI laptop extras driver
On 19/07/25 08:33PM, Andy Shevchenko wrote: > On Sun, Jun 30, 2019 at 8:41 AM Ayman Bagabas wrote: > > > > This patch series introduce changes to huawei-wmi driver that includes: > > * Move to platform driver > > * Implement WMI management interface > > * Add micmute LED support through WMI > > * Add battery charging protection support through WMI > > * Add fn-lock support through WMI > > * Implement driver quirks and parameters > > * Add a debugfs interface to WMI > > > > # Move to platform driver > > > > The current driver offers hotkeys and micmute led support only. With > > these changes, a platform driver makes more sense since it handles these > > changes pretty nicely. > > > > # Implement WMI management interface > > > > Huawei Matebook laptops come with two WMI interfaces. The first being > > WMI0 which is considered "legacy" and AFAIK only found on the Matebook X > > released in 2017. The second has a UID of "HWMI" and is found in pretty > > much all models with a slight difference in implementation except for > > the Matebook X (2017). Since this model has two interfaces, some aspects > > are controlled through the legacy interface and some through the other > > interface. Currently, the legacy interface is not fully implemented and > > is only used for hotkeys and further debugging has to be done. > > > > The WMI interface takes a 64 bit integer, although uses 32 bits most of > > the time, and returns a 256-260 bytes buffer consists of either one ACPI > > buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI > > package of two buffers, one with 4 bytes, and the other with 256 bytes. > > We only care about the latter 256 buffer in both cases since the 4 bytes > > always return zeros. The first byte of this 256 buffer always has the > > return status where 1 indicated error. Some models require calling the > > WMI interface twice to execute a command. > > > > # Add micmute LED support through WMI > > > > After implementing the WMI interface, micmute LED can be controlled > > easily. Models with the legacy interface fall back to ACPI EC method > > control since the legacy interface is not implemented. > > > > # Add battery charging protection support through WMI > > > > Most models, that has the WMI interface, are capable of battery > > protection where it can control battery charging thresholds and limits > > charging the battery to certain values. > > > > # Add fn-lock support through WMI > > > > 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. > > > > # Implement driver quirks and parameters > > > > The driver introduces 3 quirks and 2 parameters that can change the > > driver's behavior. These quirks being as: > > 1. Fixes reporting brightness keys twice since it's already handled by > >acpi-video. > > 2. Some models need a short delay when setting battery thresholds to > >prevent a race condition when two processes read/write. > > 3. Matebook X (2017) handles micmute led through the "legacy" interface > >which is not currently implemented. Use ACPI EC method to control > >this led. > > > > and the 2 parameters can en
Re: [PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver
On Sat, 2019-06-29 at 17:27 +0300, Andy Shevchenko wrote: > On Thu, Jun 13, 2019 at 6:04 AM Ayman Bagabas < > ayman.baga...@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 > > > >
[RFC 7/9] platform/x86: huawei-wmi: Add fn-lock support
Huawei Matebook laptops uses Fn key and toggle to access F1-F12 keys. Along with that, there is this feature called fn-lock that inverts the behavior of this Fn key. Implement the basic functionality of this feature to be used later by sysfs interface support introduced in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 30 ++ 1 file changed, 30 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index da3986cd0428..4159e10bda26 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -351,6 +351,36 @@ 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
[RFC 8/9] platform/x86: huawei-wmi: Add sysfs interface support
Add sysfs interface to enable the use of battery charging thresholds and fn-lock support introduced in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 82 +++ 1 file changed, 82 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 4159e10bda26..f7041fb71026 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -14,6 +14,7 @@ #include #include #include +#include #include /* @@ -381,6 +382,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) @@ -508,6 +583,12 @@ static int huawei_wmi_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to setup leds\n"); return err; } + + err = sysfs_create_groups(&pdev->dev.kobj, huawei_wmi_groups); + if (err) { + dev_err(&pdev->dev, "Failed to create sysfs interface\n"); + return err; + } } return 0; @@ -522,6 +603,7 @@ static int huawei_wmi_remove(struct platform_device *pdev) wmi_remove_notify_handler(HWMI_EVENT_GUID); if (wmi_has_guid(HWMI_METHOD_GUID)) { + sysfs_remove_groups(&pdev->dev.kobj, huawei_wmi_groups); } return 0; -- 2.20.1
[RFC 9/9] platform/x86: huawei-wmi: Add debugfs support
Add a debugfs interface that can be used to call the WMI management interface function. The WMI interface takes a 64 bit integer and returns 256-260 bytes buffer. This debugfs interface creates two files, one stores a 64 bit int and the other calls the WMI interface and dumps out the returned buffer. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 98 +++ 1 file changed, 98 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index f7041fb71026..bdca8bd76c8c 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -45,7 +46,13 @@ struct quirk_entry { static struct quirk_entry *quirks; +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + struct huawei_wmi_priv { + struct huawei_wmi_debug debug; struct input_dev *idev[2]; struct led_classdev cdev; struct mutex battery_lock; @@ -456,6 +463,94 @@ 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_priv *priv = 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_puts(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_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(m, "["); + for (i = 0; i < obj->package.count; i++) { + huawei_wmi_debugfs_call_dump(m, priv, &obj->package.elements[i]); + if (i < obj->package.count - 1) + seq_puts(m, ","); + } + seq_puts(m, "]"); + break; + default: + dev_err(&priv->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_priv *priv = 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 = &priv->debug.arg; + + err = huawei_wmi_call(&priv->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, priv, 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_priv *priv = dev_get_drvdata(dev); + + debugfs_remove_recursive(priv->debug.root); +} + +static void huawei_wmi_debugfs_setup(struct device *dev) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(dev); + + priv->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, priv->debug.root, + &priv->debug.arg); + debugfs_create_file("call", 0400, + priv->debug.root, priv, &huawei_wmi_debugfs_call_fops); +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -589,6 +684,8 @@ static int huawei_wmi_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to create sysfs interface\n"); return err; } + + huawei_wmi_debugfs_setup(&pdev->dev); } return 0; @@ -603,6 +700,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_groups(&pdev->dev.kobj, huawei_wmi_groups); } -- 2.20.1
[RFC 6/9] platform/x86: huawei-wmi: Add battery charging thresholds
Implement battery charging thresholds functionality to be used with sysfs interface support that is implemented in this series. Setting battery charging thresholds can introduce a race condition where two are trying to read/write values from/to EC memory. Even though a mutex is used, this doesn't guarantee that these values will make it to EC so a blocking sleep is necessary. This sleep is enforced on the MACH-WX9 model since turning off battery protection using (0,100) doesn't change the values in the EC memory, instead it only disables the protection. So a workaround is to set the thresholds to (0,0) before setting it to (0,100). This will ensure that we update these values in EC memory and then turning it off with (0,100). Thus, the msleep(1000). Signed-off-by: Ayman Bagabas --- In huawei_wmi_battery_get and later in huawei_wmi_fn_lock_get, 256 bytes buffer is allocated dynamically on the stack. Now I know this is discouraged, should it be changed to use kmalloc? drivers/platform/x86/huawei-wmi.c | 54 +++ 1 file changed, 54 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 9013a05d2832..da3986cd0428 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -46,6 +47,7 @@ static struct quirk_entry *quirks; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; + struct mutex battery_lock; struct mutex wmi_lock; struct platform_device *pdev; }; @@ -298,6 +300,57 @@ static int huawei_wmi_leds_setup(struct device *dev) return devm_led_classdev_register(dev, &priv->cdev); } +/* Battery protection */ + +static int huawei_wmi_battery_get(struct device *dev, int *low, int *high) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(dev); + u8 ret[0x100]; + int err, i; + + mutex_lock(&priv->battery_lock); + err = huawei_wmi_cmd(dev, BATTERY_THRESH_GET, ret, 0x100); + mutex_unlock(&priv->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_priv *priv = 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(&priv->battery_lock); + err = huawei_wmi_cmd(dev, *(u64 *)arg, NULL, NULL); + if (quirks && quirks->battery_sleep) + msleep(1000); + mutex_unlock(&priv->battery_lock); + + return err; +} + /* Input */ static void huawei_wmi_process_key(struct input_dev *idev, int code) @@ -418,6 +471,7 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&priv->wmi_lock); + mutex_init(&priv->battery_lock); err = huawei_wmi_leds_setup(&pdev->dev); if (err) { -- 2.20.1
[RFC 5/9] platform/x86: huawei-wmi: Control micmute led through wmi interface
Now that huawei WMI management interface is implemented, micmute LED can be controlled easily through this interface. Exception is the Matebook X (2017) which continue to uses ACPI EC method to control the LED. This model can control the LED through the legacy WMI interface which is not implemented ATM. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 86 ++- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 8f918138053a..9013a05d2832 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -46,8 +46,6 @@ static struct quirk_entry *quirks; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; - acpi_handle handle; - char *acpi_method; struct mutex wmi_lock; struct platform_device *pdev; }; @@ -238,49 +236,57 @@ 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) { - struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); - 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; + /* 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; + } else { + return -ENODEV; + } + + status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; } else { - return -EINVAL; - } + u8 arg[8]; - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); - if (ACPI_FAILURE(status)) - return -ENXIO; + *(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) { struct huawei_wmi_priv *priv = dev_get_drvdata(dev); - priv->handle = ec_get_handle(); - if (!priv->handle) - return 0; - - 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; @@ -412,9 +418,15 @@ static int huawei_wmi_probe(struct platform_device *pdev) if (wmi_has_guid(HWMI_METHOD_GUID)) { mutex_init(&priv->wmi_lock); + + err = huawei_wmi_leds_setup(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "Failed to setup leds\n"); + return err; + } } - return huawei_wmi_leds_setup(&pdev->dev); + return 0; } static int huawei_wmi_remove(struct platform_device *pdev) -- 2.20.1
[RFC 0/9] platform/x86: Huawei WMI laptop extras driver
This patch series introduce changes to huawei-wmi driver that includes: * Move to platform driver * Implement WMI management interface * Add micmute LED support through WMI * Add battery charging protection support through WMI * Add fn-lock support through WMI * Implement driver quirks and parameters * Add a debugfs interface to WMI # Move to platform driver The current driver offers hotkeys and micmute led support only. With these changes, a platform driver makes more sense since it handles these changes pretty nicely. # Implement WMI management interface Huawei Matebook laptops come with two WMI interfaces. The first being WMI0 which is considered "legacy" and AFAIK only found on the Matebook X released in 2017. The second has a UID of "HWMI" and is found in pretty much all models with a slight difference in implementation except for the Matebook X (2017). Since this model has two interfaces, some aspects are controlled through the legacy interface and some through the other interface. Currently, the legacy interface is not fully implemented and is only used for hotkeys and further debugging has to be done. The WMI interface takes a 64 bit integer, although uses 32 bits most of the time, and returns a 256-260 bytes buffer consists of either one ACPI buffer of 260 bytes, in the case of Matebook X (2017), or one ACPI package of two buffers, one with 4 bytes, and the other with 256 bytes. We only care about the latter 256 buffer in both cases since the 4 bytes always return zeros. The first byte of this 256 buffer always has the return status where 1 indicated error. Some models require calling the WMI interface twice to execute a command. # Add micmute LED support through WMI After implementing the WMI interface, micmute LED can be controlled easily. Models with the legacy interface fall back to ACPI EC method control since the legacy interface is not implemented. # Add battery charging protection support through WMI Most models, that has the WMI interface, are capable of battery protection where it can control battery charging thresholds and limits charging the battery to certain values. # Add fn-lock support through WMI 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. # Implement driver quirks and parameters The driver introduces 3 quirks and 2 parameters that can change the driver's behavior. These quirks being as: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. and the 2 parameters can enforce the behavior of quirk 1 & 2. # Add a debugfs interface to WMI An interface to the WMI management interface that allows easier debugging. Ayman Bagabas (9): platform/x86: huawei-wmi: rename guid and driver name platform/x86: huawei-wmi: move to platform driver platform/x86: huawei-wmi: implement huawei wmi management interface platform/x86: huawei-wmi: add quirks and module parameters platform/x86: huawei-wmi: control micmute led through wmi interface platform/x86: huawei-wmi: add battery charging thresholds platform/x86: huawei-wmi: add fn-lock support platform/x86: huawei-wmi: add sysfs interface support platform/x86: huawei-wmi: add debugfs support drivers/platform/x86/huawei-wmi.c | 710 ++ 1 file changed, 629 insertions(+), 81 deletions(-) -- 2.20.1
[RFC 2/9] platform/x86: huawei-wmi: Move to platform driver
Move from WMI driver to platform driver. This move is necessary since the driver is no longer a hotkeys driver only. Platform driver makes it easier for users to access sysfs attributes under (i.e. /sys/devices/platform/huawei-wmi) compared to wmi driver. Signed-off-by: Ayman Bagabas --- Do you think we should stick to wmi driver especially after "platform/x86: wmi: add context pointer field to struct wmi_device_id" https://patchwork.kernel.org/patch/10963421/ which is basically was my issue with wmi driver where there wasn't a way to distinguish what wmi device is associated with which guid. I believe that platform driver is more suitable for this since it has grown from just a hotkeys driver and users can access sysfs of this easily with platform driver. What do you think? drivers/platform/x86/huawei-wmi.c | 152 +- 1 file changed, 110 insertions(+), 42 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index a1159850a16c..12cae450fc1f 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -10,6 +10,7 @@ #include #include #include +#include #include /* @@ -23,12 +24,15 @@ struct huawei_wmi_priv { - struct input_dev *idev; + struct input_dev *idev[2]; struct led_classdev cdev; acpi_handle handle; char *acpi_method; + 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 } }, @@ -46,6 +50,8 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_END,0 } }; +/* LEDs */ + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -77,9 +83,9 @@ static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, 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_priv *priv = dev_get_drvdata(dev); priv->handle = ec_get_handle(); if (!priv->handle) @@ -97,15 +103,16 @@ static int huawei_wmi_leds_setup(struct wmi_device *wdev) 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.dev = dev; priv->cdev.flags = LED_CORE_SUSPENDRESUME; - return devm_led_classdev_register(&wdev->dev, &priv->cdev); + return devm_led_classdev_register(dev, &priv->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; /* @@ -129,81 +136,142 @@ 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 hua
[RFC 3/9] platform/x86: huawei-wmi: Implement huawei wmi management interface
Huawei Matebook laptops come with a WMI management interface that can control various aspects of the device. This interface is also found on the old Matebook X released in 2017. Implement basic functionality of this interface along with supported command IDs that are implemented in this series. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 130 ++ 1 file changed, 130 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 12cae450fc1f..27520b0f8956 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -10,24 +10,36 @@ #include #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \SMLS */ +}; struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; acpi_handle handle; char *acpi_method; + struct mutex wmi_lock; struct platform_device *pdev; }; @@ -50,6 +62,116 @@ 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_priv *priv = dev_get_drvdata(dev); + acpi_status status; + + mutex_lock(&priv->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&priv->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(arg); + 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; +
[RFC 4/9] platform/x86: huawei-wmi: Add quirks and module parameters
Introduce quirks and module parameters. 3 quirks are added: 1. Fixes reporting brightness keys twice since it's already handled by acpi-video. 2. Some models need a short delay when setting battery thresholds to prevent a race condition when two processes read/write. 3. Matebook X (2017) handles micmute led through the "legacy" interface which is not currently implemented. Use ACPI EC method to control this led. 2 module parameters are added to enable this short delay and/or report brightness keys through this driver. Signed-off-by: Ayman Bagabas --- 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 27520b0f8956..8f918138053a 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -34,6 +35,14 @@ enum { MICMUTE_LED_SET = 0x0b04, /* \SMLS */ }; +struct quirk_entry { + bool battery_sleep; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; + struct huawei_wmi_priv { struct input_dev *idev[2]; struct led_classdev cdev; @@ -62,6 +71,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 keys."); + +/* 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, @@ -264,6 +325,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); } @@ -377,6 +443,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) return err; -- 2.20.1
[RFC 1/9] platform/x86: huawei-wmi: Rename guid and driver name
Use WMI device UID, AMW0 has a UID of HWMI. WMI0 is the device name and doesn't have a UID so keep it as it is. Change module description to reflect the upcoming changes. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/huawei-wmi.c | 14 -- 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..a1159850a16c 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 */ @@ -15,10 +15,12 @@ /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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; @@ -37,7 +39,7 @@ 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 } }, @@ -186,7 +188,7 @@ static int huawei_wmi_probe(struct wmi_device *wdev) static const struct wmi_device_id huawei_wmi_id_table[] = { { .guid_string = WMI0_EVENT_GUID }, - { .guid_string = AMW0_EVENT_GUID }, + { .guid_string = HWMI_EVENT_GUID }, { } }; @@ -203,5 +205,5 @@ module_wmi_driver(huawei_wmi_driver); MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table); MODULE_AUTHOR("Ayman Bagabas "); -MODULE_DESCRIPTION("Huawei WMI hotkeys"); +MODULE_DESCRIPTION("Huawei WMI laptop extras driver"); MODULE_LICENSE("GPL v2"); -- 2.20.1
[PATCH v2 2/8] platform/x86: huawei-wmi: implement WMI management interface
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 --- 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 #include #include +#include #include #include /* * Huawei WMI GUIDs */ +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" /* 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 = 0x1103, /* \GBTT */ + BATTERY_THRESH_SET = 0x1003, /* \SBTT */ + FAN_SPEED_GET = 0x0802, /* \GFNS */ + FN_LOCK_GET = 0x0604, /* \GFRS */ + FN_LOCK_SET = 0x0704, /* \SFRS */ + MICMUTE_LED_SET = 0x0b04, /* \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; +
[PATCH v2 0/8] platform/x86: Huawei WMI laptop extras driver
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
[PATCH v2 1/8] platform/x86: huawei-wmi: move to platform driver
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 --- 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 */ @@ -10,23 +10,27 @@ #include #include #include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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")) -
[PATCH v1] platform/x86: Huawei laptop extras driver
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 --- 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 */ #include +#include +#include #include #include #include #include +#include +#include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 0x1103 */ + BATTERY_SET, /* \SBTT 0xXXYY1003 */ + FN_LOCK_GET, /* \
[PATCH v2 3/8] platform/x86: huawei-wmi: use quirks and module parameters
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 --- 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 +#include #include #include #include @@ -35,6 +36,14 @@ enum { MICMUTE_LED_SET = 0x0b04, /* \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; +
[PATCH v2 5/8] platform/x86: huawei-wmi: add battery charging protection support
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 --- 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 +#include #include #include #include @@ -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
[PATCH v2 4/8] platform/x86: huawei-wmi: control micmute LED through WMI interface
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 --- 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
[PATCH v2 6/8] platform/x86: huawei-wmi: add fn-lock support
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 --- 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
[PATCH v2 8/8] platform/x86: huawei-wmi: add debugfs files support
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 --- 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 +#include #include #include #include @@ -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(&pde
[PATCH v2 7/8] platform/x86: huawei-wmi: add sysfs interface support
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 --- 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 #include #include +#include #include /* @@ -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
Re: [PATCH v1] platform/x86: Huawei laptop extras driver
I'm really sorry, I just realized the changes are a lot and unreadable. Changes should be splitted into a series and resubmitted. On Thu, 2019-05-23 at 05:31 -0400, Ayman Bagabas wrote: > 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 > --- > 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 > * > *
[PATCH v1] platform/x86: Huawei laptop extras driver
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 --- 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 */ #include +#include +#include #include #include #include #include +#include +#include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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 0x1103 */ + BATTERY_SET, /* \SBTT 0xXXYY1003 */ + FN_LOCK_GET, /* \
[PATCH] ALSA: Enable micmute LED for Huawei laptops
Since this LED is found on all Huawei laptops, we can hook it to huawei-wmi platform driver to control it. Also, some renames have been made to use product name instead of common name to avoid confusions. Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 9 - 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index f83f21d64dd4..ab6a856ec614 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5747,7 +5747,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, - ALC256_FIXUP_HUAWEI_MBXP_PINS, + ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, ALC285_FIXUP_LENOVO_HEADPHONE_NOISE, @@ -6038,7 +6038,7 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, - [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + [ALC256_FIXUP_HUAWEI_MACH_WX9_PINS] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { {0x12, 0x90a60130}, @@ -7063,9 +7063,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), - SND_PCI_QUIRK(0x19e5, 0x3200, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), - SND_PCI_QUIRK(0x19e5, 0x3201, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), - SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 @@ -7124,6 +7122,7 @@ static const struct snd_pci_quirk alc269_fixup_vendor_tbl[] = { SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), {} }; -- 2.20.1
Re: [PATCH v1 1/2] platform/x86: Huawei WMI laptop extras driver update
On Wed, 2019-05-15 at 15:10 +0200, Takashi Iwai wrote: > On Mon, 13 May 2019 22:30:05 +0200, > Ayman Bagabas wrote: > > This update brings on the use of WMI BIOS management interface > > found on > > Huawei laptops. This interface can control the micmute LED found on > > most > > of these laptops, control charging thresholds values, and control > > fn-lock feature. > > > > Signed-off-by: Ayman Bagabas > > You need to put the actual change (e.g. moving to the platform > driver) > here as well, not only in the cover letter. I will put a detailed explanation in the second version. > > But I wonder what the actual motivation to move to the platform > driver... > This patch adds another platform driver with the same name, "huawei-wmi", along with the existing wmi input driver. So the driver registers two drivers platform and wmi. The reason behind this is that they both use ACPI WMI and two drivers with the same type/bus cannot have the same name. The way this works is, input driver hooks to wmi event guids while the other uses wmi method guid. Most existing x86 drivers with similar structure use only one platform driver. At first, my approach was combining all these together in one wmi driver. However, I couldn't find a way to differentiate which wmi device belong to which guid in wmi driver probe method. I could've moved everything to the platform driver but the wmi driver handles input very neatly. So with this "new" platform driver, micmute led is now controlled through wmi interface instead of ACPI EC which should work with all huawei laptops. In addition, two features were added, setting charging thresholds and fn-lock. I know having two drivers in one file might not be a good idea, should it be one platform driver instead? Thank you, Ayman > > thanks, > > Takashi > > > --- > > drivers/platform/x86/Kconfig | 8 +- > > drivers/platform/x86/huawei-wmi.c | 578 +- > > > > 2 files changed, 500 insertions(+), 86 deletions(-) > > > > diff --git a/drivers/platform/x86/Kconfig > > b/drivers/platform/x86/Kconfig > > index a1ed13183559..e46261b6def5 100644 > > --- a/drivers/platform/x86/Kconfig > > +++ b/drivers/platform/x86/Kconfig > > @@ -1287,7 +1287,7 @@ config INTEL_ATOMISP2_PM > > will be called intel_atomisp2_pm. > > > > config HUAWEI_WMI > > - tristate "Huawei WMI hotkeys driver" > > + tristate "Huawei WMI laptop extras driver" > > depends on ACPI_WMI > > depends on INPUT > > select INPUT_SPARSEKMAP > > @@ -1296,9 +1296,9 @@ config HUAWEI_WMI > > select LEDS_TRIGGER_AUDIO > > select NEW_LEDS > > help > > - This driver provides support for Huawei WMI hotkeys. > > - It enables the missing keys and adds support to the micmute > > - LED found on some of these laptops. > > + This driver provides support for some extra features found on > > Huawei > > + laptops that are controlled through WMI. These features are > > keyboard > > + hotkeys, micmute LED, charging thresholds, and fn-lock state. > > > > To compile this driver as a module, choose M here: the module > > will be called huawei-wmi. > > diff --git a/drivers/platform/x86/huawei-wmi.c > > b/drivers/platform/x86/huawei-wmi.c > > index 52fcac5b393a..4ec04196f386 100644 > > --- a/drivers/platform/x86/huawei-wmi.c > > +++ b/drivers/platform/x86/huawei-wmi.c > > @@ -1,32 +1,63 @@ > > // SPDX-License-Identifier: GPL-2.0 > > /* > > - * Huawei WMI hotkeys > > + * Huawei WMI laptop extras driver > > * > > * Copyright (C) 2018 Ayman Bagabas < > > ayman.baga...@gmail.com> > > */ > > > > #include > > +#include > > +#include > > #include > > #include > > #include > > #include > > +#include > > +#include > > +#include > > #include > > > > /* > > * Huawei WMI GUIDs > > */ > > -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" > > +#define AMW0_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" > > #define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" > > > > +/* 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; > > +/* AMW0_commands */ > > + &
Re: [PATCH v1 2/2] sound: Enable micmute led for Huawei laptops
On Wed, 2019-05-15 at 15:11 +0200, Takashi Iwai wrote: > On Mon, 13 May 2019 22:30:06 +0200, > Ayman Bagabas wrote: > > Since this LED is found on huawei laptops, we can hook it to > > huawei-wmi platform driver which uses the common WMI interface > > present > > in these laptops to control the LED. > > > > I've also made some renames and used product name instead of common > > name > > to avoid confusion. > > > > Signed-off-by: Ayman Bagabas > > This looks applicable independently from the patch 1? > If so, it can go via sound git tree while another via x86-platform > tree. > Yes it is. Will send another one. > > thanks, > > Takashi >
[PATCH v1 1/2] platform/x86: Huawei WMI laptop extras driver update
This update brings on the use of WMI BIOS management interface found on Huawei laptops. This interface can control the micmute LED found on most of these laptops, control charging thresholds values, and control fn-lock feature. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 8 +- drivers/platform/x86/huawei-wmi.c | 578 +- 2 files changed, 500 insertions(+), 86 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a1ed13183559..e46261b6def5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1287,7 +1287,7 @@ config INTEL_ATOMISP2_PM will be called intel_atomisp2_pm. config HUAWEI_WMI - tristate "Huawei WMI hotkeys driver" + tristate "Huawei WMI laptop extras driver" depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1296,9 +1296,9 @@ config HUAWEI_WMI select LEDS_TRIGGER_AUDIO select NEW_LEDS help - This driver provides support for Huawei WMI hotkeys. - It enables the missing keys and adds support to the micmute - LED found on some of these laptops. + This driver provides support for some extra features found on Huawei + laptops that are controlled through WMI. These features are keyboard + hotkeys, micmute LED, charging thresholds, and fn-lock state. To compile this driver as a module, choose M here: the module will be called huawei-wmi. diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..4ec04196f386 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -1,32 +1,63 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Huawei WMI hotkeys + * Huawei WMI laptop extras driver * * Copyright (C) 2018 Ayman Bagabas */ #include +#include +#include #include #include #include #include +#include +#include +#include #include /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C9062910" #define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" +/* 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; +/* AMW0_commands */ + +enum wmaa_cmd { + BATTERY_GET, /* \GBTT 0x1103 */ + BATTERY_SET, /* \SBTT 0xXXYY1003 */ + FN_LOCK_GET, /* \GFRS 0x0604 */ + 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_reset; + bool ec_micmute; +}; + +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 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 +68,169 @@ 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) +/* 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_battery_reset = { + .battery_reset = true, +}; + +static struct quirk_entry quirk_ec_micmute = { + .ec_micmute = 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_reset + }, + { + .callback = dmi_matched, + .ident = "Huawei MateBook X", + .matches = { +
[PATCH v1 2/2] sound: Enable micmute led for Huawei laptops
Since this LED is found on huawei laptops, we can hook it to huawei-wmi platform driver which uses the common WMI interface present in these laptops to control the LED. I've also made some renames and used product name instead of common name to avoid confusion. Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 9 - 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 42cd3945e0de..3661470766ba 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5684,7 +5684,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, - ALC256_FIXUP_HUAWEI_MBXP_PINS, + ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, ALC285_FIXUP_LENOVO_HEADPHONE_NOISE, @@ -5975,7 +5975,7 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, - [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + [ALC256_FIXUP_HUAWEI_MACH_WX9_PINS] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { {0x12, 0x90a60130}, @@ -6996,9 +6996,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), - SND_PCI_QUIRK(0x19e5, 0x3200, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), - SND_PCI_QUIRK(0x19e5, 0x3201, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), - SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 @@ -7057,6 +7055,7 @@ static const struct snd_pci_quirk alc269_fixup_vendor_tbl[] = { SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), {} }; -- 2.20.1
[PATCH v1 0/2] Huawei WMI laptop extras driver
Using the WMI BIOS management interface found on Huawei laptops, extra features such as micmute LED, charging thresholds, and fn-lock can be controlled. A platform driver has been implemented along with the existing WMI driver. The platform driver controls the LED, battery charging thresholds, and fn-lock. We enable sound micmute hooks to platform driver on Huawei laptops to get the LED to work. Ayman Bagabas (2): platform/x86: Huawei WMI laptop extras driver update sound: Enable micmute led for Huawei laptops drivers/platform/x86/Kconfig | 8 +- drivers/platform/x86/huawei-wmi.c | 578 +- sound/pci/hda/patch_realtek.c | 9 +- 3 files changed, 504 insertions(+), 91 deletions(-) -- 2.20.1
[PATCH v11 1/3] ALSA: hda: fix front speakers on Huawei MBXP.
This patch solves bug 200501 'Only 2 of 4 speakers playing sound.' https://bugzilla.kernel.org/show_bug.cgi?id=200501 It enables the front speakers on Huawei Matebook X Pro laptops. These laptops come with Dolby Atmos sound system and these pins configuration enables the front speakers. Reviewed-by: Andy Shevchenko Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 993d34c141c2..77ee471a67e5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5490,6 +5490,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, + ALC256_FIXUP_HUAWEI_MBXP_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, }; @@ -5761,6 +5762,22 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x4000}, + {0x14, 0x90170110}, + {0x18, 0x41f0}, + {0x19, 0x04a11040}, + {0x1a, 0x41f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x41f0}, + {0x21, 0x04211020}, + { } + }, + }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_x101_headset_mic, @@ -6591,6 +6608,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 -- 2.19.2
[PATCH v11 2/3] x86: add support for Huawei WMI hotkeys.
This driver adds support for missing hotkeys on some Huawei laptops. Laptops such as the Matebook X have non functioning hotkeys. Whereas newer laptops such as the Matebook X Pro come with working hotkeys out of the box. Old laptops, such as the Matebook X, report hotkey events through ACPI device "\WMI0". However, new laptops, such as the Matebook X Pro, does not have this WMI device. All the hotkeys on the Matebook X Pro work fine without this patch except (micmute, wlan, and huawei key). These keys and the brightness keys report events to "\AMW0" ACPI device. One problem is that brightness keys on the Matebook X Pro work without this patch. This results in reporting two brightness key press events one is captured by ACPI and another by this driver. A solution would be to check if such event came from the "\AMW0" WMI driver then skip reporting event. Another solution would be to leave this to user-space to handle. Which can be achieved by using "hwdb" tables and remap those keys to "unknown". This solution seems more natural to me because it leaves the decision to user-space. Acked-by: Andy Shevchenko Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 17 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 208 ++ 3 files changed, 226 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 87f70e8f4dd0..45ef4d22f14c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1292,6 +1292,23 @@ config INTEL_ATOMISP2_PM To compile this driver as a module, choose M here: the module will be called intel_atomisp2_pm. +config HUAWEI_WMI + tristate "Huawei WMI hotkeys driver" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + select NEW_LEDS + help + This driver provides support for Huawei WMI hotkeys. + It enables the missing keys and adds support to the micmute + LED found on some of these laptops. + + To compile this driver as a module, choose M here: the module + will be called huawei-wmi. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 39ae94135406..d841c550e3cc 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c new file mode 100644 index ..59872f87b741 --- /dev/null +++ b/drivers/platform/x86/huawei-wmi.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Huawei WMI hotkeys + * + * Copyright (C) 2018 Ayman Bagabas + */ + +#include +#include +#include +#include +#include +#include + +/* + * Huawei WMI GUIDs + */ +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" + +#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" + +struct huawei_wmi_priv { + struct input_dev *idev; + struct led_classdev cdev; + acpi_handle handle; + char *acpi_method; +}; + +static const struct key_entry huawei_wmi_keymap[] = { + { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, + { KE_KEY,0x284, { KEY_MUTE } }, + { KE_KEY,0x285, { KEY_VOLUMEDOWN } }, + { KE_KEY,0x286, { KEY_VOLUMEUP } }, + { KE_KEY,0x287, { KEY_MICMUTE } }, + { KE_KEY,0x289, { KEY_WLAN } }, + // Huawei |M| key + { KE_KEY,0x28a, { KEY_CONFIG } }, + // Keyboard backlight + { 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) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); + 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 = a
[PATCH v11 3/3] ALSA: hda: add support for Huawei WMI micmute LED
Some of Huawei laptops come with a LED in the micmute key. This patch enables the use of micmute LED for these devices: 1. Matebook X (19e5:3200), (19e5:3201) 2. Matebook X Pro (19e5:3204) Reviewed-by: Andy Shevchenko Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 4 1 file changed, 4 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 77ee471a67e5..9766fd249bdf 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5777,6 +5777,8 @@ static const struct hda_fixup alc269_fixups[] = { {0x21, 0x04211020}, { } }, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, @@ -6608,6 +6610,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3200, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), + SND_PCI_QUIRK(0x19e5, 0x3201, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ -- 2.19.2
[PATCH v11 0/3] Huawei laptops
This patch set is based on the new audio LED triggers in topic/leds-trigger branch from git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git Changes in v11: * Minor code changes Changes in v10: * Use ec_get_handle instead of acpi_get_handle since we are using the ec device * Switch to WMI0_EXPENSIVE_GUID and wmi_query_block to fetch keycode when WMI0 is used Changes in v9: * Explicitly use NULL in acpi_get_handle * Drop __initconst from huawei_wmi_keymap Changes in v8: * Switch to wmi_driver * Use devm to allocate and manage devices * Skip registering LED subsystem if a ACPI controller method was not found Changes in v7: * Use audio LED triggers patch set * Use KEY_CONFIG (XF86Tools) instead of KEY_PROG1. In Windows, the key is used to launch Huawei PC manager. XF86Tools is used by default on most desktop environments i.e. Gnome. Changes in v6: * Review tags Changes in v5: * Consistency in file names * How module would be enabled (Kconfig) * Match license in SPDX and MODULE_LICENSE Changes in v4: * Code formatting Changes in v3: * Support for Huawei MBX * Style and formatting issues Ayman Bagabas (3): ALSA: hda: fix front speakers on Huawei MBXP. x86: add support for Huawei WMI hotkeys. ALSA: hda: add support for Huawei WMI micmute LED drivers/platform/x86/Kconfig | 17 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 208 ++ sound/pci/hda/patch_realtek.c | 22 4 files changed, 248 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c -- 2.19.2
Re: [PATCH v10 2/3] x86: add support for Huawei WMI hotkeys.
On Tue, 2018-12-11 at 12:14 +0200, Andy Shevchenko wrote: > On Tue, Dec 11, 2018 at 8:02 AM Ayman Bagabas < > ayman.baga...@gmail.com> wrote: > > This driver adds support for missing hotkeys on some Huawei > > laptops. > > Laptops such as the Matebook X have non functioning hotkeys. > > Whereas > > newer laptops such as the Matebook X Pro come with working hotkeys > > out > > of the box. > > > > Old laptops, such as the Matebook X, report hotkey events through > > ACPI > > device "\WMI0". However, new laptops, such as the Matebook X Pro, > > does not have this WMI device. > > > > All the hotkeys on the Matebook X Pro work fine > > without this patch except (micmute, wlan, and huawei key). These > > keys > > and the brightness keys report events to "\AMW0" ACPI device. One > > problem is that brightness keys on the Matebook X Pro work without > > this > > patch. This results in reporting two brightness key press > > events one is captured by ACPI and another by this driver. > > > > A solution would be to check if such event came from the "\AMW0" > > WMI driver > > then skip reporting event. Another solution would be to leave this > > to > > user-space to handle. Which can be achieved by using "hwdb" tables > > and > > remap those keys to "unknown". This solution seems more natural to > > me > > because it leaves the decision to user-space. > > > > Reviewed-by: Takashi Iwai > > Signed-off-by: Ayman Bagabas > > --- > > drivers/platform/x86/Kconfig | 17 +++ > > drivers/platform/x86/Makefile | 1 + > > drivers/platform/x86/huawei-wmi.c | 220 > > ++ > > 3 files changed, 238 insertions(+) > > create mode 100644 drivers/platform/x86/huawei-wmi.c > > > > diff --git a/drivers/platform/x86/Kconfig > > b/drivers/platform/x86/Kconfig > > index 87f70e8f4dd0..45ef4d22f14c 100644 > > --- a/drivers/platform/x86/Kconfig > > +++ b/drivers/platform/x86/Kconfig > > @@ -1292,6 +1292,23 @@ config INTEL_ATOMISP2_PM > > To compile this driver as a module, choose M here: the > > module > > will be called intel_atomisp2_pm. > > > > +config HUAWEI_WMI > > + tristate "Huawei WMI hotkeys driver" > > + depends on ACPI_WMI > > + depends on INPUT > > + select INPUT_SPARSEKMAP > > + select LEDS_CLASS > > + select LEDS_TRIGGERS > > + select LEDS_TRIGGER_AUDIO > > + select NEW_LEDS > > + help > > + This driver provides support for Huawei WMI hotkeys. > > + It enables the missing keys and adds support to the > > micmute > > + LED found on some of these laptops. > > + > > + To compile this driver as a module, choose M here: the > > module > > + will be called huawei-wmi. > > + > > endif # X86_PLATFORM_DEVICES > > > > config PMC_ATOM > > diff --git a/drivers/platform/x86/Makefile > > b/drivers/platform/x86/Makefile > > index 39ae94135406..d841c550e3cc 100644 > > --- a/drivers/platform/x86/Makefile > > +++ b/drivers/platform/x86/Makefile > > @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o > > obj-$(CONFIG_HP_ACCEL) += hp_accel.o > > obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o > > obj-$(CONFIG_HP_WMI) += hp-wmi.o > > +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o > > obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o > > obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o > > obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o > > diff --git a/drivers/platform/x86/huawei-wmi.c > > b/drivers/platform/x86/huawei-wmi.c > > new file mode 100644 > > index ..89ba7ea33499 > > --- /dev/null > > +++ b/drivers/platform/x86/huawei-wmi.c > > @@ -0,0 +1,220 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Huawei WMI hotkeys > > + * > > + * Copyright (C) 2018 Ayman Bagabas < > > ayman.baga...@gmail.com> > > + */ > > + > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +/* > > + * Huawei WMI GUIDs > > + */ > > +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" > > +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" > > + > > +#define WMI0_EXPENSIVE_GUID &q
[PATCH v10 3/3] ALSA: hda: add support for Huawei WMI micmute LED
Some of Huawei laptops come with a LED in the micmute key. This patch enables the use of micmute LED for these devices: 1. Matebook X (19e5:3200), (19e5:3201) 2. Matebook X Pro (19e5:3204) Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 6 +- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 1326f32f4574..9766fd249bdf 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5776,7 +5776,9 @@ static const struct hda_fixup alc269_fixups[] = { {0x1e, 0x41f0}, {0x21, 0x04211020}, { } - } + }, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, @@ -6608,6 +6610,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3200, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), + SND_PCI_QUIRK(0x19e5, 0x3201, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ -- 2.19.2
[PATCH v10 2/3] x86: add support for Huawei WMI hotkeys.
This driver adds support for missing hotkeys on some Huawei laptops. Laptops such as the Matebook X have non functioning hotkeys. Whereas newer laptops such as the Matebook X Pro come with working hotkeys out of the box. Old laptops, such as the Matebook X, report hotkey events through ACPI device "\WMI0". However, new laptops, such as the Matebook X Pro, does not have this WMI device. All the hotkeys on the Matebook X Pro work fine without this patch except (micmute, wlan, and huawei key). These keys and the brightness keys report events to "\AMW0" ACPI device. One problem is that brightness keys on the Matebook X Pro work without this patch. This results in reporting two brightness key press events one is captured by ACPI and another by this driver. A solution would be to check if such event came from the "\AMW0" WMI driver then skip reporting event. Another solution would be to leave this to user-space to handle. Which can be achieved by using "hwdb" tables and remap those keys to "unknown". This solution seems more natural to me because it leaves the decision to user-space. Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 17 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 220 ++ 3 files changed, 238 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 87f70e8f4dd0..45ef4d22f14c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1292,6 +1292,23 @@ config INTEL_ATOMISP2_PM To compile this driver as a module, choose M here: the module will be called intel_atomisp2_pm. +config HUAWEI_WMI + tristate "Huawei WMI hotkeys driver" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + select NEW_LEDS + help + This driver provides support for Huawei WMI hotkeys. + It enables the missing keys and adds support to the micmute + LED found on some of these laptops. + + To compile this driver as a module, choose M here: the module + will be called huawei-wmi. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 39ae94135406..d841c550e3cc 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c new file mode 100644 index ..89ba7ea33499 --- /dev/null +++ b/drivers/platform/x86/huawei-wmi.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Huawei WMI hotkeys + * + * Copyright (C) 2018 Ayman Bagabas + */ + +#include +#include +#include +#include +#include +#include + +/* + * Huawei WMI GUIDs + */ +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" + +#define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" + +struct huawei_wmi_priv { + struct input_dev *idev; + struct led_classdev cdev; + acpi_handle handle; + char *acpi_method; +}; + +static const struct key_entry huawei_wmi_keymap[] = { + { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, + { KE_KEY,0x284, { KEY_MUTE } }, + { KE_KEY,0x285, { KEY_VOLUMEDOWN } }, + { KE_KEY,0x286, { KEY_VOLUMEUP } }, + { KE_KEY,0x287, { KEY_MICMUTE } }, + { KE_KEY,0x289, { KEY_WLAN } }, + // Huawei |M| key + { KE_KEY,0x28a, { KEY_CONFIG } }, + // Keyboard backlight + { 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) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); + 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_INT
[PATCH v10 1/3] ALSA: hda: fix front speakers on Huawei MBXP.
This patch solves bug 200501 'Only 2 of 4 speakers playing sound.' https://bugzilla.kernel.org/show_bug.cgi?id=200501 It enables the front speakers on Huawei Matebook X Pro laptops. These laptops come with Dolby Atmos sound system and these pins configuration enables the front speakers. Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 993d34c141c2..1326f32f4574 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5490,6 +5490,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, + ALC256_FIXUP_HUAWEI_MBXP_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, }; @@ -5761,6 +5762,22 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x4000}, + {0x14, 0x90170110}, + {0x18, 0x41f0}, + {0x19, 0x04a11040}, + {0x1a, 0x41f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x41f0}, + {0x21, 0x04211020}, + { } + } + }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_x101_headset_mic, @@ -6591,6 +6608,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 -- 2.19.2
[PATCH v10 0/3] Huawei laptops
This patch set is based on the new audio LED triggers in topic/leds-trigger branch from git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git Changes in v10: * Use ec_get_handle instead of acpi_get_handle since we are using the ec device * Switch to WMI0_EXPENSIVE_GUID and wmi_query_block to fetch keycode when WMI0 is used Changes in v9: * Explicitly use NULL in acpi_get_handle * Drop __initconst from huawei_wmi_keymap Changes in v8: * Switch to wmi_driver * Use devm to allocate and manage devices * Skip registering LED subsystem if a ACPI controller method was not found Changes in v7: * Use audio LED triggers patch set * Use KEY_CONFIG (XF86Tools) instead of KEY_PROG1. In Windows, the key is used to launch Huawei PC manager. XF86Tools is used by default on most desktop environments i.e. Gnome. Changes in v6: * Review tags Changes in v5: * Consistency in file names * How module would be enabled (Kconfig) * Match license in SPDX and MODULE_LICENSE Changes in v4: * Code formatting Changes in v3: * Support for Huawei MBX * Style and formatting issues Ayman Bagabas (3): ALSA: hda: fix front speakers on Huawei MBXP. x86: add support for Huawei WMI hotkeys. ALSA: hda: add support for Huawei WMI micmute LED drivers/platform/x86/Kconfig | 17 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 220 ++ sound/pci/hda/patch_realtek.c | 22 +++ 4 files changed, 260 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c -- 2.19.2
Re: [PATCH v9 2/3] x86: add support for Huawei WMI hotkeys.
On Fri, 2018-12-07 at 00:52 -0500, ayman.baga...@gmail.com wrote: On Mon, 2018-12-03 at 21:17 +0200, Andy Shevchenko wrote: > On Mon, Dec 3, 2018 at 9:04 PM Takashi Iwai wrote: > > On Mon, 03 Dec 2018 19:53:39 +0100, > > Ayman Bagabas wrote: > > > + if (code == 0x80) { > > > + acpi_status status; > > > + acpi_handle handle; > > > + unsigned long long result; > > > + union acpi_object args[1]; > > > + struct acpi_object_list arg_list = { > > > + .pointer = args, > > > + .count = ARRAY_SIZE(args), > > > + }; > > > + > > > + args[0].type = ACPI_TYPE_INTEGER; > > > + args[0].integer.value = 0; > > > + > > > + status = acpi_get_handle(NULL, "\\WMI0", &handle); > > > + if (ACPI_FAILURE(status)) { > > > + dev_err(&wdev->dev, "Unable to get ACPI > > > handle\n"); > > > + return; > > > + } > > > + > > > + status = acpi_evaluate_integer(NULL, "WQ00", > > > &arg_list, &result); > > > > I guess you need to pass handle here? In the earlier version, you > > passed \\WMI0.WQ00, so it worked with NULL handle. But now it's no > > longer so... > > I think in this case we don't need to have a separate call to get > handle and try to get integer directly. In either we will have an > error case if method / namespace / etc is not found. > > I was digging further into the DSDT table of the laptop that has WQ00 method and it turned out that this method is a ACPI_WMI_EXPENSIVE. It has its own GUID of "39142400-C6A3-40fa-BADB-8A2652834100". I believe wmi_query_block, which is deprecated BTW, can be used to access this method instead of acpi_evaluate. However, I don't have a machine to test it on. Any ideas?
Re: [PATCH v9 2/3] x86: add support for Huawei WMI hotkeys.
On Mon, 2018-12-03 at 21:17 +0200, Andy Shevchenko wrote: > On Mon, Dec 3, 2018 at 9:04 PM Takashi Iwai wrote: > > On Mon, 03 Dec 2018 19:53:39 +0100, > > Ayman Bagabas wrote: > > > + if (code == 0x80) { > > > + acpi_status status; > > > + acpi_handle handle; > > > + unsigned long long result; > > > + union acpi_object args[1]; > > > + struct acpi_object_list arg_list = { > > > + .pointer = args, > > > + .count = ARRAY_SIZE(args), > > > + }; > > > + > > > + args[0].type = ACPI_TYPE_INTEGER; > > > + args[0].integer.value = 0; > > > + > > > + status = acpi_get_handle(NULL, "\\WMI0", &handle); > > > + if (ACPI_FAILURE(status)) { > > > + dev_err(&wdev->dev, "Unable to get ACPI > > > handle\n"); > > > + return; > > > + } > > > + > > > + status = acpi_evaluate_integer(NULL, "WQ00", > > > &arg_list, &result); > > > > I guess you need to pass handle here? In the earlier version, you > > passed \\WMI0.WQ00, so it worked with NULL handle. But now it's no > > longer so... > > I think in this case we don't need to have a separate call to get > handle and try to get integer directly. In either we will have an > error case if method / namespace / etc is not found. Agree. > > > Other than that, it looks OK to me. > > Reviewed-by: Takashi Iwai Thanks. > > > > But, you don't need to rush too much. Instead, better to test your > > own patches and make sure that they really work before submitting > > the > > final version. > > Yes, I will wait couple of days for v10 in hope it will be tested > carefully. You're right, I will test it more and make sure everything work as expected. >
Re: [PATCH v9 2/3] x86: add support for Huawei WMI hotkeys.
On Mon, 2018-12-03 at 11:41 -0800, Randy Dunlap wrote: > On 12/3/18 10:53 AM, Ayman Bagabas wrote: > > This driver adds support for missing hotkeys on some Huawei > > laptops. > > Laptops such as the Matebook X have non functioning hotkeys. > > Whereas > > newer laptops such as the Matebook X Pro come with working hotkeys > > out > > of the box. > > > > Old laptops, such as the Matebook X, report hotkey events through > > ACPI > > device "\WMI0". However, new laptops, such as the Matebook X Pro, > > does not have this WMI device. > > > > All the hotkeys on the Matebook X Pro work fine > > without this patch except (micmute, wlan, and huawei key). These > > keys > > and the brightness keys report events to "\AMW0" ACPI device. One > > problem is that brightness keys on the Matebook X Pro work without > > this > > patch. This results in reporting two brightness key press > > events one is captured by ACPI and another by this driver. > > > > A solution would be to check if such event came from the "\AMW0" > > WMI driver > > then skip reporting event. Another solution would be to leave this > > to > > user-space to handle. Which can be achieved by using "hwdb" tables > > and > > remap those keys to "unknown". This solution seems more natural to > > me > > because it leaves the decision to user-space. > > > > Signed-off-by: Ayman Bagabas > > --- > > drivers/platform/x86/Kconfig | 16 +++ > > drivers/platform/x86/Makefile | 1 + > > drivers/platform/x86/huawei-wmi.c | 226 > > ++ > > 3 files changed, 243 insertions(+) > > create mode 100644 drivers/platform/x86/huawei-wmi.c > > > > diff --git a/drivers/platform/x86/Kconfig > > b/drivers/platform/x86/Kconfig > > index 87f70e8f4dd0..27db3ce7a1e1 100644 > > --- a/drivers/platform/x86/Kconfig > > +++ b/drivers/platform/x86/Kconfig > > @@ -1292,6 +1292,22 @@ config INTEL_ATOMISP2_PM > > To compile this driver as a module, choose M here: the module > > will be called intel_atomisp2_pm. > > > > +config HUAWEI_WMI > > + tristate "Huawei WMI hotkeys driver" > > + depends on ACPI_WMI > > + depends on INPUT > > + select INPUT_SPARSEKMAP > > + select LEDS_CLASS > > but LEDS_CLASS is only available if NEW_LEDS is set/enabled, so it > either > depends on LEDS_CLASS... or it needs to select NEW_LEDS, which is > what other > drivers in this Kconfig file do, even though our b.d. has said a few > times > that any one driver should not "select" => enable an entire > subsystem, > so they all should be "depends on NEW_LEDS" IMHO. Noted, I'll select NEW_LEDS just like other drivers in Kconfig. > > > + select LEDS_TRIGGERS > > + select LEDS_TRIGGER_AUDIO > > + help > > + This driver provides support for Huawei WMI hotkeys. > > + It enables the missing keys and adds support to the micmute > > + LED found on some of these laptops. > > + > > + To compile this driver as a module, choose M here: the module > > + will be called huawei-wmi. > > + > > endif # X86_PLATFORM_DEVICES > > > > config PMC_ATOM > >
[PATCH v9 2/3] x86: add support for Huawei WMI hotkeys.
This driver adds support for missing hotkeys on some Huawei laptops. Laptops such as the Matebook X have non functioning hotkeys. Whereas newer laptops such as the Matebook X Pro come with working hotkeys out of the box. Old laptops, such as the Matebook X, report hotkey events through ACPI device "\WMI0". However, new laptops, such as the Matebook X Pro, does not have this WMI device. All the hotkeys on the Matebook X Pro work fine without this patch except (micmute, wlan, and huawei key). These keys and the brightness keys report events to "\AMW0" ACPI device. One problem is that brightness keys on the Matebook X Pro work without this patch. This results in reporting two brightness key press events one is captured by ACPI and another by this driver. A solution would be to check if such event came from the "\AMW0" WMI driver then skip reporting event. Another solution would be to leave this to user-space to handle. Which can be achieved by using "hwdb" tables and remap those keys to "unknown". This solution seems more natural to me because it leaves the decision to user-space. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 16 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 226 ++ 3 files changed, 243 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 87f70e8f4dd0..27db3ce7a1e1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1292,6 +1292,22 @@ config INTEL_ATOMISP2_PM To compile this driver as a module, choose M here: the module will be called intel_atomisp2_pm. +config HUAWEI_WMI + tristate "Huawei WMI hotkeys driver" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + help + This driver provides support for Huawei WMI hotkeys. + It enables the missing keys and adds support to the micmute + LED found on some of these laptops. + + To compile this driver as a module, choose M here: the module + will be called huawei-wmi. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 39ae94135406..d841c550e3cc 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c new file mode 100644 index ..afa030fd1285 --- /dev/null +++ b/drivers/platform/x86/huawei-wmi.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Huawei WMI hotkeys + * + * Copyright (C) 2018 Ayman Bagabas + */ + +#include +#include +#include +#include +#include +#include + +/* + * Huawei WMI Events GUIDs + */ +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" + +struct huawei_wmi_priv { + struct input_dev *idev; + struct led_classdev cdev; + acpi_handle handle; + char *acpi_method; +}; + +static const struct key_entry huawei_wmi_keymap[] = { + { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, + { KE_KEY,0x284, { KEY_MUTE } }, + { KE_KEY,0x285, { KEY_VOLUMEDOWN } }, + { KE_KEY,0x286, { KEY_VOLUMEUP } }, + { KE_KEY,0x287, { KEY_MICMUTE } }, + { KE_KEY,0x289, { KEY_WLAN } }, + // Huawei |M| button + { KE_KEY,0x28a, { KEY_CONFIG } }, + // Keyboard light + { 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) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); + 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) { +
[PATCH v9 3/3] ALSA: hda: add support for Huawei WMI micmute LED
Some of Huawei laptops come with a LED in the micmute key. This patch enables the use of micmute LED for these devices: 1. Matebook X (19e5:3200), (19e5:3201) 2. Matebook X Pro (19e5:3204) Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 6 +- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 1326f32f4574..9766fd249bdf 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5776,7 +5776,9 @@ static const struct hda_fixup alc269_fixups[] = { {0x1e, 0x41f0}, {0x21, 0x04211020}, { } - } + }, + .chained = true, + .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, @@ -6608,6 +6610,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3200, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), + SND_PCI_QUIRK(0x19e5, 0x3201, "Huawei MBX", ALC255_FIXUP_MIC_MUTE_LED), SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ -- 2.19.2
[PATCH v9 0/3] Huawei laptops
This patch set is based on new audio LED triggers in branch topic/leds-trigger from git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git Changes from v8: * Explicitly use NULL in acpi_get_handle * Drop __initconst from huawei_wmi_keymap Changes from v7: * Switch to wmi_driver * Use devm to allocate and manage devices * Skip registering LED subsystem if a ACPI controller method was not found Changes from v6: * Use audio LED triggers patch set * Use KEY_CONFIG (XF86Tools) instead of KEY_PROG1. In Windows, the key is used to launch Huawei PC manager. XF86Tools is used by default on most desktop environments i.e. Gnome. Changes from v5: * Review tags Changes from v4: * Consistency in file names * How module would be enabled (Kconfig) * Match license in SPDX and MODULE_LICENSE Changes from v3: * Code formatting Changes from v2: * Support for Huawei MBX * Style and formatting issues Ayman Bagabas (3): ALSA: hda: fix front speakers on Huawei MBXP. x86: add support for Huawei WMI hotkeys. ALSA: hda: add support for Huawei WMI micmute LED drivers/platform/x86/Kconfig | 16 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 226 ++ sound/pci/hda/patch_realtek.c | 22 +++ 4 files changed, 265 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c -- 2.19.2
[PATCH v9 1/3] ALSA: hda: fix front speakers on Huawei MBXP.
This patch solves bug 200501 'Only 2 of 4 speakers playing sound.' https://bugzilla.kernel.org/show_bug.cgi?id=200501 It enables the front speakers on Huawei Matebook X Pro laptops. These laptops come with Dolby Atmos sound system and these pins configuration enables the front speakers. Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 993d34c141c2..1326f32f4574 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5490,6 +5490,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, + ALC256_FIXUP_HUAWEI_MBXP_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, }; @@ -5761,6 +5762,22 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x4000}, + {0x14, 0x90170110}, + {0x18, 0x41f0}, + {0x19, 0x04a11040}, + {0x1a, 0x41f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x41f0}, + {0x21, 0x04211020}, + { } + } + }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_x101_headset_mic, @@ -6591,6 +6608,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 -- 2.19.2
Re: [PATCH v8 3/3] ALSA: hda: add support for Huawei WMI micmute LED
On Mon, 2018-12-03 at 13:00 +0100, Takashi Iwai wrote: > On Fri, 30 Nov 2018 00:57:38 +0100, > Ayman Bagabas wrote: > > Some of Huawei laptops come with a LED in the micmute key. This > > patch > > enables the use of micmute LED for these devices: > > 1. Matebook X (19e5:3200), (19e5:3201) > > 2. Matebook X Pro (19e5:3204) > > > > Signed-off-by: Ayman Bagabas > > Feel free to take my ack > Reviewed-by: Takashi Iwai Thanks, i'll include that in next submission. > > > thanks, > > Takashi
Re: [PATCH v8 2/3] x86: add support for Huawei WMI hotkeys.
On Mon, 2018-12-03 at 13:00 +0100, Takashi Iwai wrote: > On Fri, 30 Nov 2018 00:57:37 +0100, > Ayman Bagabas wrote: > > This driver adds support for missing hotkeys on some Huawei > > laptops. > > Laptops such as the Matebook X have non functioning hotkeys. > > Whereas > > newer laptops such as the Matebook X Pro come with working hotkeys > > out > > of the box. > > > > Old laptops, such as the Matebook X, report hotkey events through > > ACPI > > device "\WMI0". However, new laptops, such as the Matebook X Pro, > > does not have this WMI device. > > > > All the hotkeys on the Matebook X Pro work fine > > without this patch except (micmute, wlan, and huawei key). These > > keys > > and the brightness keys report events to "\AMW0" ACPI device. One > > problem is that brightness keys on the Matebook X Pro work without > > this > > patch. This results in reporting two brightness key press > > events one is captured by ACPI and another by this driver. > > > > A solution would be to check if such event came from the "\AMW0" > > WMI driver > > then skip reporting event. Another solution would be to leave this > > to user-space to handle. Which > > can be achieved by using "hwdb" tables and remap those keys to > > "unknown". > > This solution seems more natural to me because it leaves the > > decision to > > user-space. > > > > Signed-off-by: Ayman Bagabas > > The new patch looks much better than the previous one, thanks for > working on it. > > Just a few comments: > > > +struct huawei_wmi_priv { > > + struct input_dev *idev; > > + struct led_classdev cdev; > > + acpi_handle handle; > > Is this handle set in anywhere? I couldn't see it in your patch. > If it's supposed to be NULL, passing NULL explicitly makes your > intention clearer. > > > > +static int huawei_wmi_leds_setup(struct wmi_device *wdev) > > +{ > > + struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); > > + acpi_status status; > > + > > + // Skip registering LED subsystem if no ACPI method was found. > > + status = acpi_get_handle(priv->handle, "\\_SB.PCI0.LPCB.EC0", > > &priv->handle); > > + if (ACPI_FAILURE(status)) > > + return 0; > > + > > + 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; > > What about suspend/resume? > When the driver is bound wit HD-audio, the HD-audio will restore the > state at resume, so it would work. But, by providing the LED class > device, it is supposed to work even without HD-audio, so it might > make > sense to pass LED_CORE_SUSPENDRESUME, too. Besides that, is there anything needed for wmi_device suspend/resume? > > > > +static int __init huawei_wmi_init(void) > > +{ > > + if (!(wmi_has_guid(WMI0_EVENT_GUID) || > > wmi_has_guid(AMW0_EVENT_GUID))) { > > + pr_debug("Compatible WMI GUID not found\n"); > > + return -ENODEV; > > + } > > This is superfluous when you implement with wmi_driver. > In theory, the supported GUID can be added dynamically via sysfs, > too. > I left it that way so it doesn't insert the module if these GUIDs were not found. Should I drop that and use module_wmi_driver instead? > > thanks, > > Takashi
[PATCH v8 2/3] x86: add support for Huawei WMI hotkeys.
This driver adds support for missing hotkeys on some Huawei laptops. Laptops such as the Matebook X have non functioning hotkeys. Whereas newer laptops such as the Matebook X Pro come with working hotkeys out of the box. Old laptops, such as the Matebook X, report hotkey events through ACPI device "\WMI0". However, new laptops, such as the Matebook X Pro, does not have this WMI device. All the hotkeys on the Matebook X Pro work fine without this patch except (micmute, wlan, and huawei key). These keys and the brightness keys report events to "\AMW0" ACPI device. One problem is that brightness keys on the Matebook X Pro work without this patch. This results in reporting two brightness key press events one is captured by ACPI and another by this driver. A solution would be to check if such event came from the "\AMW0" WMI driver then skip reporting event. Another solution would be to leave this to user-space to handle. Which can be achieved by using "hwdb" tables and remap those keys to "unknown". This solution seems more natural to me because it leaves the decision to user-space. Signed-off-by: Ayman Bagabas --- drivers/platform/x86/Kconfig | 16 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/huawei-wmi.c | 238 ++ 3 files changed, 255 insertions(+) create mode 100644 drivers/platform/x86/huawei-wmi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 87f70e8f4dd0..27db3ce7a1e1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1292,6 +1292,22 @@ config INTEL_ATOMISP2_PM To compile this driver as a module, choose M here: the module will be called intel_atomisp2_pm. +config HUAWEI_WMI + tristate "Huawei WMI hotkeys driver" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + help + This driver provides support for Huawei WMI hotkeys. + It enables the missing keys and adds support to the micmute + LED found on some of these laptops. + + To compile this driver as a module, choose M here: the module + will be called huawei-wmi. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 39ae94135406..d841c550e3cc 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o +obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c new file mode 100644 index ..5371c8e2ded7 --- /dev/null +++ b/drivers/platform/x86/huawei-wmi.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Huawei WMI hotkeys + * + * Copyright (C) 2018 Ayman Bagabas + */ + +#include +#include +#include +#include +#include +#include + +/* + * Huawei WMI Events GUIDs + */ +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" +#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C9062910" + +struct huawei_wmi_priv { + struct input_dev *idev; + struct led_classdev cdev; + acpi_handle handle; + char *acpi_method; +}; + +static const struct key_entry huawei_wmi_keymap[] __initconst = { + { KE_KEY,0x281, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY,0x282, { KEY_BRIGHTNESSUP } }, + { KE_KEY,0x284, { KEY_MUTE } }, + { KE_KEY,0x285, { KEY_VOLUMEDOWN } }, + { KE_KEY,0x286, { KEY_VOLUMEUP } }, + { KE_KEY,0x287, { KEY_MICMUTE } }, + { KE_KEY,0x289, { KEY_WLAN } }, + // Huawei |M| button + { KE_KEY,0x28a, { KEY_CONFIG } }, + // Keyboard light + { 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) +{ + struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); + 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[0].integer.value = 0; + args[1].integer.value = 0x04; + args[2].in
[PATCH v8 1/3] ALSA: hda: fix front speakers on Huawei MBXP.
This patch solves bug 200501 'Only 2 of 4 speakers playing sound.' https://bugzilla.kernel.org/show_bug.cgi?id=200501 It enables the front speakers on Huawei Matebook X Pro laptops. These laptops come with Dolby Atmos sound system and these pins configuration enables the front speakers. Reviewed-by: Takashi Iwai Signed-off-by: Ayman Bagabas --- sound/pci/hda/patch_realtek.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 993d34c141c2..1326f32f4574 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5490,6 +5490,7 @@ enum { ALC298_FIXUP_TPT470_DOCK, ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, + ALC256_FIXUP_HUAWEI_MBXP_PINS, ALC295_FIXUP_HP_X360, ALC221_FIXUP_HP_HEADSET_MIC, }; @@ -5761,6 +5762,22 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC256_FIXUP_HUAWEI_MBXP_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x4000}, + {0x14, 0x90170110}, + {0x18, 0x41f0}, + {0x19, 0x04a11040}, + {0x1a, 0x41f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x41f0}, + {0x21, 0x04211020}, + { } + } + }, [ALC269_FIXUP_ASUS_X101_FUNC] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_x101_headset_mic, @@ -6591,6 +6608,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ #if 0 -- 2.19.1