aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLejun Zhu <lejun.zhu@linux.intel.com>2014-03-31 02:12:00 -0400
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2014-03-31 02:40:56 -0400
commit61cd4822dd810e1a3c28eab1af6005728906c0e4 (patch)
tree5132231d86a3352214c698689a15be57c5eb6bf8
parent877e1f1529a5c4fcc8460c1317c753ff8a6874c5 (diff)
Input: add driver for SOC button array
This patch adds support for the GPIO buttons on some Intel Bay Trail tablets originally running Windows 8. The ACPI description of these buttons follows "Windows ACPI Design Guide for SoC Platforms". Signed-off-by: Lejun Zhu <lejun.zhu@linux.intel.com> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
-rw-r--r--drivers/input/misc/Kconfig10
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/soc_button_array.c218
3 files changed, 229 insertions, 0 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7674f05d1fb3..f772981bdcdb 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -666,4 +666,14 @@ config INPUT_IDEAPAD_SLIDEBAR
666 To compile this driver as a module, choose M here: the 666 To compile this driver as a module, choose M here: the
667 module will be called ideapad_slidebar. 667 module will be called ideapad_slidebar.
668 668
669config INPUT_SOC_BUTTON_ARRAY
670 tristate "Windows-compatible SoC Button Array"
671 depends on KEYBOARD_GPIO
672 help
673 Say Y here if you have a SoC-based tablet that originally
674 runs Windows 8.
675
676 To compile this driver as a module, choose M here: the
677 module will be called soc_button_array.
678
669endif 679endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index cda71fc52fb3..4955ad322a01 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
53obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o 53obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
54obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o 54obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
55obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o 55obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
56obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
56obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o 57obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
57obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o 58obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
58obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o 59obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c
new file mode 100644
index 000000000000..08ead2aaede5
--- /dev/null
+++ b/drivers/input/misc/soc_button_array.c
@@ -0,0 +1,218 @@
1/*
2 * Supports for the button array on SoC tablets originally running
3 * Windows 8.
4 *
5 * (C) Copyright 2014 Intel Corporation
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; version 2
10 * of the License.
11 */
12
13#include <linux/module.h>
14#include <linux/input.h>
15#include <linux/init.h>
16#include <linux/kernel.h>
17#include <linux/acpi.h>
18#include <linux/gpio/consumer.h>
19#include <linux/gpio_keys.h>
20#include <linux/input.h>
21#include <linux/platform_device.h>
22#include <linux/pnp.h>
23
24/*
25 * Definition of buttons on the tablet. The ACPI index of each button
26 * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
27 * Platforms"
28 */
29#define MAX_NBUTTONS 5
30
31struct soc_button_info {
32 const char *name;
33 int acpi_index;
34 unsigned int event_type;
35 unsigned int event_code;
36 bool autorepeat;
37 bool wakeup;
38};
39
40/*
41 * Some of the buttons like volume up/down are auto repeat, while others
42 * are not. To support both, we register two platform devices, and put
43 * buttons into them based on whether the key should be auto repeat.
44 */
45#define BUTTON_TYPES 2
46
47struct soc_button_data {
48 struct platform_device *children[BUTTON_TYPES];
49};
50
51/*
52 * Get the Nth GPIO number from the ACPI object.
53 */
54static int soc_button_lookup_gpio(struct device *dev, int acpi_index)
55{
56 struct gpio_desc *desc;
57 int gpio;
58
59 desc = gpiod_get_index(dev, KBUILD_MODNAME, acpi_index);
60 if (IS_ERR(desc))
61 return PTR_ERR(desc);
62
63 gpio = desc_to_gpio(desc);
64
65 gpiod_put(desc);
66
67 return gpio;
68}
69
70static struct platform_device *
71soc_button_device_create(struct pnp_dev *pdev,
72 const struct soc_button_info *button_info,
73 bool autorepeat)
74{
75 const struct soc_button_info *info;
76 struct platform_device *pd;
77 struct gpio_keys_button *gpio_keys;
78 struct gpio_keys_platform_data *gpio_keys_pdata;
79 int n_buttons = 0;
80 int gpio;
81 int error;
82
83 gpio_keys_pdata = devm_kzalloc(&pdev->dev,
84 sizeof(*gpio_keys_pdata) +
85 sizeof(*gpio_keys) * MAX_NBUTTONS,
86 GFP_KERNEL);
87 gpio_keys = (void *)(gpio_keys_pdata + 1);
88
89 for (info = button_info; info->name; info++) {
90 if (info->autorepeat != autorepeat)
91 continue;
92
93 gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index);
94 if (gpio < 0)
95 continue;
96
97 gpio_keys[n_buttons].type = info->event_type;
98 gpio_keys[n_buttons].code = info->event_code;
99 gpio_keys[n_buttons].gpio = gpio;
100 gpio_keys[n_buttons].active_low = 1;
101 gpio_keys[n_buttons].desc = info->name;
102 gpio_keys[n_buttons].wakeup = info->wakeup;
103 n_buttons++;
104 }
105
106 if (n_buttons == 0) {
107 error = -ENODEV;
108 goto err_free_mem;
109 }
110
111 gpio_keys_pdata->buttons = gpio_keys;
112 gpio_keys_pdata->nbuttons = n_buttons;
113 gpio_keys_pdata->rep = autorepeat;
114
115 pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
116 if (!pd) {
117 error = -ENOMEM;
118 goto err_free_mem;
119 }
120
121 error = platform_device_add_data(pd, gpio_keys_pdata,
122 sizeof(*gpio_keys_pdata));
123 if (error)
124 goto err_free_pdev;
125
126 error = platform_device_add(pd);
127 if (error)
128 goto err_free_pdev;
129
130 return pd;
131
132err_free_pdev:
133 platform_device_put(pd);
134err_free_mem:
135 devm_kfree(&pdev->dev, gpio_keys_pdata);
136 return ERR_PTR(error);
137}
138
139static void soc_button_remove(struct pnp_dev *pdev)
140{
141 struct soc_button_data *priv = pnp_get_drvdata(pdev);
142 int i;
143
144 for (i = 0; i < BUTTON_TYPES; i++)
145 if (priv->children[i])
146 platform_device_unregister(priv->children[i]);
147}
148
149static int soc_button_pnp_probe(struct pnp_dev *pdev,
150 const struct pnp_device_id *id)
151{
152 const struct soc_button_info *button_info = (void *)id->driver_data;
153 struct soc_button_data *priv;
154 struct platform_device *pd;
155 int i;
156 int error;
157
158 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
159 if (!priv)
160 return -ENOMEM;
161
162 pnp_set_drvdata(pdev, priv);
163
164 for (i = 0; i < BUTTON_TYPES; i++) {
165 pd = soc_button_device_create(pdev, button_info, i == 0);
166 if (IS_ERR(pd)) {
167 error = PTR_ERR(pd);
168 if (error != -ENODEV) {
169 soc_button_remove(pdev);
170 return error;
171 }
172 }
173
174 priv->children[i] = pd;
175 }
176
177 if (!priv->children[0] && !priv->children[1])
178 return -ENODEV;
179
180 return 0;
181}
182
183static struct soc_button_info soc_button_PNP0C40[] = {
184 { "power", 0, EV_KEY, KEY_POWER, false, true },
185 { "home", 1, EV_KEY, KEY_HOME, false, true },
186 { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false },
187 { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false },
188 { "rotation_lock", 4, EV_SW, SW_ROTATE_LOCK, false, false },
189 { }
190};
191
192static const struct pnp_device_id soc_button_pnp_match[] = {
193 { .id = "PNP0C40", .driver_data = (long)soc_button_PNP0C40 },
194 { .id = "" }
195};
196MODULE_DEVICE_TABLE(pnp, soc_button_pnp_match);
197
198static struct pnp_driver soc_button_pnp_driver = {
199 .name = KBUILD_MODNAME,
200 .id_table = soc_button_pnp_match,
201 .probe = soc_button_pnp_probe,
202 .remove = soc_button_remove,
203};
204
205static int __init soc_button_init(void)
206{
207 return pnp_register_driver(&soc_button_pnp_driver);
208}
209
210static void __exit soc_button_exit(void)
211{
212 pnp_unregister_driver(&soc_button_pnp_driver);
213}
214
215module_init(soc_button_init);
216module_exit(soc_button_exit);
217
218MODULE_LICENSE("GPL");