Commit | Line | Data |
---|---|---|
bc95df78 RK |
1 | /* |
2 | * SPEAr Keyboard Driver | |
3 | * Based on omap-keypad driver | |
4 | * | |
5 | * Copyright (C) 2010 ST Microelectronics | |
6 | * Rajeev Kumar<rajeev-dlh.kumar@st.com> | |
7 | * | |
8 | * This file is licensed under the terms of the GNU General Public | |
9 | * License version 2. This program is licensed "as is" without any | |
10 | * warranty of any kind, whether express or implied. | |
11 | */ | |
12 | ||
13 | #include <linux/clk.h> | |
14 | #include <linux/errno.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/interrupt.h> | |
17 | #include <linux/input.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/irq.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/module.h> | |
829c4f96 | 22 | #include <linux/of.h> |
bc95df78 RK |
23 | #include <linux/platform_device.h> |
24 | #include <linux/pm_wakeup.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/types.h> | |
27 | #include <plat/keyboard.h> | |
28 | ||
29 | /* Keyboard Registers */ | |
f6f2efa3 | 30 | #define MODE_CTL_REG 0x00 |
e99191f0 SH |
31 | #define STATUS_REG 0x0C |
32 | #define DATA_REG 0x10 | |
bc95df78 RK |
33 | #define INTR_MASK 0x54 |
34 | ||
35 | /* Register Values */ | |
36 | /* | |
37 | * pclk freq mask = (APB FEQ -1)= 82 MHZ.Programme bit 15-9 in mode | |
38 | * control register as 1010010(82MHZ) | |
39 | */ | |
40 | #define PCLK_FREQ_MSK 0xA400 /* 82 MHz */ | |
1932811f DT |
41 | #define NUM_ROWS 16 |
42 | #define NUM_COLS 16 | |
43 | ||
f6f2efa3 SH |
44 | #define MODE_CTL_KEYBOARD (0x2 << 0) |
45 | #define MODE_CTL_SCAN_RATE_10 (0x0 << 2) | |
46 | #define MODE_CTL_SCAN_RATE_20 (0x1 << 2) | |
47 | #define MODE_CTL_SCAN_RATE_40 (0x2 << 2) | |
48 | #define MODE_CTL_SCAN_RATE_80 (0x3 << 2) | |
49 | #define MODE_CTL_KEYNUM_SHIFT 6 | |
50 | #define MODE_CTL_START_SCAN (0x1 << 8) | |
51 | ||
52 | #define STATUS_DATA_AVAIL (0x1 << 1) | |
53 | ||
54 | #define DATA_ROW_MASK 0xF0 | |
55 | #define DATA_COLUMN_MASK 0x0F | |
56 | ||
57 | #define ROW_SHIFT 4 | |
bc95df78 RK |
58 | |
59 | struct spear_kbd { | |
60 | struct input_dev *input; | |
61 | struct resource *res; | |
62 | void __iomem *io_base; | |
63 | struct clk *clk; | |
64 | unsigned int irq; | |
f8354c60 | 65 | unsigned int mode; |
bc95df78 | 66 | unsigned short last_key; |
1932811f | 67 | unsigned short keycodes[NUM_ROWS * NUM_COLS]; |
829c4f96 | 68 | bool rep; |
bc95df78 RK |
69 | }; |
70 | ||
71 | static irqreturn_t spear_kbd_interrupt(int irq, void *dev_id) | |
72 | { | |
73 | struct spear_kbd *kbd = dev_id; | |
74 | struct input_dev *input = kbd->input; | |
75 | unsigned int key; | |
e99191f0 | 76 | u32 sts, val; |
bc95df78 | 77 | |
e99191f0 | 78 | sts = readl_relaxed(kbd->io_base + STATUS_REG); |
f6f2efa3 | 79 | if (!(sts & STATUS_DATA_AVAIL)) |
bc95df78 RK |
80 | return IRQ_NONE; |
81 | ||
82 | if (kbd->last_key != KEY_RESERVED) { | |
83 | input_report_key(input, kbd->last_key, 0); | |
84 | kbd->last_key = KEY_RESERVED; | |
85 | } | |
86 | ||
87 | /* following reads active (row, col) pair */ | |
f6f2efa3 SH |
88 | val = readl_relaxed(kbd->io_base + DATA_REG) & |
89 | (DATA_ROW_MASK | DATA_COLUMN_MASK); | |
bc95df78 RK |
90 | key = kbd->keycodes[val]; |
91 | ||
92 | input_event(input, EV_MSC, MSC_SCAN, val); | |
93 | input_report_key(input, key, 1); | |
94 | input_sync(input); | |
95 | ||
96 | kbd->last_key = key; | |
97 | ||
98 | /* clear interrupt */ | |
e99191f0 | 99 | writel_relaxed(0, kbd->io_base + STATUS_REG); |
bc95df78 RK |
100 | |
101 | return IRQ_HANDLED; | |
102 | } | |
103 | ||
104 | static int spear_kbd_open(struct input_dev *dev) | |
105 | { | |
106 | struct spear_kbd *kbd = input_get_drvdata(dev); | |
107 | int error; | |
e99191f0 | 108 | u32 val; |
bc95df78 RK |
109 | |
110 | kbd->last_key = KEY_RESERVED; | |
111 | ||
112 | error = clk_enable(kbd->clk); | |
113 | if (error) | |
114 | return error; | |
115 | ||
116 | /* program keyboard */ | |
f6f2efa3 SH |
117 | val = MODE_CTL_SCAN_RATE_80 | MODE_CTL_KEYBOARD | PCLK_FREQ_MSK | |
118 | (kbd->mode << MODE_CTL_KEYNUM_SHIFT); | |
119 | writel_relaxed(val, kbd->io_base + MODE_CTL_REG); | |
e99191f0 | 120 | writel_relaxed(1, kbd->io_base + STATUS_REG); |
bc95df78 RK |
121 | |
122 | /* start key scan */ | |
f6f2efa3 SH |
123 | val = readl_relaxed(kbd->io_base + MODE_CTL_REG); |
124 | val |= MODE_CTL_START_SCAN; | |
125 | writel_relaxed(val, kbd->io_base + MODE_CTL_REG); | |
bc95df78 RK |
126 | |
127 | return 0; | |
128 | } | |
129 | ||
130 | static void spear_kbd_close(struct input_dev *dev) | |
131 | { | |
132 | struct spear_kbd *kbd = input_get_drvdata(dev); | |
e99191f0 | 133 | u32 val; |
bc95df78 RK |
134 | |
135 | /* stop key scan */ | |
f6f2efa3 SH |
136 | val = readl_relaxed(kbd->io_base + MODE_CTL_REG); |
137 | val &= ~MODE_CTL_START_SCAN; | |
138 | writel_relaxed(val, kbd->io_base + MODE_CTL_REG); | |
bc95df78 RK |
139 | |
140 | clk_disable(kbd->clk); | |
141 | ||
142 | kbd->last_key = KEY_RESERVED; | |
143 | } | |
144 | ||
829c4f96 VK |
145 | #ifdef CONFIG_OF |
146 | static int __devinit spear_kbd_parse_dt(struct platform_device *pdev, | |
147 | struct spear_kbd *kbd) | |
bc95df78 | 148 | { |
829c4f96 | 149 | struct device_node *np = pdev->dev.of_node; |
bc95df78 | 150 | int error; |
829c4f96 | 151 | u32 val; |
bc95df78 | 152 | |
829c4f96 VK |
153 | if (!np) { |
154 | dev_err(&pdev->dev, "Missing DT data\n"); | |
bc95df78 RK |
155 | return -EINVAL; |
156 | } | |
157 | ||
829c4f96 VK |
158 | if (of_property_read_bool(np, "autorepeat")) |
159 | kbd->rep = true; | |
160 | ||
161 | error = of_property_read_u32(np, "st,mode", &val); | |
162 | if (error) { | |
163 | dev_err(&pdev->dev, "DT: Invalid or missing mode\n"); | |
164 | return error; | |
bc95df78 RK |
165 | } |
166 | ||
829c4f96 VK |
167 | kbd->mode = val; |
168 | return 0; | |
169 | } | |
170 | #else | |
171 | static inline int spear_kbd_parse_dt(struct platform_device *pdev, | |
172 | struct spear_kbd *kbd) | |
173 | { | |
174 | return -ENOSYS; | |
175 | } | |
176 | #endif | |
177 | ||
178 | static int __devinit spear_kbd_probe(struct platform_device *pdev) | |
179 | { | |
180 | struct kbd_platform_data *pdata = dev_get_platdata(&pdev->dev); | |
181 | const struct matrix_keymap_data *keymap = pdata ? pdata->keymap : NULL; | |
182 | struct spear_kbd *kbd; | |
183 | struct input_dev *input_dev; | |
184 | struct resource *res; | |
185 | int irq; | |
186 | int error; | |
187 | ||
bc95df78 RK |
188 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
189 | if (!res) { | |
190 | dev_err(&pdev->dev, "no keyboard resource defined\n"); | |
191 | return -EBUSY; | |
192 | } | |
193 | ||
194 | irq = platform_get_irq(pdev, 0); | |
195 | if (irq < 0) { | |
196 | dev_err(&pdev->dev, "not able to get irq for the device\n"); | |
197 | return irq; | |
198 | } | |
199 | ||
200 | kbd = kzalloc(sizeof(*kbd), GFP_KERNEL); | |
201 | input_dev = input_allocate_device(); | |
202 | if (!kbd || !input_dev) { | |
203 | dev_err(&pdev->dev, "out of memory\n"); | |
204 | error = -ENOMEM; | |
205 | goto err_free_mem; | |
206 | } | |
207 | ||
208 | kbd->input = input_dev; | |
209 | kbd->irq = irq; | |
829c4f96 VK |
210 | |
211 | if (!pdata) { | |
212 | error = spear_kbd_parse_dt(pdev, kbd); | |
213 | if (error) | |
214 | goto err_free_mem; | |
215 | } else { | |
216 | kbd->mode = pdata->mode; | |
217 | kbd->rep = pdata->rep; | |
218 | } | |
f8354c60 | 219 | |
bc95df78 RK |
220 | kbd->res = request_mem_region(res->start, resource_size(res), |
221 | pdev->name); | |
222 | if (!kbd->res) { | |
223 | dev_err(&pdev->dev, "keyboard region already claimed\n"); | |
224 | error = -EBUSY; | |
225 | goto err_free_mem; | |
226 | } | |
227 | ||
228 | kbd->io_base = ioremap(res->start, resource_size(res)); | |
229 | if (!kbd->io_base) { | |
230 | dev_err(&pdev->dev, "ioremap failed for kbd_region\n"); | |
231 | error = -ENOMEM; | |
232 | goto err_release_mem_region; | |
233 | } | |
234 | ||
235 | kbd->clk = clk_get(&pdev->dev, NULL); | |
236 | if (IS_ERR(kbd->clk)) { | |
237 | error = PTR_ERR(kbd->clk); | |
238 | goto err_iounmap; | |
239 | } | |
240 | ||
241 | input_dev->name = "Spear Keyboard"; | |
242 | input_dev->phys = "keyboard/input0"; | |
243 | input_dev->dev.parent = &pdev->dev; | |
244 | input_dev->id.bustype = BUS_HOST; | |
245 | input_dev->id.vendor = 0x0001; | |
246 | input_dev->id.product = 0x0001; | |
247 | input_dev->id.version = 0x0100; | |
248 | input_dev->open = spear_kbd_open; | |
249 | input_dev->close = spear_kbd_close; | |
250 | ||
1932811f DT |
251 | error = matrix_keypad_build_keymap(keymap, NULL, NUM_ROWS, NUM_COLS, |
252 | kbd->keycodes, input_dev); | |
253 | if (error) { | |
254 | dev_err(&pdev->dev, "Failed to build keymap\n"); | |
255 | goto err_put_clk; | |
256 | } | |
257 | ||
829c4f96 | 258 | if (kbd->rep) |
bc95df78 RK |
259 | __set_bit(EV_REP, input_dev->evbit); |
260 | input_set_capability(input_dev, EV_MSC, MSC_SCAN); | |
261 | ||
bc95df78 RK |
262 | input_set_drvdata(input_dev, kbd); |
263 | ||
264 | error = request_irq(irq, spear_kbd_interrupt, 0, "keyboard", kbd); | |
265 | if (error) { | |
266 | dev_err(&pdev->dev, "request_irq fail\n"); | |
267 | goto err_put_clk; | |
268 | } | |
269 | ||
270 | error = input_register_device(input_dev); | |
271 | if (error) { | |
272 | dev_err(&pdev->dev, "Unable to register keyboard device\n"); | |
273 | goto err_free_irq; | |
274 | } | |
275 | ||
276 | device_init_wakeup(&pdev->dev, 1); | |
277 | platform_set_drvdata(pdev, kbd); | |
278 | ||
279 | return 0; | |
280 | ||
281 | err_free_irq: | |
282 | free_irq(kbd->irq, kbd); | |
283 | err_put_clk: | |
284 | clk_put(kbd->clk); | |
285 | err_iounmap: | |
286 | iounmap(kbd->io_base); | |
287 | err_release_mem_region: | |
288 | release_mem_region(res->start, resource_size(res)); | |
289 | err_free_mem: | |
290 | input_free_device(input_dev); | |
291 | kfree(kbd); | |
292 | ||
293 | return error; | |
294 | } | |
295 | ||
296 | static int __devexit spear_kbd_remove(struct platform_device *pdev) | |
297 | { | |
298 | struct spear_kbd *kbd = platform_get_drvdata(pdev); | |
299 | ||
300 | free_irq(kbd->irq, kbd); | |
301 | input_unregister_device(kbd->input); | |
302 | clk_put(kbd->clk); | |
303 | iounmap(kbd->io_base); | |
304 | release_mem_region(kbd->res->start, resource_size(kbd->res)); | |
305 | kfree(kbd); | |
306 | ||
5bdea835 | 307 | device_init_wakeup(&pdev->dev, 0); |
bc95df78 RK |
308 | platform_set_drvdata(pdev, NULL); |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
313 | #ifdef CONFIG_PM | |
314 | static int spear_kbd_suspend(struct device *dev) | |
315 | { | |
316 | struct platform_device *pdev = to_platform_device(dev); | |
317 | struct spear_kbd *kbd = platform_get_drvdata(pdev); | |
318 | struct input_dev *input_dev = kbd->input; | |
319 | ||
320 | mutex_lock(&input_dev->mutex); | |
321 | ||
322 | if (input_dev->users) | |
323 | clk_enable(kbd->clk); | |
324 | ||
325 | if (device_may_wakeup(&pdev->dev)) | |
326 | enable_irq_wake(kbd->irq); | |
327 | ||
328 | mutex_unlock(&input_dev->mutex); | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
333 | static int spear_kbd_resume(struct device *dev) | |
334 | { | |
335 | struct platform_device *pdev = to_platform_device(dev); | |
336 | struct spear_kbd *kbd = platform_get_drvdata(pdev); | |
337 | struct input_dev *input_dev = kbd->input; | |
338 | ||
339 | mutex_lock(&input_dev->mutex); | |
340 | ||
341 | if (device_may_wakeup(&pdev->dev)) | |
342 | disable_irq_wake(kbd->irq); | |
343 | ||
344 | if (input_dev->users) | |
345 | clk_enable(kbd->clk); | |
346 | ||
347 | mutex_unlock(&input_dev->mutex); | |
348 | ||
349 | return 0; | |
350 | } | |
bc95df78 RK |
351 | #endif |
352 | ||
f79e30a8 VK |
353 | static SIMPLE_DEV_PM_OPS(spear_kbd_pm_ops, spear_kbd_suspend, spear_kbd_resume); |
354 | ||
829c4f96 VK |
355 | #ifdef CONFIG_OF |
356 | static const struct of_device_id spear_kbd_id_table[] = { | |
357 | { .compatible = "st,spear300-kbd" }, | |
358 | {} | |
359 | }; | |
360 | MODULE_DEVICE_TABLE(of, spear_kbd_id_table); | |
361 | #endif | |
362 | ||
bc95df78 RK |
363 | static struct platform_driver spear_kbd_driver = { |
364 | .probe = spear_kbd_probe, | |
365 | .remove = __devexit_p(spear_kbd_remove), | |
366 | .driver = { | |
367 | .name = "keyboard", | |
368 | .owner = THIS_MODULE, | |
bc95df78 | 369 | .pm = &spear_kbd_pm_ops, |
829c4f96 | 370 | .of_match_table = of_match_ptr(spear_kbd_id_table), |
bc95df78 RK |
371 | }, |
372 | }; | |
5146c84f | 373 | module_platform_driver(spear_kbd_driver); |
bc95df78 RK |
374 | |
375 | MODULE_AUTHOR("Rajeev Kumar"); | |
376 | MODULE_DESCRIPTION("SPEAr Keyboard Driver"); | |
377 | MODULE_LICENSE("GPL"); |