aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Tissoires <benjamin.tissoires@redhat.com>2016-12-05 10:10:33 -0500
committerAndy Shevchenko <andriy.shevchenko@linux.intel.com>2016-12-16 16:30:26 -0500
commit1a64b719d3ae0e4fb939d9a9e31abb60b4ce4eb1 (patch)
tree0c3538a780260e78e750ed7bf1f5c16a51aa729c
parent3dda3b3798f96d2974b5f60811142d3e25547807 (diff)
platform/x86: Introduce button support for the Surface 3
The Surface 3 is not following the ACPI spec for PNP0C40, but nearly. The device is connected to a I2C device that might have some magic but we don't know about. Just create the device after the enumeration and use the declared GPIOs to provide button support. This driver is just an adaptation of drivers/input/misc/soc_button_array.c The Surface Pro 3 is using an ACPI driver and matches against the bid of the device ("VGBI"). To prevent this incompatible driver to be used on the Surface Pro, we add a match on the Surface 3 bid "TEV2". link: https://bugzilla.kernel.org/show_bug.cgi?id=102761 Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
-rw-r--r--drivers/platform/x86/Kconfig6
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/surface3_button.c250
3 files changed, 257 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 0414d76dc15c..493e3386fbf6 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1023,6 +1023,12 @@ config SURFACE_PRO3_BUTTON
1023 ---help--- 1023 ---help---
1024 This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. 1024 This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
1025 1025
1026config SURFACE_3_BUTTON
1027 tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
1028 depends on ACPI && KEYBOARD_GPIO
1029 ---help---
1030 This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
1031
1026config INTEL_PUNIT_IPC 1032config INTEL_PUNIT_IPC
1027 tristate "Intel P-Unit IPC Driver" 1033 tristate "Intel P-Unit IPC Driver"
1028 ---help--- 1034 ---help---
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 6cd3d909b759..e9290290f026 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o
67obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o 67obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
68obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o 68obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
69obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o 69obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
70obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
70obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o 71obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
71obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ 72obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \
72 intel_telemetry_pltdrv.o \ 73 intel_telemetry_pltdrv.o \
diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/x86/surface3_button.c
new file mode 100644
index 000000000000..8bfd7f613d36
--- /dev/null
+++ b/drivers/platform/x86/surface3_button.c
@@ -0,0 +1,250 @@
1/*
2 * Supports for the button array on the Surface tablets.
3 *
4 * (C) Copyright 2016 Red Hat, Inc
5 *
6 * Based on soc_button_array.c:
7 *
8 * {C} Copyright 2014 Intel Corporation
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; version 2
13 * of the License.
14 */
15
16#include <linux/module.h>
17#include <linux/input.h>
18#include <linux/init.h>
19#include <linux/kernel.h>
20#include <linux/i2c.h>
21#include <linux/slab.h>
22#include <linux/acpi.h>
23#include <linux/gpio/consumer.h>
24#include <linux/gpio_keys.h>
25#include <linux/gpio.h>
26#include <linux/platform_device.h>
27
28
29#define SURFACE_BUTTON_OBJ_NAME "TEV2"
30#define MAX_NBUTTONS 4
31
32/*
33 * Some of the buttons like volume up/down are auto repeat, while others
34 * are not. To support both, we register two platform devices, and put
35 * buttons into them based on whether the key should be auto repeat.
36 */
37#define BUTTON_TYPES 2
38
39/*
40 * Power button, Home button, Volume buttons support is supposed to
41 * be covered by drivers/input/misc/soc_button_array.c, which is implemented
42 * according to "Windows ACPI Design Guide for SoC Platforms".
43 * However surface 3 seems not to obey the specs, instead it uses
44 * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly
45 * different in which the Home button is active high.
46 * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3
47 * is a reduce platform and thus uses GPIOs, not ACPI events.
48 * We choose an I2C driver here because we need to access the resources
49 * declared under the device node, while surfacepro3_button.c only needs
50 * the ACPI companion node.
51 */
52static const struct acpi_device_id surface3_acpi_match[] = {
53 { "MSHW0028", 0 },
54 { }
55};
56MODULE_DEVICE_TABLE(acpi, surface3_acpi_match);
57
58struct surface3_button_info {
59 const char *name;
60 int acpi_index;
61 unsigned int event_type;
62 unsigned int event_code;
63 bool autorepeat;
64 bool wakeup;
65 bool active_low;
66};
67
68struct surface3_button_data {
69 struct platform_device *children[BUTTON_TYPES];
70};
71
72/*
73 * Get the Nth GPIO number from the ACPI object.
74 */
75static int surface3_button_lookup_gpio(struct device *dev, int acpi_index)
76{
77 struct gpio_desc *desc;
78 int gpio;
79
80 desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS);
81 if (IS_ERR(desc))
82 return PTR_ERR(desc);
83
84 gpio = desc_to_gpio(desc);
85
86 gpiod_put(desc);
87
88 return gpio;
89}
90
91static struct platform_device *
92surface3_button_device_create(struct i2c_client *client,
93 const struct surface3_button_info *button_info,
94 bool autorepeat)
95{
96 const struct surface3_button_info *info;
97 struct platform_device *pd;
98 struct gpio_keys_button *gpio_keys;
99 struct gpio_keys_platform_data *gpio_keys_pdata;
100 int n_buttons = 0;
101 int gpio;
102 int error;
103
104 gpio_keys_pdata = devm_kzalloc(&client->dev,
105 sizeof(*gpio_keys_pdata) +
106 sizeof(*gpio_keys) * MAX_NBUTTONS,
107 GFP_KERNEL);
108 if (!gpio_keys_pdata)
109 return ERR_PTR(-ENOMEM);
110
111 gpio_keys = (void *)(gpio_keys_pdata + 1);
112
113 for (info = button_info; info->name; info++) {
114 if (info->autorepeat != autorepeat)
115 continue;
116
117 gpio = surface3_button_lookup_gpio(&client->dev,
118 info->acpi_index);
119 if (!gpio_is_valid(gpio))
120 continue;
121
122 gpio_keys[n_buttons].type = info->event_type;
123 gpio_keys[n_buttons].code = info->event_code;
124 gpio_keys[n_buttons].gpio = gpio;
125 gpio_keys[n_buttons].active_low = info->active_low;
126 gpio_keys[n_buttons].desc = info->name;
127 gpio_keys[n_buttons].wakeup = info->wakeup;
128 n_buttons++;
129 }
130
131 if (n_buttons == 0) {
132 error = -ENODEV;
133 goto err_free_mem;
134 }
135
136 gpio_keys_pdata->buttons = gpio_keys;
137 gpio_keys_pdata->nbuttons = n_buttons;
138 gpio_keys_pdata->rep = autorepeat;
139
140 pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
141 if (!pd) {
142 error = -ENOMEM;
143 goto err_free_mem;
144 }
145
146 error = platform_device_add_data(pd, gpio_keys_pdata,
147 sizeof(*gpio_keys_pdata));
148 if (error)
149 goto err_free_pdev;
150
151 error = platform_device_add(pd);
152 if (error)
153 goto err_free_pdev;
154
155 return pd;
156
157err_free_pdev:
158 platform_device_put(pd);
159err_free_mem:
160 devm_kfree(&client->dev, gpio_keys_pdata);
161 return ERR_PTR(error);
162}
163
164static int surface3_button_remove(struct i2c_client *client)
165{
166 struct surface3_button_data *priv = i2c_get_clientdata(client);
167
168 int i;
169
170 for (i = 0; i < BUTTON_TYPES; i++)
171 if (priv->children[i])
172 platform_device_unregister(priv->children[i]);
173
174 return 0;
175}
176
177static struct surface3_button_info surface3_button_surface3[] = {
178 { "power", 0, EV_KEY, KEY_POWER, false, true, true },
179 { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
180 { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
181 { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
182 { }
183};
184
185static int surface3_button_probe(struct i2c_client *client,
186 const struct i2c_device_id *id)
187{
188 struct device *dev = &client->dev;
189 struct surface3_button_data *priv;
190 struct platform_device *pd;
191 int i;
192 int error;
193
194 if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)),
195 SURFACE_BUTTON_OBJ_NAME,
196 strlen(SURFACE_BUTTON_OBJ_NAME)))
197 return -ENODEV;
198
199 if (gpiod_count(dev, KBUILD_MODNAME) <= 0) {
200 dev_dbg(dev, "no GPIO attached, ignoring...\n");
201 return -ENODEV;
202 }
203
204 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
205 if (!priv)
206 return -ENOMEM;
207
208 i2c_set_clientdata(client, priv);
209
210 for (i = 0; i < BUTTON_TYPES; i++) {
211 pd = surface3_button_device_create(client,
212 surface3_button_surface3,
213 i == 0);
214 if (IS_ERR(pd)) {
215 error = PTR_ERR(pd);
216 if (error != -ENODEV) {
217 surface3_button_remove(client);
218 return error;
219 }
220 continue;
221 }
222
223 priv->children[i] = pd;
224 }
225
226 if (!priv->children[0] && !priv->children[1])
227 return -ENODEV;
228
229 return 0;
230}
231
232static const struct i2c_device_id surface3_id[] = {
233 { }
234};
235MODULE_DEVICE_TABLE(i2c, surface3_id);
236
237static struct i2c_driver surface3_driver = {
238 .probe = surface3_button_probe,
239 .remove = surface3_button_remove,
240 .id_table = surface3_id,
241 .driver = {
242 .name = "surface3",
243 .acpi_match_table = ACPI_PTR(surface3_acpi_match),
244 },
245};
246module_i2c_driver(surface3_driver);
247
248MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
249MODULE_DESCRIPTION("surface3 button array driver");
250MODULE_LICENSE("GPL v2");