blob: be0f5d19d0238590f2ffd219a5ef602e505fcb3c [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * $Id: sunkbd.c,v 1.14 2001/09/25 10:12:07 vojtech Exp $
3 *
4 * Copyright (c) 1999-2001 Vojtech Pavlik
5 */
6
7/*
8 * Sun keyboard driver for Linux
9 */
10
11/*
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 *
26 * Should you need to contact me, the author, you can do so either by
27 * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
28 * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
29 */
30
31#include <linux/delay.h>
32#include <linux/slab.h>
33#include <linux/module.h>
34#include <linux/interrupt.h>
35#include <linux/init.h>
36#include <linux/input.h>
37#include <linux/serio.h>
38#include <linux/workqueue.h>
39
40#define DRIVER_DESC "Sun keyboard driver"
41
42MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
43MODULE_DESCRIPTION(DRIVER_DESC);
44MODULE_LICENSE("GPL");
45
46static unsigned char sunkbd_keycode[128] = {
Vojtech Pavlik8d9a9ae2005-09-05 00:12:47 -050047 0,128,114,129,115, 59, 60, 68, 61, 87, 62, 88, 63,100, 64,112,
Linus Torvalds1da177e2005-04-16 15:20:36 -070048 65, 66, 67, 56,103,119, 99, 70,105,130,131,108,106, 1, 2, 3,
49 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 41, 14,110,113, 98, 55,
50 116,132, 83,133,102, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
51 26, 27,111,127, 71, 72, 73, 74,134,135,107, 0, 29, 30, 31, 32,
52 33, 34, 35, 36, 37, 38, 39, 40, 43, 28, 96, 75, 76, 77, 82,136,
53 104,137, 69, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,101,
54 79, 80, 81, 0, 0, 0,138, 58,125, 57,126,109, 86, 78
55};
56
57#define SUNKBD_CMD_RESET 0x1
58#define SUNKBD_CMD_BELLON 0x2
59#define SUNKBD_CMD_BELLOFF 0x3
60#define SUNKBD_CMD_CLICK 0xa
61#define SUNKBD_CMD_NOCLICK 0xb
62#define SUNKBD_CMD_SETLED 0xe
63#define SUNKBD_CMD_LAYOUT 0xf
64
65#define SUNKBD_RET_RESET 0xff
66#define SUNKBD_RET_ALLUP 0x7f
67#define SUNKBD_RET_LAYOUT 0xfe
68
69#define SUNKBD_LAYOUT_5_MASK 0x20
70#define SUNKBD_RELEASE 0x80
71#define SUNKBD_KEY 0x7f
72
73/*
74 * Per-keyboard data.
75 */
76
77struct sunkbd {
78 unsigned char keycode[128];
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -050079 struct input_dev *dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -070080 struct serio *serio;
81 struct work_struct tq;
82 wait_queue_head_t wait;
83 char name[64];
84 char phys[32];
85 char type;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -050086 unsigned char enabled;
Linus Torvalds1da177e2005-04-16 15:20:36 -070087 volatile s8 reset;
88 volatile s8 layout;
89};
90
91/*
92 * sunkbd_interrupt() is called by the low level driver when a character
93 * is received.
94 */
95
96static irqreturn_t sunkbd_interrupt(struct serio *serio,
David Howells7d12e782006-10-05 14:55:46 +010097 unsigned char data, unsigned int flags)
Linus Torvalds1da177e2005-04-16 15:20:36 -070098{
99 struct sunkbd* sunkbd = serio_get_drvdata(serio);
100
101 if (sunkbd->reset <= -1) { /* If cp[i] is 0xff, sunkbd->reset will stay -1. */
102 sunkbd->reset = data; /* The keyboard sends 0xff 0xff 0xID on powerup */
103 wake_up_interruptible(&sunkbd->wait);
104 goto out;
105 }
106
107 if (sunkbd->layout == -1) {
108 sunkbd->layout = data;
109 wake_up_interruptible(&sunkbd->wait);
110 goto out;
111 }
112
113 switch (data) {
114
115 case SUNKBD_RET_RESET:
116 schedule_work(&sunkbd->tq);
117 sunkbd->reset = -1;
118 break;
119
120 case SUNKBD_RET_LAYOUT:
121 sunkbd->layout = -1;
122 break;
123
124 case SUNKBD_RET_ALLUP: /* All keys released */
125 break;
126
127 default:
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500128 if (!sunkbd->enabled)
129 break;
130
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 if (sunkbd->keycode[data & SUNKBD_KEY]) {
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500132 input_report_key(sunkbd->dev, sunkbd->keycode[data & SUNKBD_KEY], !(data & SUNKBD_RELEASE));
133 input_sync(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134 } else {
135 printk(KERN_WARNING "sunkbd.c: Unknown key (scancode %#x) %s.\n",
136 data & SUNKBD_KEY, data & SUNKBD_RELEASE ? "released" : "pressed");
137 }
138 }
139out:
140 return IRQ_HANDLED;
141}
142
143/*
144 * sunkbd_event() handles events from the input module.
145 */
146
147static int sunkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
148{
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400149 struct sunkbd *sunkbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150
151 switch (type) {
152
153 case EV_LED:
154
155 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED);
156 sunkbd->serio->write(sunkbd->serio,
157 (!!test_bit(LED_CAPSL, dev->led) << 3) | (!!test_bit(LED_SCROLLL, dev->led) << 2) |
158 (!!test_bit(LED_COMPOSE, dev->led) << 1) | !!test_bit(LED_NUML, dev->led));
159 return 0;
160
161 case EV_SND:
162
163 switch (code) {
164
165 case SND_CLICK:
166 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value);
167 return 0;
168
169 case SND_BELL:
170 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value);
171 return 0;
172 }
173
174 break;
175 }
176
177 return -1;
178}
179
180/*
181 * sunkbd_initialize() checks for a Sun keyboard attached, and determines
182 * its type.
183 */
184
185static int sunkbd_initialize(struct sunkbd *sunkbd)
186{
187 sunkbd->reset = -2;
188 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_RESET);
189 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500190 if (sunkbd->reset < 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 return -1;
192
193 sunkbd->type = sunkbd->reset;
194
195 if (sunkbd->type == 4) { /* Type 4 keyboard */
196 sunkbd->layout = -2;
197 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_LAYOUT);
198 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->layout >= 0, HZ/4);
199 if (sunkbd->layout < 0) return -1;
200 if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK) sunkbd->type = 5;
201 }
202
203 return 0;
204}
205
206/*
207 * sunkbd_reinit() sets leds and beeps to a state the computer remembers they
208 * were in.
209 */
210
David Howellsc4028952006-11-22 14:57:56 +0000211static void sunkbd_reinit(struct work_struct *work)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212{
David Howellsc4028952006-11-22 14:57:56 +0000213 struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214
215 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
216
217 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_SETLED);
218 sunkbd->serio->write(sunkbd->serio,
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500219 (!!test_bit(LED_CAPSL, sunkbd->dev->led) << 3) | (!!test_bit(LED_SCROLLL, sunkbd->dev->led) << 2) |
220 (!!test_bit(LED_COMPOSE, sunkbd->dev->led) << 1) | !!test_bit(LED_NUML, sunkbd->dev->led));
221 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev->snd));
222 sunkbd->serio->write(sunkbd->serio, SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev->snd));
223}
224
225static void sunkbd_enable(struct sunkbd *sunkbd, int enable)
226{
227 serio_pause_rx(sunkbd->serio);
Fabrice Knevez9bc83dc2006-12-14 15:20:29 -0800228 sunkbd->enabled = enable;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500229 serio_continue_rx(sunkbd->serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700230}
231
232/*
233 * sunkbd_connect() probes for a Sun keyboard and fills the necessary structures.
234 */
235
236static int sunkbd_connect(struct serio *serio, struct serio_driver *drv)
237{
238 struct sunkbd *sunkbd;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500239 struct input_dev *input_dev;
240 int err = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700241 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500243 sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL);
244 input_dev = input_allocate_device();
245 if (!sunkbd || !input_dev)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500246 goto fail1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247
248 sunkbd->serio = serio;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500249 sunkbd->dev = input_dev;
250 init_waitqueue_head(&sunkbd->wait);
David Howellsc4028952006-11-22 14:57:56 +0000251 INIT_WORK(&sunkbd->tq, sunkbd_reinit);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500252 snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700253
254 serio_set_drvdata(serio, sunkbd);
255
256 err = serio_open(serio, drv);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500257 if (err)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500258 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259
260 if (sunkbd_initialize(sunkbd) < 0) {
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500261 err = -ENODEV;
262 goto fail3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700263 }
264
Dmitry Torokhovea08c6f2006-06-26 01:46:17 -0400265 snprintf(sunkbd->name, sizeof(sunkbd->name), "Sun Type %d keyboard", sunkbd->type);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700266 memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500267
268 input_dev->name = sunkbd->name;
269 input_dev->phys = sunkbd->phys;
270 input_dev->id.bustype = BUS_RS232;
271 input_dev->id.vendor = SERIO_SUNKBD;
272 input_dev->id.product = sunkbd->type;
273 input_dev->id.version = 0x0100;
Dmitry Torokhov469ba4d2007-04-12 01:34:58 -0400274 input_dev->dev.parent = &serio->dev;
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400275
276 input_set_drvdata(input_dev, sunkbd);
277
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500278 input_dev->event = sunkbd_event;
279
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700280 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
281 BIT_MASK(EV_SND) | BIT_MASK(EV_REP);
282 input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) |
283 BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML);
284 input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500285
286 input_dev->keycode = sunkbd->keycode;
287 input_dev->keycodesize = sizeof(unsigned char);
288 input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289 for (i = 0; i < 128; i++)
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500290 set_bit(sunkbd->keycode[i], input_dev->keybit);
291 clear_bit(0, input_dev->keybit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700292
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500293 sunkbd_enable(sunkbd, 1);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500294
295 err = input_register_device(sunkbd->dev);
296 if (err)
297 goto fail4;
298
Linus Torvalds1da177e2005-04-16 15:20:36 -0700299 return 0;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500300
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500301 fail4: sunkbd_enable(sunkbd, 0);
302 fail3: serio_close(serio);
303 fail2: serio_set_drvdata(serio, NULL);
304 fail1: input_free_device(input_dev);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500305 kfree(sunkbd);
306 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700307}
308
309/*
310 * sunkbd_disconnect() unregisters and closes behind us.
311 */
312
313static void sunkbd_disconnect(struct serio *serio)
314{
315 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500316
317 sunkbd_enable(sunkbd, 0);
318 input_unregister_device(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319 serio_close(serio);
320 serio_set_drvdata(serio, NULL);
321 kfree(sunkbd);
322}
323
324static struct serio_device_id sunkbd_serio_ids[] = {
325 {
326 .type = SERIO_RS232,
327 .proto = SERIO_SUNKBD,
328 .id = SERIO_ANY,
329 .extra = SERIO_ANY,
330 },
331 {
332 .type = SERIO_RS232,
333 .proto = SERIO_UNKNOWN, /* sunkbd does probe */
334 .id = SERIO_ANY,
335 .extra = SERIO_ANY,
336 },
337 { 0 }
338};
339
340MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids);
341
342static struct serio_driver sunkbd_drv = {
343 .driver = {
344 .name = "sunkbd",
345 },
346 .description = DRIVER_DESC,
347 .id_table = sunkbd_serio_ids,
348 .interrupt = sunkbd_interrupt,
349 .connect = sunkbd_connect,
350 .disconnect = sunkbd_disconnect,
351};
352
353/*
354 * The functions for insering/removing us as a module.
355 */
356
357static int __init sunkbd_init(void)
358{
Akinobu Mita153a9df02006-11-23 23:35:10 -0500359 return serio_register_driver(&sunkbd_drv);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700360}
361
362static void __exit sunkbd_exit(void)
363{
364 serio_unregister_driver(&sunkbd_drv);
365}
366
367module_init(sunkbd_init);
368module_exit(sunkbd_exit);