Commit 576d2881 authored by Takashi Iwai's avatar Takashi Iwai
Browse files

Merge branch 'topic/huawei-leds' into for-next



Pull Huawei LEDS and hotkey support.

Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parents 94ffb030 e2744fd7
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+208 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 *  Huawei WMI hotkeys
 *
 *  Copyright (C) 2018	      Ayman Bagabas <ayman.bagabas@gmail.com>
 */

#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/wmi.h>

/*
 * Huawei WMI GUIDs
 */
#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100"
#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000"

#define 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_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;
	}

	status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL);
	if (ACPI_FAILURE(status))
		return -ENXIO;

	return 0;
}

static int huawei_wmi_leds_setup(struct wmi_device *wdev)
{
	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->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;
	priv->cdev.default_trigger = "audio-micmute";
	priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
	priv->cdev.dev = &wdev->dev;
	priv->cdev.flags = LED_CORE_SUSPENDRESUME;

	return devm_led_classdev_register(&wdev->dev, &priv->cdev);
}

static void huawei_wmi_process_key(struct wmi_device *wdev, int code)
{
	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
	const struct key_entry *key;

	/*
	 * WMI0 uses code 0x80 to indicate a hotkey event.
	 * The actual key is fetched from the method WQ00
	 * using WMI0_EXPENSIVE_GUID.
	 */
	if (code == 0x80) {
		struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
		union acpi_object *obj;
		acpi_status status;

		status = wmi_query_block(WMI0_EXPENSIVE_GUID, 0, &response);
		if (ACPI_FAILURE(status))
			return;

		obj = (union acpi_object *)response.pointer;
		if (obj && obj->type == ACPI_TYPE_INTEGER)
			code = obj->integer.value;

		kfree(response.pointer);
	}

	key = sparse_keymap_entry_from_scancode(priv->idev, code);
	if (!key) {
		dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code);
		return;
	}

	sparse_keymap_report_entry(priv->idev, key, 1, true);
}

static void huawei_wmi_notify(struct wmi_device *wdev,
		union acpi_object *obj)
{
	if (obj->type == ACPI_TYPE_INTEGER)
		huawei_wmi_process_key(wdev, obj->integer.value);
	else
		dev_info(&wdev->dev, "Bad response type %d\n", obj->type);
}

static int huawei_wmi_input_setup(struct wmi_device *wdev)
{
	struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
	int err;

	priv->idev = devm_input_allocate_device(&wdev->dev);
	if (!priv->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;

	err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL);
	if (err)
		return err;

	return input_register_device(priv->idev);
}

static int huawei_wmi_probe(struct wmi_device *wdev)
{
	struct huawei_wmi_priv *priv;
	int err;

	priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	dev_set_drvdata(&wdev->dev, priv);

	err = huawei_wmi_input_setup(wdev);
	if (err)
		return err;

	return huawei_wmi_leds_setup(wdev);
}

static const struct wmi_device_id huawei_wmi_id_table[] = {
	{ .guid_string = WMI0_EVENT_GUID },
	{ .guid_string = AMW0_EVENT_GUID },
	{  }
};

static struct wmi_driver huawei_wmi_driver = {
	.driver = {
		.name = "huawei-wmi",
	},
	.id_table = huawei_wmi_id_table,
	.probe = huawei_wmi_probe,
	.notify = huawei_wmi_notify,
};

module_wmi_driver(huawei_wmi_driver);

MODULE_ALIAS("wmi:"WMI0_EVENT_GUID);
MODULE_ALIAS("wmi:"AMW0_EVENT_GUID);
MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>");
MODULE_DESCRIPTION("Huawei WMI hotkeys");
MODULE_LICENSE("GPL v2");
+22 −0
Original line number Diff line number Diff line
@@ -5572,6 +5572,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,
	ALC285_FIXUP_LENOVO_HEADPHONE_NOISE,
@@ -5852,6 +5853,24 @@ 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, 0x40000000},
			{0x14, 0x90170110},
			{0x18, 0x411111f0},
			{0x19, 0x04a11040},
			{0x1a, 0x411111f0},
			{0x1b, 0x90170112},
			{0x1d, 0x40759a05},
			{0x1e, 0x411111f0},
			{0x21, 0x04211020},
			{ }
		},
		.chained = true,
		.chain_id = ALC255_FIXUP_MIC_MUTE_LED
	},
	[ALC269_FIXUP_ASUS_X101_FUNC] = {
		.type = HDA_FIXUP_FUNC,
		.v.func = alc269_fixup_x101_headset_mic,
@@ -6742,6 +6761,9 @@ 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 */

#if 0