Re: [PATCH v1] platform/x86: Huawei laptop extras driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux