| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Samsung keypad driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2010 Samsung Electronics Co.Ltd | 
|  | 5 | * Author: Joonyoung Shim <jy0922.shim@samsung.com> | 
|  | 6 | * Author: Donghwa Lee <dh09.lee@samsung.com> | 
|  | 7 | * | 
|  | 8 | * This program is free software; you can redistribute  it and/or modify it | 
|  | 9 | * under  the terms of  the GNU General  Public License as published by the | 
|  | 10 | * Free Software Foundation;  either version 2 of the  License, or (at your | 
|  | 11 | * option) any later version. | 
|  | 12 | */ | 
|  | 13 |  | 
|  | 14 | #include <linux/clk.h> | 
|  | 15 | #include <linux/delay.h> | 
|  | 16 | #include <linux/err.h> | 
|  | 17 | #include <linux/init.h> | 
|  | 18 | #include <linux/input.h> | 
|  | 19 | #include <linux/interrupt.h> | 
|  | 20 | #include <linux/io.h> | 
|  | 21 | #include <linux/module.h> | 
|  | 22 | #include <linux/platform_device.h> | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 23 | #include <linux/pm.h> | 
|  | 24 | #include <linux/pm_runtime.h> | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 25 | #include <linux/slab.h> | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 26 | #include <linux/of.h> | 
|  | 27 | #include <linux/of_gpio.h> | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 28 | #include <linux/sched.h> | 
| Dmitry Torokhov | 8d964a2 | 2011-11-07 23:59:41 -0800 | [diff] [blame] | 29 | #include <linux/input/samsung-keypad.h> | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 30 |  | 
|  | 31 | #define SAMSUNG_KEYIFCON			0x00 | 
|  | 32 | #define SAMSUNG_KEYIFSTSCLR			0x04 | 
|  | 33 | #define SAMSUNG_KEYIFCOL			0x08 | 
|  | 34 | #define SAMSUNG_KEYIFROW			0x0c | 
|  | 35 | #define SAMSUNG_KEYIFFC				0x10 | 
|  | 36 |  | 
|  | 37 | /* SAMSUNG_KEYIFCON */ | 
|  | 38 | #define SAMSUNG_KEYIFCON_INT_F_EN		(1 << 0) | 
|  | 39 | #define SAMSUNG_KEYIFCON_INT_R_EN		(1 << 1) | 
|  | 40 | #define SAMSUNG_KEYIFCON_DF_EN			(1 << 2) | 
|  | 41 | #define SAMSUNG_KEYIFCON_FC_EN			(1 << 3) | 
|  | 42 | #define SAMSUNG_KEYIFCON_WAKEUPEN		(1 << 4) | 
|  | 43 |  | 
|  | 44 | /* SAMSUNG_KEYIFSTSCLR */ | 
|  | 45 | #define SAMSUNG_KEYIFSTSCLR_P_INT_MASK		(0xff << 0) | 
|  | 46 | #define SAMSUNG_KEYIFSTSCLR_R_INT_MASK		(0xff << 8) | 
|  | 47 | #define SAMSUNG_KEYIFSTSCLR_R_INT_OFFSET	8 | 
|  | 48 | #define S5PV210_KEYIFSTSCLR_P_INT_MASK		(0x3fff << 0) | 
|  | 49 | #define S5PV210_KEYIFSTSCLR_R_INT_MASK		(0x3fff << 16) | 
|  | 50 | #define S5PV210_KEYIFSTSCLR_R_INT_OFFSET	16 | 
|  | 51 |  | 
|  | 52 | /* SAMSUNG_KEYIFCOL */ | 
|  | 53 | #define SAMSUNG_KEYIFCOL_MASK			(0xff << 0) | 
|  | 54 | #define S5PV210_KEYIFCOLEN_MASK			(0xff << 8) | 
|  | 55 |  | 
|  | 56 | /* SAMSUNG_KEYIFROW */ | 
|  | 57 | #define SAMSUNG_KEYIFROW_MASK			(0xff << 0) | 
|  | 58 | #define S5PV210_KEYIFROW_MASK			(0x3fff << 0) | 
|  | 59 |  | 
|  | 60 | /* SAMSUNG_KEYIFFC */ | 
|  | 61 | #define SAMSUNG_KEYIFFC_MASK			(0x3ff << 0) | 
|  | 62 |  | 
|  | 63 | enum samsung_keypad_type { | 
|  | 64 | KEYPAD_TYPE_SAMSUNG, | 
|  | 65 | KEYPAD_TYPE_S5PV210, | 
|  | 66 | }; | 
|  | 67 |  | 
|  | 68 | struct samsung_keypad { | 
|  | 69 | struct input_dev *input_dev; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 70 | struct platform_device *pdev; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 71 | struct clk *clk; | 
|  | 72 | void __iomem *base; | 
|  | 73 | wait_queue_head_t wait; | 
|  | 74 | bool stopped; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 75 | bool wake_enabled; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 76 | int irq; | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 77 | enum samsung_keypad_type type; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 78 | unsigned int row_shift; | 
|  | 79 | unsigned int rows; | 
|  | 80 | unsigned int cols; | 
|  | 81 | unsigned int row_state[SAMSUNG_MAX_COLS]; | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 82 | #ifdef CONFIG_OF | 
|  | 83 | int row_gpios[SAMSUNG_MAX_ROWS]; | 
|  | 84 | int col_gpios[SAMSUNG_MAX_COLS]; | 
|  | 85 | #endif | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 86 | unsigned short keycodes[]; | 
|  | 87 | }; | 
|  | 88 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 89 | static void samsung_keypad_scan(struct samsung_keypad *keypad, | 
|  | 90 | unsigned int *row_state) | 
|  | 91 | { | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 92 | unsigned int col; | 
|  | 93 | unsigned int val; | 
|  | 94 |  | 
|  | 95 | for (col = 0; col < keypad->cols; col++) { | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 96 | if (keypad->type == KEYPAD_TYPE_S5PV210) { | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 97 | val = S5PV210_KEYIFCOLEN_MASK; | 
|  | 98 | val &= ~(1 << col) << 8; | 
|  | 99 | } else { | 
|  | 100 | val = SAMSUNG_KEYIFCOL_MASK; | 
|  | 101 | val &= ~(1 << col); | 
|  | 102 | } | 
|  | 103 |  | 
|  | 104 | writel(val, keypad->base + SAMSUNG_KEYIFCOL); | 
|  | 105 | mdelay(1); | 
|  | 106 |  | 
|  | 107 | val = readl(keypad->base + SAMSUNG_KEYIFROW); | 
|  | 108 | row_state[col] = ~val & ((1 << keypad->rows) - 1); | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | /* KEYIFCOL reg clear */ | 
|  | 112 | writel(0, keypad->base + SAMSUNG_KEYIFCOL); | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | static bool samsung_keypad_report(struct samsung_keypad *keypad, | 
|  | 116 | unsigned int *row_state) | 
|  | 117 | { | 
|  | 118 | struct input_dev *input_dev = keypad->input_dev; | 
|  | 119 | unsigned int changed; | 
|  | 120 | unsigned int pressed; | 
|  | 121 | unsigned int key_down = 0; | 
|  | 122 | unsigned int val; | 
|  | 123 | unsigned int col, row; | 
|  | 124 |  | 
|  | 125 | for (col = 0; col < keypad->cols; col++) { | 
|  | 126 | changed = row_state[col] ^ keypad->row_state[col]; | 
|  | 127 | key_down |= row_state[col]; | 
|  | 128 | if (!changed) | 
|  | 129 | continue; | 
|  | 130 |  | 
|  | 131 | for (row = 0; row < keypad->rows; row++) { | 
|  | 132 | if (!(changed & (1 << row))) | 
|  | 133 | continue; | 
|  | 134 |  | 
|  | 135 | pressed = row_state[col] & (1 << row); | 
|  | 136 |  | 
|  | 137 | dev_dbg(&keypad->input_dev->dev, | 
|  | 138 | "key %s, row: %d, col: %d\n", | 
|  | 139 | pressed ? "pressed" : "released", row, col); | 
|  | 140 |  | 
|  | 141 | val = MATRIX_SCAN_CODE(row, col, keypad->row_shift); | 
|  | 142 |  | 
|  | 143 | input_event(input_dev, EV_MSC, MSC_SCAN, val); | 
|  | 144 | input_report_key(input_dev, | 
|  | 145 | keypad->keycodes[val], pressed); | 
|  | 146 | } | 
|  | 147 | input_sync(keypad->input_dev); | 
|  | 148 | } | 
|  | 149 |  | 
|  | 150 | memcpy(keypad->row_state, row_state, sizeof(keypad->row_state)); | 
|  | 151 |  | 
|  | 152 | return key_down; | 
|  | 153 | } | 
|  | 154 |  | 
|  | 155 | static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) | 
|  | 156 | { | 
|  | 157 | struct samsung_keypad *keypad = dev_id; | 
|  | 158 | unsigned int row_state[SAMSUNG_MAX_COLS]; | 
|  | 159 | unsigned int val; | 
|  | 160 | bool key_down; | 
|  | 161 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 162 | pm_runtime_get_sync(&keypad->pdev->dev); | 
|  | 163 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 164 | do { | 
|  | 165 | val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR); | 
|  | 166 | /* Clear interrupt. */ | 
|  | 167 | writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); | 
|  | 168 |  | 
|  | 169 | samsung_keypad_scan(keypad, row_state); | 
|  | 170 |  | 
|  | 171 | key_down = samsung_keypad_report(keypad, row_state); | 
|  | 172 | if (key_down) | 
|  | 173 | wait_event_timeout(keypad->wait, keypad->stopped, | 
|  | 174 | msecs_to_jiffies(50)); | 
|  | 175 |  | 
|  | 176 | } while (key_down && !keypad->stopped); | 
|  | 177 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 178 | pm_runtime_put_sync(&keypad->pdev->dev); | 
|  | 179 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 180 | return IRQ_HANDLED; | 
|  | 181 | } | 
|  | 182 |  | 
|  | 183 | static void samsung_keypad_start(struct samsung_keypad *keypad) | 
|  | 184 | { | 
|  | 185 | unsigned int val; | 
|  | 186 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 187 | pm_runtime_get_sync(&keypad->pdev->dev); | 
|  | 188 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 189 | /* Tell IRQ thread that it may poll the device. */ | 
|  | 190 | keypad->stopped = false; | 
|  | 191 |  | 
|  | 192 | clk_enable(keypad->clk); | 
|  | 193 |  | 
|  | 194 | /* Enable interrupt bits. */ | 
|  | 195 | val = readl(keypad->base + SAMSUNG_KEYIFCON); | 
|  | 196 | val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN; | 
|  | 197 | writel(val, keypad->base + SAMSUNG_KEYIFCON); | 
|  | 198 |  | 
|  | 199 | /* KEYIFCOL reg clear. */ | 
|  | 200 | writel(0, keypad->base + SAMSUNG_KEYIFCOL); | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 201 |  | 
|  | 202 | pm_runtime_put_sync(&keypad->pdev->dev); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 203 | } | 
|  | 204 |  | 
|  | 205 | static void samsung_keypad_stop(struct samsung_keypad *keypad) | 
|  | 206 | { | 
|  | 207 | unsigned int val; | 
|  | 208 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 209 | pm_runtime_get_sync(&keypad->pdev->dev); | 
|  | 210 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 211 | /* Signal IRQ thread to stop polling and disable the handler. */ | 
|  | 212 | keypad->stopped = true; | 
|  | 213 | wake_up(&keypad->wait); | 
|  | 214 | disable_irq(keypad->irq); | 
|  | 215 |  | 
|  | 216 | /* Clear interrupt. */ | 
|  | 217 | writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); | 
|  | 218 |  | 
|  | 219 | /* Disable interrupt bits. */ | 
|  | 220 | val = readl(keypad->base + SAMSUNG_KEYIFCON); | 
|  | 221 | val &= ~(SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN); | 
|  | 222 | writel(val, keypad->base + SAMSUNG_KEYIFCON); | 
|  | 223 |  | 
|  | 224 | clk_disable(keypad->clk); | 
|  | 225 |  | 
|  | 226 | /* | 
|  | 227 | * Now that chip should not generate interrupts we can safely | 
|  | 228 | * re-enable the handler. | 
|  | 229 | */ | 
|  | 230 | enable_irq(keypad->irq); | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 231 |  | 
|  | 232 | pm_runtime_put_sync(&keypad->pdev->dev); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 233 | } | 
|  | 234 |  | 
|  | 235 | static int samsung_keypad_open(struct input_dev *input_dev) | 
|  | 236 | { | 
|  | 237 | struct samsung_keypad *keypad = input_get_drvdata(input_dev); | 
|  | 238 |  | 
|  | 239 | samsung_keypad_start(keypad); | 
|  | 240 |  | 
|  | 241 | return 0; | 
|  | 242 | } | 
|  | 243 |  | 
|  | 244 | static void samsung_keypad_close(struct input_dev *input_dev) | 
|  | 245 | { | 
|  | 246 | struct samsung_keypad *keypad = input_get_drvdata(input_dev); | 
|  | 247 |  | 
|  | 248 | samsung_keypad_stop(keypad); | 
|  | 249 | } | 
|  | 250 |  | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 251 | #ifdef CONFIG_OF | 
|  | 252 | static struct samsung_keypad_platdata *samsung_keypad_parse_dt( | 
|  | 253 | struct device *dev) | 
|  | 254 | { | 
|  | 255 | struct samsung_keypad_platdata *pdata; | 
|  | 256 | struct matrix_keymap_data *keymap_data; | 
|  | 257 | uint32_t *keymap, num_rows = 0, num_cols = 0; | 
|  | 258 | struct device_node *np = dev->of_node, *key_np; | 
|  | 259 | unsigned int key_count = 0; | 
|  | 260 |  | 
|  | 261 | pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); | 
|  | 262 | if (!pdata) { | 
|  | 263 | dev_err(dev, "could not allocate memory for platform data\n"); | 
|  | 264 | return NULL; | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | of_property_read_u32(np, "samsung,keypad-num-rows", &num_rows); | 
|  | 268 | of_property_read_u32(np, "samsung,keypad-num-columns", &num_cols); | 
|  | 269 | if (!num_rows || !num_cols) { | 
|  | 270 | dev_err(dev, "number of keypad rows/columns not specified\n"); | 
|  | 271 | return NULL; | 
|  | 272 | } | 
|  | 273 | pdata->rows = num_rows; | 
|  | 274 | pdata->cols = num_cols; | 
|  | 275 |  | 
|  | 276 | keymap_data = devm_kzalloc(dev, sizeof(*keymap_data), GFP_KERNEL); | 
|  | 277 | if (!keymap_data) { | 
|  | 278 | dev_err(dev, "could not allocate memory for keymap data\n"); | 
|  | 279 | return NULL; | 
|  | 280 | } | 
|  | 281 | pdata->keymap_data = keymap_data; | 
|  | 282 |  | 
|  | 283 | for_each_child_of_node(np, key_np) | 
|  | 284 | key_count++; | 
|  | 285 |  | 
|  | 286 | keymap_data->keymap_size = key_count; | 
|  | 287 | keymap = devm_kzalloc(dev, sizeof(uint32_t) * key_count, GFP_KERNEL); | 
|  | 288 | if (!keymap) { | 
|  | 289 | dev_err(dev, "could not allocate memory for keymap\n"); | 
|  | 290 | return NULL; | 
|  | 291 | } | 
|  | 292 | keymap_data->keymap = keymap; | 
|  | 293 |  | 
|  | 294 | for_each_child_of_node(np, key_np) { | 
|  | 295 | u32 row, col, key_code; | 
|  | 296 | of_property_read_u32(key_np, "keypad,row", &row); | 
|  | 297 | of_property_read_u32(key_np, "keypad,column", &col); | 
|  | 298 | of_property_read_u32(key_np, "linux,code", &key_code); | 
|  | 299 | *keymap++ = KEY(row, col, key_code); | 
|  | 300 | } | 
|  | 301 |  | 
|  | 302 | if (of_get_property(np, "linux,input-no-autorepeat", NULL)) | 
|  | 303 | pdata->no_autorepeat = true; | 
|  | 304 | if (of_get_property(np, "linux,input-wakeup", NULL)) | 
|  | 305 | pdata->wakeup = true; | 
|  | 306 |  | 
|  | 307 | return pdata; | 
|  | 308 | } | 
|  | 309 |  | 
|  | 310 | static void samsung_keypad_parse_dt_gpio(struct device *dev, | 
|  | 311 | struct samsung_keypad *keypad) | 
|  | 312 | { | 
|  | 313 | struct device_node *np = dev->of_node; | 
|  | 314 | int gpio, ret, row, col; | 
|  | 315 |  | 
|  | 316 | for (row = 0; row < keypad->rows; row++) { | 
|  | 317 | gpio = of_get_named_gpio(np, "row-gpios", row); | 
|  | 318 | keypad->row_gpios[row] = gpio; | 
|  | 319 | if (!gpio_is_valid(gpio)) { | 
|  | 320 | dev_err(dev, "keypad row[%d]: invalid gpio %d\n", | 
|  | 321 | row, gpio); | 
|  | 322 | continue; | 
|  | 323 | } | 
|  | 324 |  | 
|  | 325 | ret = gpio_request(gpio, "keypad-row"); | 
|  | 326 | if (ret) | 
|  | 327 | dev_err(dev, "keypad row[%d] gpio request failed\n", | 
|  | 328 | row); | 
|  | 329 | } | 
|  | 330 |  | 
|  | 331 | for (col = 0; col < keypad->cols; col++) { | 
|  | 332 | gpio = of_get_named_gpio(np, "col-gpios", col); | 
|  | 333 | keypad->col_gpios[col] = gpio; | 
|  | 334 | if (!gpio_is_valid(gpio)) { | 
|  | 335 | dev_err(dev, "keypad column[%d]: invalid gpio %d\n", | 
|  | 336 | col, gpio); | 
|  | 337 | continue; | 
|  | 338 | } | 
|  | 339 |  | 
|  | 340 | ret = gpio_request(gpio, "keypad-col"); | 
|  | 341 | if (ret) | 
|  | 342 | dev_err(dev, "keypad column[%d] gpio request failed\n", | 
|  | 343 | col); | 
|  | 344 | } | 
|  | 345 | } | 
|  | 346 |  | 
|  | 347 | static void samsung_keypad_dt_gpio_free(struct samsung_keypad *keypad) | 
|  | 348 | { | 
|  | 349 | int cnt; | 
|  | 350 |  | 
|  | 351 | for (cnt = 0; cnt < keypad->rows; cnt++) | 
|  | 352 | if (gpio_is_valid(keypad->row_gpios[cnt])) | 
|  | 353 | gpio_free(keypad->row_gpios[cnt]); | 
|  | 354 |  | 
|  | 355 | for (cnt = 0; cnt < keypad->cols; cnt++) | 
|  | 356 | if (gpio_is_valid(keypad->col_gpios[cnt])) | 
|  | 357 | gpio_free(keypad->col_gpios[cnt]); | 
|  | 358 | } | 
|  | 359 | #else | 
|  | 360 | static | 
|  | 361 | struct samsung_keypad_platdata *samsung_keypad_parse_dt(struct device *dev) | 
|  | 362 | { | 
|  | 363 | return NULL; | 
|  | 364 | } | 
|  | 365 |  | 
|  | 366 | static void samsung_keypad_dt_gpio_free(struct samsung_keypad *keypad) | 
|  | 367 | { | 
|  | 368 | } | 
|  | 369 | #endif | 
|  | 370 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 371 | static int __devinit samsung_keypad_probe(struct platform_device *pdev) | 
|  | 372 | { | 
|  | 373 | const struct samsung_keypad_platdata *pdata; | 
|  | 374 | const struct matrix_keymap_data *keymap_data; | 
|  | 375 | struct samsung_keypad *keypad; | 
|  | 376 | struct resource *res; | 
|  | 377 | struct input_dev *input_dev; | 
|  | 378 | unsigned int row_shift; | 
|  | 379 | unsigned int keymap_size; | 
|  | 380 | int error; | 
|  | 381 |  | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 382 | if (pdev->dev.of_node) | 
|  | 383 | pdata = samsung_keypad_parse_dt(&pdev->dev); | 
|  | 384 | else | 
|  | 385 | pdata = pdev->dev.platform_data; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 386 | if (!pdata) { | 
|  | 387 | dev_err(&pdev->dev, "no platform data defined\n"); | 
|  | 388 | return -EINVAL; | 
|  | 389 | } | 
|  | 390 |  | 
|  | 391 | keymap_data = pdata->keymap_data; | 
|  | 392 | if (!keymap_data) { | 
|  | 393 | dev_err(&pdev->dev, "no keymap data defined\n"); | 
|  | 394 | return -EINVAL; | 
|  | 395 | } | 
|  | 396 |  | 
|  | 397 | if (!pdata->rows || pdata->rows > SAMSUNG_MAX_ROWS) | 
|  | 398 | return -EINVAL; | 
|  | 399 |  | 
|  | 400 | if (!pdata->cols || pdata->cols > SAMSUNG_MAX_COLS) | 
|  | 401 | return -EINVAL; | 
|  | 402 |  | 
|  | 403 | /* initialize the gpio */ | 
|  | 404 | if (pdata->cfg_gpio) | 
|  | 405 | pdata->cfg_gpio(pdata->rows, pdata->cols); | 
|  | 406 |  | 
|  | 407 | row_shift = get_count_order(pdata->cols); | 
|  | 408 | keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]); | 
|  | 409 |  | 
|  | 410 | keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL); | 
|  | 411 | input_dev = input_allocate_device(); | 
|  | 412 | if (!keypad || !input_dev) { | 
|  | 413 | error = -ENOMEM; | 
|  | 414 | goto err_free_mem; | 
|  | 415 | } | 
|  | 416 |  | 
|  | 417 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 418 | if (!res) { | 
|  | 419 | error = -ENODEV; | 
|  | 420 | goto err_free_mem; | 
|  | 421 | } | 
|  | 422 |  | 
|  | 423 | keypad->base = ioremap(res->start, resource_size(res)); | 
|  | 424 | if (!keypad->base) { | 
|  | 425 | error = -EBUSY; | 
|  | 426 | goto err_free_mem; | 
|  | 427 | } | 
|  | 428 |  | 
|  | 429 | keypad->clk = clk_get(&pdev->dev, "keypad"); | 
|  | 430 | if (IS_ERR(keypad->clk)) { | 
|  | 431 | dev_err(&pdev->dev, "failed to get keypad clk\n"); | 
|  | 432 | error = PTR_ERR(keypad->clk); | 
|  | 433 | goto err_unmap_base; | 
|  | 434 | } | 
|  | 435 |  | 
|  | 436 | keypad->input_dev = input_dev; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 437 | keypad->pdev = pdev; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 438 | keypad->row_shift = row_shift; | 
|  | 439 | keypad->rows = pdata->rows; | 
|  | 440 | keypad->cols = pdata->cols; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 441 | keypad->stopped = true; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 442 | init_waitqueue_head(&keypad->wait); | 
|  | 443 |  | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 444 | if (pdev->dev.of_node) { | 
|  | 445 | #ifdef CONFIG_OF | 
|  | 446 | samsung_keypad_parse_dt_gpio(&pdev->dev, keypad); | 
|  | 447 | keypad->type = of_device_is_compatible(pdev->dev.of_node, | 
|  | 448 | "samsung,s5pv210-keypad"); | 
|  | 449 | #endif | 
|  | 450 | } else { | 
|  | 451 | keypad->type = platform_get_device_id(pdev)->driver_data; | 
|  | 452 | } | 
|  | 453 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 454 | input_dev->name = pdev->name; | 
|  | 455 | input_dev->id.bustype = BUS_HOST; | 
|  | 456 | input_dev->dev.parent = &pdev->dev; | 
|  | 457 | input_set_drvdata(input_dev, keypad); | 
|  | 458 |  | 
|  | 459 | input_dev->open = samsung_keypad_open; | 
|  | 460 | input_dev->close = samsung_keypad_close; | 
|  | 461 |  | 
|  | 462 | input_dev->evbit[0] = BIT_MASK(EV_KEY); | 
|  | 463 | if (!pdata->no_autorepeat) | 
|  | 464 | input_dev->evbit[0] |= BIT_MASK(EV_REP); | 
|  | 465 |  | 
|  | 466 | input_set_capability(input_dev, EV_MSC, MSC_SCAN); | 
|  | 467 |  | 
|  | 468 | input_dev->keycode = keypad->keycodes; | 
|  | 469 | input_dev->keycodesize = sizeof(keypad->keycodes[0]); | 
|  | 470 | input_dev->keycodemax = pdata->rows << row_shift; | 
|  | 471 |  | 
|  | 472 | matrix_keypad_build_keymap(keymap_data, row_shift, | 
|  | 473 | input_dev->keycode, input_dev->keybit); | 
|  | 474 |  | 
|  | 475 | keypad->irq = platform_get_irq(pdev, 0); | 
|  | 476 | if (keypad->irq < 0) { | 
|  | 477 | error = keypad->irq; | 
|  | 478 | goto err_put_clk; | 
|  | 479 | } | 
|  | 480 |  | 
|  | 481 | error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq, | 
|  | 482 | IRQF_ONESHOT, dev_name(&pdev->dev), keypad); | 
|  | 483 | if (error) { | 
|  | 484 | dev_err(&pdev->dev, "failed to register keypad interrupt\n"); | 
|  | 485 | goto err_put_clk; | 
|  | 486 | } | 
|  | 487 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 488 | device_init_wakeup(&pdev->dev, pdata->wakeup); | 
|  | 489 | platform_set_drvdata(pdev, keypad); | 
|  | 490 | pm_runtime_enable(&pdev->dev); | 
|  | 491 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 492 | error = input_register_device(keypad->input_dev); | 
|  | 493 | if (error) | 
|  | 494 | goto err_free_irq; | 
|  | 495 |  | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 496 | if (pdev->dev.of_node) { | 
|  | 497 | devm_kfree(&pdev->dev, (void *)pdata->keymap_data->keymap); | 
|  | 498 | devm_kfree(&pdev->dev, (void *)pdata->keymap_data); | 
|  | 499 | devm_kfree(&pdev->dev, (void *)pdata); | 
|  | 500 | } | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 501 | return 0; | 
|  | 502 |  | 
|  | 503 | err_free_irq: | 
|  | 504 | free_irq(keypad->irq, keypad); | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 505 | pm_runtime_disable(&pdev->dev); | 
|  | 506 | device_init_wakeup(&pdev->dev, 0); | 
|  | 507 | platform_set_drvdata(pdev, NULL); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 508 | err_put_clk: | 
|  | 509 | clk_put(keypad->clk); | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 510 | samsung_keypad_dt_gpio_free(keypad); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 511 | err_unmap_base: | 
|  | 512 | iounmap(keypad->base); | 
|  | 513 | err_free_mem: | 
|  | 514 | input_free_device(input_dev); | 
|  | 515 | kfree(keypad); | 
|  | 516 |  | 
|  | 517 | return error; | 
|  | 518 | } | 
|  | 519 |  | 
|  | 520 | static int __devexit samsung_keypad_remove(struct platform_device *pdev) | 
|  | 521 | { | 
|  | 522 | struct samsung_keypad *keypad = platform_get_drvdata(pdev); | 
|  | 523 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 524 | pm_runtime_disable(&pdev->dev); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 525 | device_init_wakeup(&pdev->dev, 0); | 
|  | 526 | platform_set_drvdata(pdev, NULL); | 
|  | 527 |  | 
|  | 528 | input_unregister_device(keypad->input_dev); | 
|  | 529 |  | 
|  | 530 | /* | 
|  | 531 | * It is safe to free IRQ after unregistering device because | 
|  | 532 | * samsung_keypad_close will shut off interrupts. | 
|  | 533 | */ | 
|  | 534 | free_irq(keypad->irq, keypad); | 
|  | 535 |  | 
|  | 536 | clk_put(keypad->clk); | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 537 | samsung_keypad_dt_gpio_free(keypad); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 538 |  | 
|  | 539 | iounmap(keypad->base); | 
|  | 540 | kfree(keypad); | 
|  | 541 |  | 
|  | 542 | return 0; | 
|  | 543 | } | 
|  | 544 |  | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 545 | #ifdef CONFIG_PM_RUNTIME | 
|  | 546 | static int samsung_keypad_runtime_suspend(struct device *dev) | 
|  | 547 | { | 
|  | 548 | struct platform_device *pdev = to_platform_device(dev); | 
|  | 549 | struct samsung_keypad *keypad = platform_get_drvdata(pdev); | 
|  | 550 | unsigned int val; | 
|  | 551 | int error; | 
|  | 552 |  | 
|  | 553 | if (keypad->stopped) | 
|  | 554 | return 0; | 
|  | 555 |  | 
|  | 556 | /* This may fail on some SoCs due to lack of controller support */ | 
|  | 557 | error = enable_irq_wake(keypad->irq); | 
|  | 558 | if (!error) | 
|  | 559 | keypad->wake_enabled = true; | 
|  | 560 |  | 
|  | 561 | val = readl(keypad->base + SAMSUNG_KEYIFCON); | 
|  | 562 | val |= SAMSUNG_KEYIFCON_WAKEUPEN; | 
|  | 563 | writel(val, keypad->base + SAMSUNG_KEYIFCON); | 
|  | 564 |  | 
|  | 565 | clk_disable(keypad->clk); | 
|  | 566 |  | 
|  | 567 | return 0; | 
|  | 568 | } | 
|  | 569 |  | 
|  | 570 | static int samsung_keypad_runtime_resume(struct device *dev) | 
|  | 571 | { | 
|  | 572 | struct platform_device *pdev = to_platform_device(dev); | 
|  | 573 | struct samsung_keypad *keypad = platform_get_drvdata(pdev); | 
|  | 574 | unsigned int val; | 
|  | 575 |  | 
|  | 576 | if (keypad->stopped) | 
|  | 577 | return 0; | 
|  | 578 |  | 
|  | 579 | clk_enable(keypad->clk); | 
|  | 580 |  | 
|  | 581 | val = readl(keypad->base + SAMSUNG_KEYIFCON); | 
|  | 582 | val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; | 
|  | 583 | writel(val, keypad->base + SAMSUNG_KEYIFCON); | 
|  | 584 |  | 
|  | 585 | if (keypad->wake_enabled) | 
|  | 586 | disable_irq_wake(keypad->irq); | 
|  | 587 |  | 
|  | 588 | return 0; | 
|  | 589 | } | 
|  | 590 | #endif | 
|  | 591 |  | 
| Dmitry Torokhov | 400bf29 | 2011-11-07 23:59:35 -0800 | [diff] [blame] | 592 | #ifdef CONFIG_PM_SLEEP | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 593 | static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, | 
|  | 594 | bool enable) | 
|  | 595 | { | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 596 | unsigned int val; | 
|  | 597 |  | 
|  | 598 | clk_enable(keypad->clk); | 
|  | 599 |  | 
|  | 600 | val = readl(keypad->base + SAMSUNG_KEYIFCON); | 
|  | 601 | if (enable) { | 
|  | 602 | val |= SAMSUNG_KEYIFCON_WAKEUPEN; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 603 | if (device_may_wakeup(&keypad->pdev->dev)) | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 604 | enable_irq_wake(keypad->irq); | 
|  | 605 | } else { | 
|  | 606 | val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 607 | if (device_may_wakeup(&keypad->pdev->dev)) | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 608 | disable_irq_wake(keypad->irq); | 
|  | 609 | } | 
|  | 610 | writel(val, keypad->base + SAMSUNG_KEYIFCON); | 
|  | 611 |  | 
|  | 612 | clk_disable(keypad->clk); | 
|  | 613 | } | 
|  | 614 |  | 
|  | 615 | static int samsung_keypad_suspend(struct device *dev) | 
|  | 616 | { | 
|  | 617 | struct platform_device *pdev = to_platform_device(dev); | 
|  | 618 | struct samsung_keypad *keypad = platform_get_drvdata(pdev); | 
|  | 619 | struct input_dev *input_dev = keypad->input_dev; | 
|  | 620 |  | 
|  | 621 | mutex_lock(&input_dev->mutex); | 
|  | 622 |  | 
|  | 623 | if (input_dev->users) | 
|  | 624 | samsung_keypad_stop(keypad); | 
|  | 625 |  | 
|  | 626 | samsung_keypad_toggle_wakeup(keypad, true); | 
|  | 627 |  | 
|  | 628 | mutex_unlock(&input_dev->mutex); | 
|  | 629 |  | 
|  | 630 | return 0; | 
|  | 631 | } | 
|  | 632 |  | 
|  | 633 | static int samsung_keypad_resume(struct device *dev) | 
|  | 634 | { | 
|  | 635 | struct platform_device *pdev = to_platform_device(dev); | 
|  | 636 | struct samsung_keypad *keypad = platform_get_drvdata(pdev); | 
|  | 637 | struct input_dev *input_dev = keypad->input_dev; | 
|  | 638 |  | 
|  | 639 | mutex_lock(&input_dev->mutex); | 
|  | 640 |  | 
|  | 641 | samsung_keypad_toggle_wakeup(keypad, false); | 
|  | 642 |  | 
|  | 643 | if (input_dev->users) | 
|  | 644 | samsung_keypad_start(keypad); | 
|  | 645 |  | 
|  | 646 | mutex_unlock(&input_dev->mutex); | 
|  | 647 |  | 
|  | 648 | return 0; | 
|  | 649 | } | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 650 | #endif | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 651 |  | 
|  | 652 | static const struct dev_pm_ops samsung_keypad_pm_ops = { | 
| Mark Brown | 48c98b1 | 2011-12-29 09:58:16 -0800 | [diff] [blame] | 653 | SET_SYSTEM_SLEEP_PM_OPS(samsung_keypad_suspend, samsung_keypad_resume) | 
|  | 654 | SET_RUNTIME_PM_OPS(samsung_keypad_runtime_suspend, | 
|  | 655 | samsung_keypad_runtime_resume, NULL) | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 656 | }; | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 657 |  | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 658 | #ifdef CONFIG_OF | 
|  | 659 | static const struct of_device_id samsung_keypad_dt_match[] = { | 
|  | 660 | { .compatible = "samsung,s3c6410-keypad" }, | 
|  | 661 | { .compatible = "samsung,s5pv210-keypad" }, | 
|  | 662 | {}, | 
|  | 663 | }; | 
|  | 664 | MODULE_DEVICE_TABLE(of, samsung_keypad_dt_match); | 
|  | 665 | #else | 
|  | 666 | #define samsung_keypad_dt_match NULL | 
|  | 667 | #endif | 
|  | 668 |  | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 669 | static struct platform_device_id samsung_keypad_driver_ids[] = { | 
|  | 670 | { | 
|  | 671 | .name		= "samsung-keypad", | 
|  | 672 | .driver_data	= KEYPAD_TYPE_SAMSUNG, | 
|  | 673 | }, { | 
|  | 674 | .name		= "s5pv210-keypad", | 
|  | 675 | .driver_data	= KEYPAD_TYPE_S5PV210, | 
|  | 676 | }, | 
|  | 677 | { }, | 
|  | 678 | }; | 
|  | 679 | MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids); | 
|  | 680 |  | 
|  | 681 | static struct platform_driver samsung_keypad_driver = { | 
|  | 682 | .probe		= samsung_keypad_probe, | 
|  | 683 | .remove		= __devexit_p(samsung_keypad_remove), | 
|  | 684 | .driver		= { | 
|  | 685 | .name	= "samsung-keypad", | 
|  | 686 | .owner	= THIS_MODULE, | 
| Thomas Abraham | b3d6ac3 | 2011-11-02 19:37:22 +0900 | [diff] [blame] | 687 | .of_match_table = samsung_keypad_dt_match, | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 688 | .pm	= &samsung_keypad_pm_ops, | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 689 | }, | 
|  | 690 | .id_table	= samsung_keypad_driver_ids, | 
|  | 691 | }; | 
| JJ Ding | 5146c84 | 2011-11-29 11:08:39 -0800 | [diff] [blame] | 692 | module_platform_driver(samsung_keypad_driver); | 
| Joonyoung Shim | 0fffed2 | 2010-07-21 00:45:10 -0700 | [diff] [blame] | 693 |  | 
|  | 694 | MODULE_DESCRIPTION("Samsung keypad driver"); | 
|  | 695 | MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); | 
|  | 696 | MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); | 
|  | 697 | MODULE_LICENSE("GPL"); |