blob: 9fce6d1e29b20bbc2e03f6e3e1a3d39f93c61b9d [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 * Copyright (c) 1999-2001 Vojtech Pavlik
3 */
4
5/*
6 * Sun keyboard driver for Linux
7 */
8
9/*
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 * Should you need to contact me, the author, you can do so either by
25 * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
26 * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
27 */
28
29#include <linux/delay.h>
30#include <linux/slab.h>
31#include <linux/module.h>
32#include <linux/interrupt.h>
33#include <linux/init.h>
34#include <linux/input.h>
35#include <linux/serio.h>
36#include <linux/workqueue.h>
37
38#define DRIVER_DESC "Sun keyboard driver"
39
40MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
41MODULE_DESCRIPTION(DRIVER_DESC);
42MODULE_LICENSE("GPL");
43
44static unsigned char sunkbd_keycode[128] = {
Vojtech Pavlik8d9a9ae2005-09-05 00:12:47 -050045 0,128,114,129,115, 59, 60, 68, 61, 87, 62, 88, 63,100, 64,112,
Linus Torvalds1da177e2005-04-16 15:20:36 -070046 65, 66, 67, 56,103,119, 99, 70,105,130,131,108,106, 1, 2, 3,
47 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 41, 14,110,113, 98, 55,
48 116,132, 83,133,102, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
49 26, 27,111,127, 71, 72, 73, 74,134,135,107, 0, 29, 30, 31, 32,
50 33, 34, 35, 36, 37, 38, 39, 40, 43, 28, 96, 75, 76, 77, 82,136,
51 104,137, 69, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,101,
52 79, 80, 81, 0, 0, 0,138, 58,125, 57,126,109, 86, 78
53};
54
55#define SUNKBD_CMD_RESET 0x1
56#define SUNKBD_CMD_BELLON 0x2
57#define SUNKBD_CMD_BELLOFF 0x3
58#define SUNKBD_CMD_CLICK 0xa
59#define SUNKBD_CMD_NOCLICK 0xb
60#define SUNKBD_CMD_SETLED 0xe
61#define SUNKBD_CMD_LAYOUT 0xf
62
63#define SUNKBD_RET_RESET 0xff
64#define SUNKBD_RET_ALLUP 0x7f
65#define SUNKBD_RET_LAYOUT 0xfe
66
67#define SUNKBD_LAYOUT_5_MASK 0x20
68#define SUNKBD_RELEASE 0x80
69#define SUNKBD_KEY 0x7f
70
71/*
72 * Per-keyboard data.
73 */
74
75struct sunkbd {
76 unsigned char keycode[128];
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -050077 struct input_dev *dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078 struct serio *serio;
79 struct work_struct tq;
80 wait_queue_head_t wait;
81 char name[64];
82 char phys[32];
83 char type;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -050084 unsigned char enabled;
Linus Torvalds1da177e2005-04-16 15:20:36 -070085 volatile s8 reset;
86 volatile s8 layout;
87};
88
89/*
90 * sunkbd_interrupt() is called by the low level driver when a character
91 * is received.
92 */
93
94static irqreturn_t sunkbd_interrupt(struct serio *serio,
David Howells7d12e782006-10-05 14:55:46 +010095 unsigned char data, unsigned int flags)
Linus Torvalds1da177e2005-04-16 15:20:36 -070096{
97 struct sunkbd* sunkbd = serio_get_drvdata(serio);
98
99 if (sunkbd->reset <= -1) { /* If cp[i] is 0xff, sunkbd->reset will stay -1. */
100 sunkbd->reset = data; /* The keyboard sends 0xff 0xff 0xID on powerup */
101 wake_up_interruptible(&sunkbd->wait);
102 goto out;
103 }
104
105 if (sunkbd->layout == -1) {
106 sunkbd->layout = data;
107 wake_up_interruptible(&sunkbd->wait);
108 goto out;
109 }
110
111 switch (data) {
112
113 case SUNKBD_RET_RESET:
114 schedule_work(&sunkbd->tq);
115 sunkbd->reset = -1;
116 break;
117
118 case SUNKBD_RET_LAYOUT:
119 sunkbd->layout = -1;
120 break;
121
122 case SUNKBD_RET_ALLUP: /* All keys released */
123 break;
124
125 default:
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500126 if (!sunkbd->enabled)
127 break;
128
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129 if (sunkbd->keycode[data & SUNKBD_KEY]) {
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500130 input_report_key(sunkbd->dev, sunkbd->keycode[data & SUNKBD_KEY], !(data & SUNKBD_RELEASE));
131 input_sync(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 } else {
133 printk(KERN_WARNING "sunkbd.c: Unknown key (scancode %#x) %s.\n",
134 data & SUNKBD_KEY, data & SUNKBD_RELEASE ? "released" : "pressed");
135 }
136 }
137out:
138 return IRQ_HANDLED;
139}
140
141/*
142 * sunkbd_event() handles events from the input module.
143 */
144
145static int sunkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
146{
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400147 struct sunkbd *sunkbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148
149 switch (type) {
150
151 case EV_LED:
152
153 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED);
154 sunkbd->serio->write(sunkbd->serio,
155 (!!test_bit(LED_CAPSL, dev->led) << 3) | (!!test_bit(LED_SCROLLL, dev->led) << 2) |
156 (!!test_bit(LED_COMPOSE, dev->led) << 1) | !!test_bit(LED_NUML, dev->led));
157 return 0;
158
159 case EV_SND:
160
161 switch (code) {
162
163 case SND_CLICK:
164 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value);
165 return 0;
166
167 case SND_BELL:
168 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value);
169 return 0;
170 }
171
172 break;
173 }
174
175 return -1;
176}
177
178/*
179 * sunkbd_initialize() checks for a Sun keyboard attached, and determines
180 * its type.
181 */
182
183static int sunkbd_initialize(struct sunkbd *sunkbd)
184{
185 sunkbd->reset = -2;
186 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_RESET);
187 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500188 if (sunkbd->reset < 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700189 return -1;
190
191 sunkbd->type = sunkbd->reset;
192
193 if (sunkbd->type == 4) { /* Type 4 keyboard */
194 sunkbd->layout = -2;
195 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_LAYOUT);
196 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->layout >= 0, HZ/4);
197 if (sunkbd->layout < 0) return -1;
198 if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK) sunkbd->type = 5;
199 }
200
201 return 0;
202}
203
204/*
205 * sunkbd_reinit() sets leds and beeps to a state the computer remembers they
206 * were in.
207 */
208
David Howellsc4028952006-11-22 14:57:56 +0000209static void sunkbd_reinit(struct work_struct *work)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700210{
David Howellsc4028952006-11-22 14:57:56 +0000211 struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212
213 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
214
215 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED);
216 sunkbd->serio->write(sunkbd->serio,
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500217 (!!test_bit(LED_CAPSL, sunkbd->dev->led) << 3) | (!!test_bit(LED_SCROLLL, sunkbd->dev->led) << 2) |
218 (!!test_bit(LED_COMPOSE, sunkbd->dev->led) << 1) | !!test_bit(LED_NUML, sunkbd->dev->led));
219 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev->snd));
220 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev->snd));
221}
222
223static void sunkbd_enable(struct sunkbd *sunkbd, int enable)
224{
225 serio_pause_rx(sunkbd->serio);
Fabrice Knevez9bc83dc2006-12-14 15:20:29 -0800226 sunkbd->enabled = enable;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500227 serio_continue_rx(sunkbd->serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228}
229
230/*
231 * sunkbd_connect() probes for a Sun keyboard and fills the necessary structures.
232 */
233
234static int sunkbd_connect(struct serio *serio, struct serio_driver *drv)
235{
236 struct sunkbd *sunkbd;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500237 struct input_dev *input_dev;
238 int err = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700239 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500241 sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL);
242 input_dev = input_allocate_device();
243 if (!sunkbd || !input_dev)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500244 goto fail1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700245
246 sunkbd->serio = serio;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500247 sunkbd->dev = input_dev;
248 init_waitqueue_head(&sunkbd->wait);
David Howellsc4028952006-11-22 14:57:56 +0000249 INIT_WORK(&sunkbd->tq, sunkbd_reinit);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500250 snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251
252 serio_set_drvdata(serio, sunkbd);
253
254 err = serio_open(serio, drv);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500255 if (err)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500256 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700257
258 if (sunkbd_initialize(sunkbd) < 0) {
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500259 err = -ENODEV;
260 goto fail3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700261 }
262
Dmitry Torokhovea08c6f2006-06-26 01:46:17 -0400263 snprintf(sunkbd->name, sizeof(sunkbd->name), "Sun Type %d keyboard", sunkbd->type);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264 memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500265
266 input_dev->name = sunkbd->name;
267 input_dev->phys = sunkbd->phys;
268 input_dev->id.bustype = BUS_RS232;
269 input_dev->id.vendor = SERIO_SUNKBD;
270 input_dev->id.product = sunkbd->type;
271 input_dev->id.version = 0x0100;
Dmitry Torokhov469ba4d2007-04-12 01:34:58 -0400272 input_dev->dev.parent = &serio->dev;
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400273
274 input_set_drvdata(input_dev, sunkbd);
275
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500276 input_dev->event = sunkbd_event;
277
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700278 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
279 BIT_MASK(EV_SND) | BIT_MASK(EV_REP);
280 input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) |
281 BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML);
282 input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500283
284 input_dev->keycode = sunkbd->keycode;
285 input_dev->keycodesize = sizeof(unsigned char);
286 input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700287 for (i = 0; i < 128; i++)
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500288 set_bit(sunkbd->keycode[i], input_dev->keybit);
289 clear_bit(0, input_dev->keybit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500291 sunkbd_enable(sunkbd, 1);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500292
293 err = input_register_device(sunkbd->dev);
294 if (err)
295 goto fail4;
296
Linus Torvalds1da177e2005-04-16 15:20:36 -0700297 return 0;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500298
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500299 fail4: sunkbd_enable(sunkbd, 0);
300 fail3: serio_close(serio);
301 fail2: serio_set_drvdata(serio, NULL);
302 fail1: input_free_device(input_dev);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500303 kfree(sunkbd);
304 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700305}
306
307/*
308 * sunkbd_disconnect() unregisters and closes behind us.
309 */
310
311static void sunkbd_disconnect(struct serio *serio)
312{
313 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500314
315 sunkbd_enable(sunkbd, 0);
316 input_unregister_device(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317 serio_close(serio);
318 serio_set_drvdata(serio, NULL);
319 kfree(sunkbd);
320}
321
322static struct serio_device_id sunkbd_serio_ids[] = {
323 {
324 .type = SERIO_RS232,
325 .proto = SERIO_SUNKBD,
326 .id = SERIO_ANY,
327 .extra = SERIO_ANY,
328 },
329 {
330 .type = SERIO_RS232,
331 .proto = SERIO_UNKNOWN, /* sunkbd does probe */
332 .id = SERIO_ANY,
333 .extra = SERIO_ANY,
334 },
335 { 0 }
336};
337
338MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids);
339
340static struct serio_driver sunkbd_drv = {
341 .driver = {
342 .name = "sunkbd",
343 },
344 .description = DRIVER_DESC,
345 .id_table = sunkbd_serio_ids,
346 .interrupt = sunkbd_interrupt,
347 .connect = sunkbd_connect,
348 .disconnect = sunkbd_disconnect,
349};
350
351/*
352 * The functions for insering/removing us as a module.
353 */
354
355static int __init sunkbd_init(void)
356{
Akinobu Mita153a9df02006-11-23 23:35:10 -0500357 return serio_register_driver(&sunkbd_drv);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700358}
359
360static void __exit sunkbd_exit(void)
361{
362 serio_unregister_driver(&sunkbd_drv);
363}
364
365module_init(sunkbd_init);
366module_exit(sunkbd_exit);