summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/wireless-hotkey.c
blob: 3151844d1699ddefbd33daf8c0a7de2ebcc6ebd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Airplane mode button for AMD, HP & Xiaomi laptops
 *
 *  Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com>
 *  Copyright (C) 2021 Advanced Micro Devices
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>

MODULE_DESCRIPTION("Airplane mode button for AMD, HP & Xiaomi laptops");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Hung");
MODULE_ALIAS("acpi*:HPQ6001:*");
MODULE_ALIAS("acpi*:WSTADEF:*");
MODULE_ALIAS("acpi*:AMDI0051:*");
MODULE_ALIAS("acpi*:LGEX0815:*");

struct wl_button {
	struct input_dev *input_dev;
	char phys[32];
};

static const struct acpi_device_id wl_ids[] = {
	{"HPQ6001", 0},
	{"WSTADEF", 0},
	{"AMDI0051", 0},
	{"LGEX0815", 0},
	{"", 0},
};

static int wireless_input_setup(struct device *dev)
{
	struct wl_button *button = dev_get_drvdata(dev);
	int err;

	button->input_dev = input_allocate_device();
	if (!button->input_dev)
		return -ENOMEM;

	snprintf(button->phys, sizeof(button->phys), "%s/input0",
		 acpi_device_hid(ACPI_COMPANION(dev)));

	button->input_dev->name = "Wireless hotkeys";
	button->input_dev->phys = button->phys;
	button->input_dev->id.bustype = BUS_HOST;
	button->input_dev->evbit[0] = BIT(EV_KEY);
	set_bit(KEY_RFKILL, button->input_dev->keybit);

	err = input_register_device(button->input_dev);
	if (err)
		goto err_free_dev;

	return 0;

err_free_dev:
	input_free_device(button->input_dev);
	return err;
}

static void wireless_input_destroy(struct device *dev)
{
	struct wl_button *button = dev_get_drvdata(dev);

	input_unregister_device(button->input_dev);
	kfree(button);
}

static void wl_notify(acpi_handle handle, u32 event, void *data)
{
	struct wl_button *button = data;

	if (event != 0x80) {
		pr_info("Received unknown event (0x%x)\n", event);
		return;
	}

	input_report_key(button->input_dev, KEY_RFKILL, 1);
	input_sync(button->input_dev);
	input_report_key(button->input_dev, KEY_RFKILL, 0);
	input_sync(button->input_dev);
}

static int wl_probe(struct platform_device *pdev)
{
	struct acpi_device *adev;
	struct wl_button *button;
	int err;

	adev = ACPI_COMPANION(&pdev->dev);
	if (!adev)
		return -ENODEV;

	button = kzalloc_obj(struct wl_button);
	if (!button)
		return -ENOMEM;

	platform_set_drvdata(pdev, button);

	err = wireless_input_setup(&pdev->dev);
	if (err) {
		pr_err("Failed to setup wireless hotkeys\n");
		kfree(button);
		return err;
	}
	err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
					      wl_notify, button);
	if (err) {
		pr_err("Failed to install ACPI notify handler\n");
		wireless_input_destroy(&pdev->dev);
	}

	return err;
}

static void wl_remove(struct platform_device *pdev)
{
	acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
				       ACPI_DEVICE_NOTIFY, wl_notify);
	wireless_input_destroy(&pdev->dev);
}

static struct platform_driver wl_driver = {
	.probe = wl_probe,
	.remove = wl_remove,
	.driver = {
		.name = "wireless-hotkey",
		.acpi_match_table = wl_ids,
	},
};

module_platform_driver(wl_driver);