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 | |
| 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>
| -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"); | ||
