diff options
author | Kim Kyuwon <q1.kim@samsung.com> | 2009-09-22 01:17:04 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2009-09-22 01:26:40 -0400 |
commit | 0baf81ba157cb2b89448f0b73fcd9a4f191be8c6 (patch) | |
tree | ae3fe539cbd1543d6ed73b1b726912e770cfbf1a /drivers/input/keyboard | |
parent | 88751dd6ce1fb0627c36c4ab08a40730e5a50d3e (diff) |
Input: add driver for Maxim MAX7359 key switch controller
The Maxim MAX7359 is a I2C interfaced key switch controller which
provides microprocessors with management of up to 64 key switches.
This patch adds support for the MAX7359 key switch controller.
Signed-off-by: Kim Kyuwon <q1.kim@samsung.com>
Reviewed-by: Trilok Soni <soni.trilok@gmail.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input/keyboard')
-rw-r--r-- | drivers/input/keyboard/Kconfig | 11 | ||||
-rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/keyboard/max7359_keypad.c | 352 |
3 files changed, 364 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index d615c09a83c6..57055bcbd7a6 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig | |||
@@ -261,6 +261,17 @@ config KEYBOARD_MAPLE | |||
261 | To compile this driver as a module, choose M here: the | 261 | To compile this driver as a module, choose M here: the |
262 | module will be called maple_keyb. | 262 | module will be called maple_keyb. |
263 | 263 | ||
264 | config KEYBOARD_MAX7359 | ||
265 | tristate "Maxim MAX7359 Key Switch Controller" | ||
266 | depends on I2C | ||
267 | help | ||
268 | If you say yes here you get support for the Maxim MAX7359 Key | ||
269 | Switch Controller chip. This providers microprocessors with | ||
270 | management of up to 64 key switches | ||
271 | |||
272 | To compile this driver as a module, choose M here: the | ||
273 | module will be called max7359_keypad. | ||
274 | |||
264 | config KEYBOARD_NEWTON | 275 | config KEYBOARD_NEWTON |
265 | tristate "Newton keyboard" | 276 | tristate "Newton keyboard" |
266 | select SERIO | 277 | select SERIO |
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index a5c08cdf8083..85ab894f613a 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile | |||
@@ -22,6 +22,7 @@ obj-$(CONFIG_KEYBOARD_LM8323) += lm8323.o | |||
22 | obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o | 22 | obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o |
23 | obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o | 23 | obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o |
24 | obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o | 24 | obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o |
25 | obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o | ||
25 | obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o | 26 | obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o |
26 | obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o | 27 | obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o |
27 | obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o | 28 | obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o |
diff --git a/drivers/input/keyboard/max7359_keypad.c b/drivers/input/keyboard/max7359_keypad.c new file mode 100644 index 000000000000..8b3ee142a6c6 --- /dev/null +++ b/drivers/input/keyboard/max7359_keypad.c | |||
@@ -0,0 +1,352 @@ | |||
1 | /* | ||
2 | * max7359_keypad.c - MAX7359 Key Switch Controller Driver | ||
3 | * | ||
4 | * Copyright (C) 2009 Samsung Electronics | ||
5 | * Kim Kyuwon <q1.kim@samsung.com> | ||
6 | * | ||
7 | * Based on pxa27x_keypad.c | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | * | ||
13 | * Datasheet: http://www.maxim-ic.com/quick_view2.cfm/qv_pk/5456 | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <linux/interrupt.h> | ||
19 | #include <linux/input.h> | ||
20 | #include <linux/input/matrix_keypad.h> | ||
21 | |||
22 | #define MAX7359_MAX_KEY_ROWS 8 | ||
23 | #define MAX7359_MAX_KEY_COLS 8 | ||
24 | #define MAX7359_MAX_KEY_NUM (MAX7359_MAX_KEY_ROWS * MAX7359_MAX_KEY_COLS) | ||
25 | #define MAX7359_ROW_SHIFT 3 | ||
26 | |||
27 | /* | ||
28 | * MAX7359 registers | ||
29 | */ | ||
30 | #define MAX7359_REG_KEYFIFO 0x00 | ||
31 | #define MAX7359_REG_CONFIG 0x01 | ||
32 | #define MAX7359_REG_DEBOUNCE 0x02 | ||
33 | #define MAX7359_REG_INTERRUPT 0x03 | ||
34 | #define MAX7359_REG_PORTS 0x04 | ||
35 | #define MAX7359_REG_KEYREP 0x05 | ||
36 | #define MAX7359_REG_SLEEP 0x06 | ||
37 | |||
38 | /* | ||
39 | * Configuration register bits | ||
40 | */ | ||
41 | #define MAX7359_CFG_SLEEP (1 << 7) | ||
42 | #define MAX7359_CFG_INTERRUPT (1 << 5) | ||
43 | #define MAX7359_CFG_KEY_RELEASE (1 << 3) | ||
44 | #define MAX7359_CFG_WAKEUP (1 << 1) | ||
45 | #define MAX7359_CFG_TIMEOUT (1 << 0) | ||
46 | |||
47 | /* | ||
48 | * Autosleep register values (ms) | ||
49 | */ | ||
50 | #define MAX7359_AUTOSLEEP_8192 0x01 | ||
51 | #define MAX7359_AUTOSLEEP_4096 0x02 | ||
52 | #define MAX7359_AUTOSLEEP_2048 0x03 | ||
53 | #define MAX7359_AUTOSLEEP_1024 0x04 | ||
54 | #define MAX7359_AUTOSLEEP_512 0x05 | ||
55 | #define MAX7359_AUTOSLEEP_256 0x06 | ||
56 | |||
57 | struct max7359_keypad { | ||
58 | /* matrix key code map */ | ||
59 | unsigned short keycodes[MAX7359_MAX_KEY_NUM]; | ||
60 | |||
61 | struct work_struct work; | ||
62 | |||
63 | struct input_dev *input_dev; | ||
64 | struct i2c_client *client; | ||
65 | |||
66 | u32 irq; | ||
67 | }; | ||
68 | |||
69 | static int max7359_write_reg(struct i2c_client *client, u8 reg, u8 val) | ||
70 | { | ||
71 | int ret = i2c_smbus_write_byte_data(client, reg, val); | ||
72 | |||
73 | if (ret < 0) | ||
74 | dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", | ||
75 | __func__, reg, val, ret); | ||
76 | return ret; | ||
77 | } | ||
78 | |||
79 | static int max7359_read_reg(struct i2c_client *client, int reg) | ||
80 | { | ||
81 | int ret = i2c_smbus_read_byte_data(client, reg); | ||
82 | |||
83 | if (ret < 0) | ||
84 | dev_err(&client->dev, "%s: reg 0x%x, err %d\n", | ||
85 | __func__, reg, ret); | ||
86 | return ret; | ||
87 | } | ||
88 | |||
89 | static void max7359_build_keycode(struct max7359_keypad *keypad, | ||
90 | const struct matrix_keymap_data *keymap_data) | ||
91 | { | ||
92 | struct input_dev *input_dev = keypad->input_dev; | ||
93 | int i; | ||
94 | |||
95 | for (i = 0; i < keymap_data->keymap_size; i++) { | ||
96 | unsigned int key = keymap_data->keymap[i]; | ||
97 | unsigned int row = KEY_ROW(key); | ||
98 | unsigned int col = KEY_COL(key); | ||
99 | unsigned int scancode = MATRIX_SCAN_CODE(row, col, | ||
100 | MAX7359_ROW_SHIFT); | ||
101 | unsigned short keycode = KEY_VAL(key); | ||
102 | |||
103 | keypad->keycodes[scancode] = keycode; | ||
104 | __set_bit(keycode, input_dev->keybit); | ||
105 | } | ||
106 | __clear_bit(KEY_RESERVED, input_dev->keybit); | ||
107 | } | ||
108 | |||
109 | static void max7359_worker(struct work_struct *work) | ||
110 | { | ||
111 | struct max7359_keypad *keypad = | ||
112 | container_of(work, struct max7359_keypad, work); | ||
113 | struct input_dev *input_dev = keypad->input_dev; | ||
114 | int val, row, col, release, code; | ||
115 | |||
116 | val = max7359_read_reg(keypad->client, MAX7359_REG_KEYFIFO); | ||
117 | row = val & 0x7; | ||
118 | col = (val >> 3) & 0x7; | ||
119 | release = val & 0x40; | ||
120 | |||
121 | code = MATRIX_SCAN_CODE(row, col, MAX7359_ROW_SHIFT); | ||
122 | |||
123 | input_event(input_dev, EV_MSC, MSC_SCAN, code); | ||
124 | input_report_key(input_dev, keypad->keycodes[code], !release); | ||
125 | input_sync(input_dev); | ||
126 | |||
127 | enable_irq(keypad->irq); | ||
128 | |||
129 | dev_dbg(&keypad->client->dev, "key[%d:%d] %s\n", row, col, | ||
130 | (release ? "release" : "press")); | ||
131 | } | ||
132 | |||
133 | static irqreturn_t max7359_interrupt(int irq, void *dev_id) | ||
134 | { | ||
135 | struct max7359_keypad *keypad = dev_id; | ||
136 | |||
137 | if (!work_pending(&keypad->work)) { | ||
138 | disable_irq_nosync(keypad->irq); | ||
139 | schedule_work(&keypad->work); | ||
140 | } | ||
141 | |||
142 | return IRQ_HANDLED; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * Let MAX7359 fall into a deep sleep: | ||
147 | * If no keys are pressed, enter sleep mode for 8192 ms. And if any | ||
148 | * key is pressed, the MAX7359 returns to normal operating mode. | ||
149 | */ | ||
150 | static inline void max7359_fall_deepsleep(struct i2c_client *client) | ||
151 | { | ||
152 | max7359_write_reg(client, MAX7359_REG_SLEEP, MAX7359_AUTOSLEEP_8192); | ||
153 | } | ||
154 | |||
155 | /* | ||
156 | * Let MAX7359 take a catnap: | ||
157 | * Autosleep just for 256 ms. | ||
158 | */ | ||
159 | static inline void max7359_take_catnap(struct i2c_client *client) | ||
160 | { | ||
161 | max7359_write_reg(client, MAX7359_REG_SLEEP, MAX7359_AUTOSLEEP_256); | ||
162 | } | ||
163 | |||
164 | static int max7359_open(struct input_dev *dev) | ||
165 | { | ||
166 | struct max7359_keypad *keypad = input_get_drvdata(dev); | ||
167 | |||
168 | max7359_take_catnap(keypad->client); | ||
169 | |||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static void max7359_close(struct input_dev *dev) | ||
174 | { | ||
175 | struct max7359_keypad *keypad = input_get_drvdata(dev); | ||
176 | |||
177 | max7359_fall_deepsleep(keypad->client); | ||
178 | } | ||
179 | |||
180 | static void max7359_initialize(struct i2c_client *client) | ||
181 | { | ||
182 | max7359_write_reg(client, MAX7359_REG_CONFIG, | ||
183 | MAX7359_CFG_INTERRUPT | /* Irq clears after host read */ | ||
184 | MAX7359_CFG_KEY_RELEASE | /* Key release enable */ | ||
185 | MAX7359_CFG_WAKEUP); /* Key press wakeup enable */ | ||
186 | |||
187 | /* Full key-scan functionality */ | ||
188 | max7359_write_reg(client, MAX7359_REG_DEBOUNCE, 0x1F); | ||
189 | |||
190 | /* nINT asserts every debounce cycles */ | ||
191 | max7359_write_reg(client, MAX7359_REG_INTERRUPT, 0x01); | ||
192 | |||
193 | max7359_fall_deepsleep(client); | ||
194 | } | ||
195 | |||
196 | static int __devinit max7359_probe(struct i2c_client *client, | ||
197 | const struct i2c_device_id *id) | ||
198 | { | ||
199 | const struct matrix_keymap_data *keymap_data = client->dev.platform_data; | ||
200 | struct max7359_keypad *keypad; | ||
201 | struct input_dev *input_dev; | ||
202 | int ret; | ||
203 | int error; | ||
204 | |||
205 | if (!client->irq) { | ||
206 | dev_err(&client->dev, "The irq number should not be zero\n"); | ||
207 | return -EINVAL; | ||
208 | } | ||
209 | |||
210 | /* Detect MAX7359: The initial Keys FIFO value is '0x3F' */ | ||
211 | ret = max7359_read_reg(client, MAX7359_REG_KEYFIFO); | ||
212 | if (ret < 0) { | ||
213 | dev_err(&client->dev, "failed to detect device\n"); | ||
214 | return -ENODEV; | ||
215 | } | ||
216 | |||
217 | dev_dbg(&client->dev, "keys FIFO is 0x%02x\n", ret); | ||
218 | |||
219 | keypad = kzalloc(sizeof(struct max7359_keypad), GFP_KERNEL); | ||
220 | input_dev = input_allocate_device(); | ||
221 | if (!keypad || !input_dev) { | ||
222 | dev_err(&client->dev, "failed to allocate memory\n"); | ||
223 | error = -ENOMEM; | ||
224 | goto failed_free_mem; | ||
225 | } | ||
226 | |||
227 | keypad->client = client; | ||
228 | keypad->input_dev = input_dev; | ||
229 | keypad->irq = client->irq; | ||
230 | INIT_WORK(&keypad->work, max7359_worker); | ||
231 | |||
232 | input_dev->name = client->name; | ||
233 | input_dev->id.bustype = BUS_I2C; | ||
234 | input_dev->open = max7359_open; | ||
235 | input_dev->close = max7359_close; | ||
236 | input_dev->dev.parent = &client->dev; | ||
237 | |||
238 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); | ||
239 | input_dev->keycodesize = sizeof(keypad->keycodes[0]); | ||
240 | input_dev->keycodemax = ARRAY_SIZE(keypad->keycodes); | ||
241 | input_dev->keycode = keypad->keycodes; | ||
242 | |||
243 | input_set_capability(input_dev, EV_MSC, MSC_SCAN); | ||
244 | input_set_drvdata(input_dev, keypad); | ||
245 | |||
246 | max7359_build_keycode(keypad, keymap_data); | ||
247 | |||
248 | error = request_irq(keypad->irq, max7359_interrupt, | ||
249 | IRQF_TRIGGER_LOW, client->name, keypad); | ||
250 | if (error) { | ||
251 | dev_err(&client->dev, "failed to register interrupt\n"); | ||
252 | goto failed_free_mem; | ||
253 | } | ||
254 | |||
255 | /* Register the input device */ | ||
256 | error = input_register_device(input_dev); | ||
257 | if (error) { | ||
258 | dev_err(&client->dev, "failed to register input device\n"); | ||
259 | goto failed_free_irq; | ||
260 | } | ||
261 | |||
262 | /* Initialize MAX7359 */ | ||
263 | max7359_initialize(client); | ||
264 | |||
265 | i2c_set_clientdata(client, keypad); | ||
266 | device_init_wakeup(&client->dev, 1); | ||
267 | |||
268 | return 0; | ||
269 | |||
270 | failed_free_irq: | ||
271 | free_irq(keypad->irq, keypad); | ||
272 | failed_free_mem: | ||
273 | input_free_device(input_dev); | ||
274 | kfree(keypad); | ||
275 | return error; | ||
276 | } | ||
277 | |||
278 | static int __devexit max7359_remove(struct i2c_client *client) | ||
279 | { | ||
280 | struct max7359_keypad *keypad = i2c_get_clientdata(client); | ||
281 | |||
282 | cancel_work_sync(&keypad->work); | ||
283 | input_unregister_device(keypad->input_dev); | ||
284 | free_irq(keypad->irq, keypad); | ||
285 | i2c_set_clientdata(client, NULL); | ||
286 | kfree(keypad); | ||
287 | |||
288 | return 0; | ||
289 | } | ||
290 | |||
291 | #ifdef CONFIG_PM | ||
292 | static int max7359_suspend(struct i2c_client *client, pm_message_t mesg) | ||
293 | { | ||
294 | struct max7359_keypad *keypad = i2c_get_clientdata(client); | ||
295 | |||
296 | max7359_fall_deepsleep(client); | ||
297 | |||
298 | if (device_may_wakeup(&client->dev)) | ||
299 | enable_irq_wake(keypad->irq); | ||
300 | |||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | static int max7359_resume(struct i2c_client *client) | ||
305 | { | ||
306 | struct max7359_keypad *keypad = i2c_get_clientdata(client); | ||
307 | |||
308 | if (device_may_wakeup(&client->dev)) | ||
309 | disable_irq_wake(keypad->irq); | ||
310 | |||
311 | /* Restore the default setting */ | ||
312 | max7359_take_catnap(client); | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | #else | ||
317 | #define max7359_suspend NULL | ||
318 | #define max7359_resume NULL | ||
319 | #endif | ||
320 | |||
321 | static const struct i2c_device_id max7359_ids[] = { | ||
322 | { "max7359", 0 }, | ||
323 | { } | ||
324 | }; | ||
325 | MODULE_DEVICE_TABLE(i2c, max7359_ids); | ||
326 | |||
327 | static struct i2c_driver max7359_i2c_driver = { | ||
328 | .driver = { | ||
329 | .name = "max7359", | ||
330 | }, | ||
331 | .probe = max7359_probe, | ||
332 | .remove = __devexit_p(max7359_remove), | ||
333 | .suspend = max7359_suspend, | ||
334 | .resume = max7359_resume, | ||
335 | .id_table = max7359_ids, | ||
336 | }; | ||
337 | |||
338 | static int __init max7359_init(void) | ||
339 | { | ||
340 | return i2c_add_driver(&max7359_i2c_driver); | ||
341 | } | ||
342 | module_init(max7359_init); | ||
343 | |||
344 | static void __exit max7359_exit(void) | ||
345 | { | ||
346 | i2c_del_driver(&max7359_i2c_driver); | ||
347 | } | ||
348 | module_exit(max7359_exit); | ||
349 | |||
350 | MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>"); | ||
351 | MODULE_DESCRIPTION("MAX7359 Key Switch Controller Driver"); | ||
352 | MODULE_LICENSE("GPL v2"); | ||