blob: 5f836b1638c159fa4f2386e09c516092abe8dbd8 [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>
Alexey Dobriyand43c36d2009-10-07 17:09:06 +040030#include <linux/sched.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070031#include <linux/slab.h>
32#include <linux/module.h>
33#include <linux/interrupt.h>
34#include <linux/init.h>
35#include <linux/input.h>
36#include <linux/serio.h>
37#include <linux/workqueue.h>
38
39#define DRIVER_DESC "Sun keyboard driver"
40
41MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
42MODULE_DESCRIPTION(DRIVER_DESC);
43MODULE_LICENSE("GPL");
44
45static unsigned char sunkbd_keycode[128] = {
Vojtech Pavlik8d9a9ae2005-09-05 00:12:47 -050046 0,128,114,129,115, 59, 60, 68, 61, 87, 62, 88, 63,100, 64,112,
Linus Torvalds1da177e2005-04-16 15:20:36 -070047 65, 66, 67, 56,103,119, 99, 70,105,130,131,108,106, 1, 2, 3,
48 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 41, 14,110,113, 98, 55,
49 116,132, 83,133,102, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
50 26, 27,111,127, 71, 72, 73, 74,134,135,107, 0, 29, 30, 31, 32,
51 33, 34, 35, 36, 37, 38, 39, 40, 43, 28, 96, 75, 76, 77, 82,136,
52 104,137, 69, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,101,
53 79, 80, 81, 0, 0, 0,138, 58,125, 57,126,109, 86, 78
54};
55
56#define SUNKBD_CMD_RESET 0x1
57#define SUNKBD_CMD_BELLON 0x2
58#define SUNKBD_CMD_BELLOFF 0x3
59#define SUNKBD_CMD_CLICK 0xa
60#define SUNKBD_CMD_NOCLICK 0xb
61#define SUNKBD_CMD_SETLED 0xe
62#define SUNKBD_CMD_LAYOUT 0xf
63
64#define SUNKBD_RET_RESET 0xff
65#define SUNKBD_RET_ALLUP 0x7f
66#define SUNKBD_RET_LAYOUT 0xfe
67
68#define SUNKBD_LAYOUT_5_MASK 0x20
69#define SUNKBD_RELEASE 0x80
70#define SUNKBD_KEY 0x7f
71
72/*
73 * Per-keyboard data.
74 */
75
76struct sunkbd {
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070077 unsigned char keycode[ARRAY_SIZE(sunkbd_keycode)];
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -050078 struct input_dev *dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -070079 struct serio *serio;
80 struct work_struct tq;
81 wait_queue_head_t wait;
82 char name[64];
83 char phys[32];
84 char type;
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070085 bool enabled;
Linus Torvalds1da177e2005-04-16 15:20:36 -070086 volatile s8 reset;
87 volatile s8 layout;
88};
89
90/*
91 * sunkbd_interrupt() is called by the low level driver when a character
92 * is received.
93 */
94
95static irqreturn_t sunkbd_interrupt(struct serio *serio,
David Howells7d12e782006-10-05 14:55:46 +010096 unsigned char data, unsigned int flags)
Linus Torvalds1da177e2005-04-16 15:20:36 -070097{
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070098 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -070099
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700100 if (sunkbd->reset <= -1) {
101 /*
102 * If cp[i] is 0xff, sunkbd->reset will stay -1.
103 * The keyboard sends 0xff 0xff 0xID on powerup.
104 */
105 sunkbd->reset = data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700106 wake_up_interruptible(&sunkbd->wait);
107 goto out;
108 }
109
110 if (sunkbd->layout == -1) {
111 sunkbd->layout = data;
112 wake_up_interruptible(&sunkbd->wait);
113 goto out;
114 }
115
116 switch (data) {
117
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700118 case SUNKBD_RET_RESET:
119 schedule_work(&sunkbd->tq);
120 sunkbd->reset = -1;
121 break;
122
123 case SUNKBD_RET_LAYOUT:
124 sunkbd->layout = -1;
125 break;
126
127 case SUNKBD_RET_ALLUP: /* All keys released */
128 break;
129
130 default:
131 if (!sunkbd->enabled)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 break;
133
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700134 if (sunkbd->keycode[data & SUNKBD_KEY]) {
135 input_report_key(sunkbd->dev,
136 sunkbd->keycode[data & SUNKBD_KEY],
137 !(data & SUNKBD_RELEASE));
138 input_sync(sunkbd->dev);
139 } else {
140 printk(KERN_WARNING
141 "sunkbd.c: Unknown key (scancode %#x) %s.\n",
142 data & SUNKBD_KEY,
143 data & SUNKBD_RELEASE ? "released" : "pressed");
144 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700145 }
146out:
147 return IRQ_HANDLED;
148}
149
150/*
151 * sunkbd_event() handles events from the input module.
152 */
153
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700154static int sunkbd_event(struct input_dev *dev,
155 unsigned int type, unsigned int code, int value)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156{
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400157 struct sunkbd *sunkbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700158
159 switch (type) {
160
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700161 case EV_LED:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700162
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700163 serio_write(sunkbd->serio, SUNKBD_CMD_SETLED);
164 serio_write(sunkbd->serio,
165 (!!test_bit(LED_CAPSL, dev->led) << 3) |
166 (!!test_bit(LED_SCROLLL, dev->led) << 2) |
167 (!!test_bit(LED_COMPOSE, dev->led) << 1) |
168 !!test_bit(LED_NUML, dev->led));
169 return 0;
170
171 case EV_SND:
172
173 switch (code) {
174
175 case SND_CLICK:
176 serio_write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 return 0;
178
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700179 case SND_BELL:
180 serio_write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value);
181 return 0;
182 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700183
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700184 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700185 }
186
187 return -1;
188}
189
190/*
191 * sunkbd_initialize() checks for a Sun keyboard attached, and determines
192 * its type.
193 */
194
195static int sunkbd_initialize(struct sunkbd *sunkbd)
196{
197 sunkbd->reset = -2;
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700198 serio_write(sunkbd->serio, SUNKBD_CMD_RESET);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700199 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500200 if (sunkbd->reset < 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700201 return -1;
202
203 sunkbd->type = sunkbd->reset;
204
205 if (sunkbd->type == 4) { /* Type 4 keyboard */
206 sunkbd->layout = -2;
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700207 serio_write(sunkbd->serio, SUNKBD_CMD_LAYOUT);
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700208 wait_event_interruptible_timeout(sunkbd->wait,
209 sunkbd->layout >= 0, HZ / 4);
210 if (sunkbd->layout < 0)
211 return -1;
212 if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK)
213 sunkbd->type = 5;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214 }
215
216 return 0;
217}
218
219/*
220 * sunkbd_reinit() sets leds and beeps to a state the computer remembers they
221 * were in.
222 */
223
David Howellsc4028952006-11-22 14:57:56 +0000224static void sunkbd_reinit(struct work_struct *work)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225{
David Howellsc4028952006-11-22 14:57:56 +0000226 struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700227
228 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
229
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700230 serio_write(sunkbd->serio, SUNKBD_CMD_SETLED);
231 serio_write(sunkbd->serio,
232 (!!test_bit(LED_CAPSL, sunkbd->dev->led) << 3) |
233 (!!test_bit(LED_SCROLLL, sunkbd->dev->led) << 2) |
234 (!!test_bit(LED_COMPOSE, sunkbd->dev->led) << 1) |
235 !!test_bit(LED_NUML, sunkbd->dev->led));
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700236 serio_write(sunkbd->serio,
237 SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev->snd));
238 serio_write(sunkbd->serio,
239 SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev->snd));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500240}
241
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700242static void sunkbd_enable(struct sunkbd *sunkbd, bool enable)
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500243{
244 serio_pause_rx(sunkbd->serio);
Fabrice Knevez9bc83dc2006-12-14 15:20:29 -0800245 sunkbd->enabled = enable;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500246 serio_continue_rx(sunkbd->serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247}
248
249/*
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700250 * sunkbd_connect() probes for a Sun keyboard and fills the necessary
251 * structures.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700252 */
253
254static int sunkbd_connect(struct serio *serio, struct serio_driver *drv)
255{
256 struct sunkbd *sunkbd;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500257 struct input_dev *input_dev;
258 int err = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700260
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500261 sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL);
262 input_dev = input_allocate_device();
263 if (!sunkbd || !input_dev)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500264 goto fail1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700265
266 sunkbd->serio = serio;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500267 sunkbd->dev = input_dev;
268 init_waitqueue_head(&sunkbd->wait);
David Howellsc4028952006-11-22 14:57:56 +0000269 INIT_WORK(&sunkbd->tq, sunkbd_reinit);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500270 snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700271
272 serio_set_drvdata(serio, sunkbd);
273
274 err = serio_open(serio, drv);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500275 if (err)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500276 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277
278 if (sunkbd_initialize(sunkbd) < 0) {
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500279 err = -ENODEV;
280 goto fail3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700281 }
282
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700283 snprintf(sunkbd->name, sizeof(sunkbd->name),
284 "Sun Type %d keyboard", sunkbd->type);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700285 memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500286
287 input_dev->name = sunkbd->name;
288 input_dev->phys = sunkbd->phys;
289 input_dev->id.bustype = BUS_RS232;
290 input_dev->id.vendor = SERIO_SUNKBD;
291 input_dev->id.product = sunkbd->type;
292 input_dev->id.version = 0x0100;
Dmitry Torokhov469ba4d2007-04-12 01:34:58 -0400293 input_dev->dev.parent = &serio->dev;
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400294
295 input_set_drvdata(input_dev, sunkbd);
296
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500297 input_dev->event = sunkbd_event;
298
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700299 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
300 BIT_MASK(EV_SND) | BIT_MASK(EV_REP);
301 input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) |
302 BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML);
303 input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500304
305 input_dev->keycode = sunkbd->keycode;
306 input_dev->keycodesize = sizeof(unsigned char);
307 input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode);
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700308 for (i = 0; i < ARRAY_SIZE(sunkbd_keycode); i++)
309 __set_bit(sunkbd->keycode[i], input_dev->keybit);
310 __clear_bit(KEY_RESERVED, input_dev->keybit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700311
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700312 sunkbd_enable(sunkbd, true);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500313
314 err = input_register_device(sunkbd->dev);
315 if (err)
316 goto fail4;
317
Linus Torvalds1da177e2005-04-16 15:20:36 -0700318 return 0;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500319
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700320 fail4: sunkbd_enable(sunkbd, false);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500321 fail3: serio_close(serio);
322 fail2: serio_set_drvdata(serio, NULL);
323 fail1: input_free_device(input_dev);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500324 kfree(sunkbd);
325 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700326}
327
328/*
329 * sunkbd_disconnect() unregisters and closes behind us.
330 */
331
332static void sunkbd_disconnect(struct serio *serio)
333{
334 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500335
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700336 sunkbd_enable(sunkbd, false);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500337 input_unregister_device(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700338 serio_close(serio);
339 serio_set_drvdata(serio, NULL);
340 kfree(sunkbd);
341}
342
343static struct serio_device_id sunkbd_serio_ids[] = {
344 {
345 .type = SERIO_RS232,
346 .proto = SERIO_SUNKBD,
347 .id = SERIO_ANY,
348 .extra = SERIO_ANY,
349 },
350 {
351 .type = SERIO_RS232,
352 .proto = SERIO_UNKNOWN, /* sunkbd does probe */
353 .id = SERIO_ANY,
354 .extra = SERIO_ANY,
355 },
356 { 0 }
357};
358
359MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids);
360
361static struct serio_driver sunkbd_drv = {
362 .driver = {
363 .name = "sunkbd",
364 },
365 .description = DRIVER_DESC,
366 .id_table = sunkbd_serio_ids,
367 .interrupt = sunkbd_interrupt,
368 .connect = sunkbd_connect,
369 .disconnect = sunkbd_disconnect,
370};
371
Axel Lin65ac9f72012-04-03 23:50:17 -0700372module_serio_driver(sunkbd_drv);