diff options
author | Michael Hennerich <michael.hennerich@analog.com> | 2009-09-18 01:39:38 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2009-09-18 02:24:04 -0400 |
commit | 88751dd6ce1fb0627c36c4ab08a40730e5a50d3e (patch) | |
tree | 9a3c7f4b6c8d321999bb66732953ceb300f1a3c4 /drivers | |
parent | 38e783b38148531c0840ac130b97eb8158f84b48 (diff) |
Input: add driver for ADP5588 QWERTY I2C Keypad
Signed-off-by: Michael Hennerich <michael.hennerich@analog.com>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 10 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/keyboard/adp5588-keys.c | 361 |
3 files changed, 372 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b14bd3a07140..d615c09a83c6 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig | |||
@@ -24,6 +24,16 @@ config KEYBOARD_AAED2000 | |||
24 | To compile this driver as a module, choose M here: the | 24 | To compile this driver as a module, choose M here: the |
25 | module will be called aaed2000_kbd. | 25 | module will be called aaed2000_kbd. |
26 | 26 | ||
27 | config KEYBOARD_ADP5588 | ||
28 | tristate "ADP5588 I2C QWERTY Keypad and IO Expander" | ||
29 | depends on I2C | ||
30 | help | ||
31 | Say Y here if you want to use a ADP5588 attached to your | ||
32 | system I2C bus. | ||
33 | |||
34 | To compile this driver as a module, choose M here: the | ||
35 | module will be called adp5588-keys. | ||
36 | |||
27 | config KEYBOARD_AMIGA | 37 | config KEYBOARD_AMIGA |
28 | tristate "Amiga keyboard" | 38 | tristate "Amiga keyboard" |
29 | depends on AMIGA | 39 | depends on AMIGA |
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index ab35ac36111e..a5c08cdf8083 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile | |||
@@ -5,6 +5,7 @@ | |||
5 | # Each configuration option enables a list of files. | 5 | # Each configuration option enables a list of files. |
6 | 6 | ||
7 | obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o | 7 | obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o |
8 | obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o | ||
8 | obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o | 9 | obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o |
9 | obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o | 10 | obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o |
10 | obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o | 11 | obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o |
diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c new file mode 100644 index 000000000000..d48c808d5928 --- /dev/null +++ b/drivers/input/keyboard/adp5588-keys.c | |||
@@ -0,0 +1,361 @@ | |||
1 | /* | ||
2 | * File: drivers/input/keyboard/adp5588_keys.c | ||
3 | * Description: keypad driver for ADP5588 I2C QWERTY Keypad and IO Expander | ||
4 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
5 | * | ||
6 | * Copyright (C) 2008-2009 Analog Devices Inc. | ||
7 | * Licensed under the GPL-2 or later. | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/version.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <linux/irq.h> | ||
15 | #include <linux/workqueue.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/pm.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/input.h> | ||
20 | #include <linux/i2c.h> | ||
21 | |||
22 | #include <linux/i2c/adp5588.h> | ||
23 | |||
24 | /* Configuration Register1 */ | ||
25 | #define AUTO_INC (1 << 7) | ||
26 | #define GPIEM_CFG (1 << 6) | ||
27 | #define OVR_FLOW_M (1 << 5) | ||
28 | #define INT_CFG (1 << 4) | ||
29 | #define OVR_FLOW_IEN (1 << 3) | ||
30 | #define K_LCK_IM (1 << 2) | ||
31 | #define GPI_IEN (1 << 1) | ||
32 | #define KE_IEN (1 << 0) | ||
33 | |||
34 | /* Interrupt Status Register */ | ||
35 | #define CMP2_INT (1 << 5) | ||
36 | #define CMP1_INT (1 << 4) | ||
37 | #define OVR_FLOW_INT (1 << 3) | ||
38 | #define K_LCK_INT (1 << 2) | ||
39 | #define GPI_INT (1 << 1) | ||
40 | #define KE_INT (1 << 0) | ||
41 | |||
42 | /* Key Lock and Event Counter Register */ | ||
43 | #define K_LCK_EN (1 << 6) | ||
44 | #define LCK21 0x30 | ||
45 | #define KEC 0xF | ||
46 | |||
47 | /* Key Event Register xy */ | ||
48 | #define KEY_EV_PRESSED (1 << 7) | ||
49 | #define KEY_EV_MASK (0x7F) | ||
50 | |||
51 | #define KP_SEL(x) (0xFFFF >> (16 - x)) /* 2^x-1 */ | ||
52 | |||
53 | #define KEYP_MAX_EVENT 10 | ||
54 | |||
55 | /* | ||
56 | * Early pre 4.0 Silicon required to delay readout by at least 25ms, | ||
57 | * since the Event Counter Register updated 25ms after the interrupt | ||
58 | * asserted. | ||
59 | */ | ||
60 | #define WA_DELAYED_READOUT_REVID(rev) ((rev) < 4) | ||
61 | |||
62 | struct adp5588_kpad { | ||
63 | struct i2c_client *client; | ||
64 | struct input_dev *input; | ||
65 | struct delayed_work work; | ||
66 | unsigned long delay; | ||
67 | unsigned short keycode[ADP5588_KEYMAPSIZE]; | ||
68 | }; | ||
69 | |||
70 | static int adp5588_read(struct i2c_client *client, u8 reg) | ||
71 | { | ||
72 | int ret = i2c_smbus_read_byte_data(client, reg); | ||
73 | |||
74 | if (ret < 0) | ||
75 | dev_err(&client->dev, "Read Error\n"); | ||
76 | |||
77 | return ret; | ||
78 | } | ||
79 | |||
80 | static int adp5588_write(struct i2c_client *client, u8 reg, u8 val) | ||
81 | { | ||
82 | return i2c_smbus_write_byte_data(client, reg, val); | ||
83 | } | ||
84 | |||
85 | static void adp5588_work(struct work_struct *work) | ||
86 | { | ||
87 | struct adp5588_kpad *kpad = container_of(work, | ||
88 | struct adp5588_kpad, work.work); | ||
89 | struct i2c_client *client = kpad->client; | ||
90 | int i, key, status, ev_cnt; | ||
91 | |||
92 | status = adp5588_read(client, INT_STAT); | ||
93 | |||
94 | if (status & OVR_FLOW_INT) /* Unlikely and should never happen */ | ||
95 | dev_err(&client->dev, "Event Overflow Error\n"); | ||
96 | |||
97 | if (status & KE_INT) { | ||
98 | ev_cnt = adp5588_read(client, KEY_LCK_EC_STAT) & KEC; | ||
99 | if (ev_cnt) { | ||
100 | for (i = 0; i < ev_cnt; i++) { | ||
101 | key = adp5588_read(client, Key_EVENTA + i); | ||
102 | input_report_key(kpad->input, | ||
103 | kpad->keycode[(key & KEY_EV_MASK) - 1], | ||
104 | key & KEY_EV_PRESSED); | ||
105 | } | ||
106 | input_sync(kpad->input); | ||
107 | } | ||
108 | } | ||
109 | adp5588_write(client, INT_STAT, status); /* Status is W1C */ | ||
110 | } | ||
111 | |||
112 | static irqreturn_t adp5588_irq(int irq, void *handle) | ||
113 | { | ||
114 | struct adp5588_kpad *kpad = handle; | ||
115 | |||
116 | /* | ||
117 | * use keventd context to read the event fifo registers | ||
118 | * Schedule readout at least 25ms after notification for | ||
119 | * REVID < 4 | ||
120 | */ | ||
121 | |||
122 | schedule_delayed_work(&kpad->work, kpad->delay); | ||
123 | |||
124 | return IRQ_HANDLED; | ||
125 | } | ||
126 | |||
127 | static int __devinit adp5588_setup(struct i2c_client *client) | ||
128 | { | ||
129 | struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; | ||
130 | int i, ret; | ||
131 | |||
132 | ret = adp5588_write(client, KP_GPIO1, KP_SEL(pdata->rows)); | ||
133 | ret |= adp5588_write(client, KP_GPIO2, KP_SEL(pdata->cols) & 0xFF); | ||
134 | ret |= adp5588_write(client, KP_GPIO3, KP_SEL(pdata->cols) >> 8); | ||
135 | |||
136 | if (pdata->en_keylock) { | ||
137 | ret |= adp5588_write(client, UNLOCK1, pdata->unlock_key1); | ||
138 | ret |= adp5588_write(client, UNLOCK2, pdata->unlock_key2); | ||
139 | ret |= adp5588_write(client, KEY_LCK_EC_STAT, K_LCK_EN); | ||
140 | } | ||
141 | |||
142 | for (i = 0; i < KEYP_MAX_EVENT; i++) | ||
143 | ret |= adp5588_read(client, Key_EVENTA); | ||
144 | |||
145 | ret |= adp5588_write(client, INT_STAT, CMP2_INT | CMP1_INT | | ||
146 | OVR_FLOW_INT | K_LCK_INT | | ||
147 | GPI_INT | KE_INT); /* Status is W1C */ | ||
148 | |||
149 | ret |= adp5588_write(client, CFG, INT_CFG | OVR_FLOW_IEN | KE_IEN); | ||
150 | |||
151 | if (ret < 0) { | ||
152 | dev_err(&client->dev, "Write Error\n"); | ||
153 | return ret; | ||
154 | } | ||
155 | |||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static int __devinit adp5588_probe(struct i2c_client *client, | ||
160 | const struct i2c_device_id *id) | ||
161 | { | ||
162 | struct adp5588_kpad *kpad; | ||
163 | struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; | ||
164 | struct input_dev *input; | ||
165 | unsigned int revid; | ||
166 | int ret, i; | ||
167 | int error; | ||
168 | |||
169 | if (!i2c_check_functionality(client->adapter, | ||
170 | I2C_FUNC_SMBUS_BYTE_DATA)) { | ||
171 | dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); | ||
172 | return -EIO; | ||
173 | } | ||
174 | |||
175 | if (!pdata) { | ||
176 | dev_err(&client->dev, "no platform data?\n"); | ||
177 | return -EINVAL; | ||
178 | } | ||
179 | |||
180 | if (!pdata->rows || !pdata->cols || !pdata->keymap) { | ||
181 | dev_err(&client->dev, "no rows, cols or keymap from pdata\n"); | ||
182 | return -EINVAL; | ||
183 | } | ||
184 | |||
185 | if (pdata->keymapsize != ADP5588_KEYMAPSIZE) { | ||
186 | dev_err(&client->dev, "invalid keymapsize\n"); | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | |||
190 | if (!client->irq) { | ||
191 | dev_err(&client->dev, "no IRQ?\n"); | ||
192 | return -EINVAL; | ||
193 | } | ||
194 | |||
195 | kpad = kzalloc(sizeof(*kpad), GFP_KERNEL); | ||
196 | input = input_allocate_device(); | ||
197 | if (!kpad || !input) { | ||
198 | error = -ENOMEM; | ||
199 | goto err_free_mem; | ||
200 | } | ||
201 | |||
202 | kpad->client = client; | ||
203 | kpad->input = input; | ||
204 | INIT_DELAYED_WORK(&kpad->work, adp5588_work); | ||
205 | |||
206 | ret = adp5588_read(client, DEV_ID); | ||
207 | if (ret < 0) { | ||
208 | error = ret; | ||
209 | goto err_free_mem; | ||
210 | } | ||
211 | |||
212 | revid = (u8) ret & ADP5588_DEVICE_ID_MASK; | ||
213 | if (WA_DELAYED_READOUT_REVID(revid)) | ||
214 | kpad->delay = msecs_to_jiffies(30); | ||
215 | |||
216 | input->name = client->name; | ||
217 | input->phys = "adp5588-keys/input0"; | ||
218 | input->dev.parent = &client->dev; | ||
219 | |||
220 | input_set_drvdata(input, kpad); | ||
221 | |||
222 | input->id.bustype = BUS_I2C; | ||
223 | input->id.vendor = 0x0001; | ||
224 | input->id.product = 0x0001; | ||
225 | input->id.version = revid; | ||
226 | |||
227 | input->keycodesize = sizeof(kpad->keycode[0]); | ||
228 | input->keycodemax = pdata->keymapsize; | ||
229 | input->keycode = kpad->keycode; | ||
230 | |||
231 | memcpy(kpad->keycode, pdata->keymap, | ||
232 | pdata->keymapsize * input->keycodesize); | ||
233 | |||
234 | /* setup input device */ | ||
235 | __set_bit(EV_KEY, input->evbit); | ||
236 | |||
237 | if (pdata->repeat) | ||
238 | __set_bit(EV_REP, input->evbit); | ||
239 | |||
240 | for (i = 0; i < input->keycodemax; i++) | ||
241 | __set_bit(kpad->keycode[i] & KEY_MAX, input->keybit); | ||
242 | __clear_bit(KEY_RESERVED, input->keybit); | ||
243 | |||
244 | error = input_register_device(input); | ||
245 | if (error) { | ||
246 | dev_err(&client->dev, "unable to register input device\n"); | ||
247 | goto err_free_mem; | ||
248 | } | ||
249 | |||
250 | error = request_irq(client->irq, adp5588_irq, | ||
251 | IRQF_TRIGGER_FALLING | IRQF_DISABLED, | ||
252 | client->dev.driver->name, kpad); | ||
253 | if (error) { | ||
254 | dev_err(&client->dev, "irq %d busy?\n", client->irq); | ||
255 | goto err_unreg_dev; | ||
256 | } | ||
257 | |||
258 | error = adp5588_setup(client); | ||
259 | if (error) | ||
260 | goto err_free_irq; | ||
261 | |||
262 | device_init_wakeup(&client->dev, 1); | ||
263 | i2c_set_clientdata(client, kpad); | ||
264 | |||
265 | dev_info(&client->dev, "Rev.%d keypad, irq %d\n", revid, client->irq); | ||
266 | return 0; | ||
267 | |||
268 | err_free_irq: | ||
269 | free_irq(client->irq, kpad); | ||
270 | err_unreg_dev: | ||
271 | input_unregister_device(input); | ||
272 | input = NULL; | ||
273 | err_free_mem: | ||
274 | input_free_device(input); | ||
275 | kfree(kpad); | ||
276 | |||
277 | return error; | ||
278 | } | ||
279 | |||
280 | static int __devexit adp5588_remove(struct i2c_client *client) | ||
281 | { | ||
282 | struct adp5588_kpad *kpad = i2c_get_clientdata(client); | ||
283 | |||
284 | adp5588_write(client, CFG, 0); | ||
285 | free_irq(client->irq, kpad); | ||
286 | cancel_delayed_work_sync(&kpad->work); | ||
287 | input_unregister_device(kpad->input); | ||
288 | i2c_set_clientdata(client, NULL); | ||
289 | kfree(kpad); | ||
290 | |||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | #ifdef CONFIG_PM | ||
295 | static int adp5588_suspend(struct device *dev) | ||
296 | { | ||
297 | struct adp5588_kpad *kpad = dev_get_drvdata(dev); | ||
298 | struct i2c_client *client = kpad->client; | ||
299 | |||
300 | disable_irq(client->irq); | ||
301 | cancel_delayed_work_sync(&kpad->work); | ||
302 | |||
303 | if (device_may_wakeup(&client->dev)) | ||
304 | enable_irq_wake(client->irq); | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static int adp5588_resume(struct device *dev) | ||
310 | { | ||
311 | struct adp5588_kpad *kpad = dev_get_drvdata(dev); | ||
312 | struct i2c_client *client = kpad->client; | ||
313 | |||
314 | if (device_may_wakeup(&client->dev)) | ||
315 | disable_irq_wake(client->irq); | ||
316 | |||
317 | enable_irq(client->irq); | ||
318 | |||
319 | return 0; | ||
320 | } | ||
321 | |||
322 | static struct dev_pm_ops adp5588_dev_pm_ops = { | ||
323 | .suspend = adp5588_suspend, | ||
324 | .resume = adp5588_resume, | ||
325 | }; | ||
326 | #endif | ||
327 | |||
328 | static const struct i2c_device_id adp5588_id[] = { | ||
329 | { KBUILD_MODNAME, 0 }, | ||
330 | { } | ||
331 | }; | ||
332 | MODULE_DEVICE_TABLE(i2c, adp5588_id); | ||
333 | |||
334 | static struct i2c_driver adp5588_driver = { | ||
335 | .driver = { | ||
336 | .name = KBUILD_MODNAME, | ||
337 | #ifdef CONFIG_PM | ||
338 | .pm = &adp5588_dev_pm_ops, | ||
339 | #endif | ||
340 | }, | ||
341 | .probe = adp5588_probe, | ||
342 | .remove = __devexit_p(adp5588_remove), | ||
343 | .id_table = adp5588_id, | ||
344 | }; | ||
345 | |||
346 | static int __init adp5588_init(void) | ||
347 | { | ||
348 | return i2c_add_driver(&adp5588_driver); | ||
349 | } | ||
350 | module_init(adp5588_init); | ||
351 | |||
352 | static void __exit adp5588_exit(void) | ||
353 | { | ||
354 | i2c_del_driver(&adp5588_driver); | ||
355 | } | ||
356 | module_exit(adp5588_exit); | ||
357 | |||
358 | MODULE_LICENSE("GPL"); | ||
359 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | ||
360 | MODULE_DESCRIPTION("ADP5588 Keypad driver"); | ||
361 | MODULE_ALIAS("platform:adp5588-keys"); | ||