blob: dc6bb9d5b4f0287ab3cbba6fbb1fdb0121f532d5 [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>
Linus Torvalds1da177e2005-04-16 15:20:36 -070034#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 {
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070076 unsigned char keycode[ARRAY_SIZE(sunkbd_keycode)];
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 Torokhov7cac9cd2009-09-03 21:57:48 -070084 bool 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{
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070097 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -070098
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -070099 if (sunkbd->reset <= -1) {
100 /*
101 * If cp[i] is 0xff, sunkbd->reset will stay -1.
102 * The keyboard sends 0xff 0xff 0xID on powerup.
103 */
104 sunkbd->reset = data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700105 wake_up_interruptible(&sunkbd->wait);
106 goto out;
107 }
108
109 if (sunkbd->layout == -1) {
110 sunkbd->layout = data;
111 wake_up_interruptible(&sunkbd->wait);
112 goto out;
113 }
114
115 switch (data) {
116
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700117 case SUNKBD_RET_RESET:
118 schedule_work(&sunkbd->tq);
119 sunkbd->reset = -1;
120 break;
121
122 case SUNKBD_RET_LAYOUT:
123 sunkbd->layout = -1;
124 break;
125
126 case SUNKBD_RET_ALLUP: /* All keys released */
127 break;
128
129 default:
130 if (!sunkbd->enabled)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 break;
132
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700133 if (sunkbd->keycode[data & SUNKBD_KEY]) {
134 input_report_key(sunkbd->dev,
135 sunkbd->keycode[data & SUNKBD_KEY],
136 !(data & SUNKBD_RELEASE));
137 input_sync(sunkbd->dev);
138 } else {
139 printk(KERN_WARNING
140 "sunkbd.c: Unknown key (scancode %#x) %s.\n",
141 data & SUNKBD_KEY,
142 data & SUNKBD_RELEASE ? "released" : "pressed");
143 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700144 }
145out:
146 return IRQ_HANDLED;
147}
148
149/*
150 * sunkbd_event() handles events from the input module.
151 */
152
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700153static int sunkbd_event(struct input_dev *dev,
154 unsigned int type, unsigned int code, int value)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155{
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400156 struct sunkbd *sunkbd = input_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157
158 switch (type) {
159
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700160 case EV_LED:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700161
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700162 serio_write(sunkbd->serio, SUNKBD_CMD_SETLED);
163 serio_write(sunkbd->serio,
164 (!!test_bit(LED_CAPSL, dev->led) << 3) |
165 (!!test_bit(LED_SCROLLL, dev->led) << 2) |
166 (!!test_bit(LED_COMPOSE, dev->led) << 1) |
167 !!test_bit(LED_NUML, dev->led));
168 return 0;
169
170 case EV_SND:
171
172 switch (code) {
173
174 case SND_CLICK:
175 serio_write(sunkbd->serio, SUNKBD_CMD_NOCLICK - value);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700176 return 0;
177
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700178 case SND_BELL:
179 serio_write(sunkbd->serio, SUNKBD_CMD_BELLOFF - value);
180 return 0;
181 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700182
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700183 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700184 }
185
186 return -1;
187}
188
189/*
190 * sunkbd_initialize() checks for a Sun keyboard attached, and determines
191 * its type.
192 */
193
194static int sunkbd_initialize(struct sunkbd *sunkbd)
195{
196 sunkbd->reset = -2;
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700197 serio_write(sunkbd->serio, SUNKBD_CMD_RESET);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700198 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500199 if (sunkbd->reset < 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700200 return -1;
201
202 sunkbd->type = sunkbd->reset;
203
204 if (sunkbd->type == 4) { /* Type 4 keyboard */
205 sunkbd->layout = -2;
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700206 serio_write(sunkbd->serio, SUNKBD_CMD_LAYOUT);
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700207 wait_event_interruptible_timeout(sunkbd->wait,
208 sunkbd->layout >= 0, HZ / 4);
209 if (sunkbd->layout < 0)
210 return -1;
211 if (sunkbd->layout & SUNKBD_LAYOUT_5_MASK)
212 sunkbd->type = 5;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700213 }
214
215 return 0;
216}
217
218/*
219 * sunkbd_reinit() sets leds and beeps to a state the computer remembers they
220 * were in.
221 */
222
David Howellsc4028952006-11-22 14:57:56 +0000223static void sunkbd_reinit(struct work_struct *work)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700224{
David Howellsc4028952006-11-22 14:57:56 +0000225 struct sunkbd *sunkbd = container_of(work, struct sunkbd, tq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226
227 wait_event_interruptible_timeout(sunkbd->wait, sunkbd->reset >= 0, HZ);
228
Dmitry Torokhovdd0d5442009-08-05 00:30:26 -0700229 serio_write(sunkbd->serio, SUNKBD_CMD_SETLED);
230 serio_write(sunkbd->serio,
231 (!!test_bit(LED_CAPSL, sunkbd->dev->led) << 3) |
232 (!!test_bit(LED_SCROLLL, sunkbd->dev->led) << 2) |
233 (!!test_bit(LED_COMPOSE, sunkbd->dev->led) << 1) |
234 !!test_bit(LED_NUML, sunkbd->dev->led));
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700235 serio_write(sunkbd->serio,
236 SUNKBD_CMD_NOCLICK - !!test_bit(SND_CLICK, sunkbd->dev->snd));
237 serio_write(sunkbd->serio,
238 SUNKBD_CMD_BELLOFF - !!test_bit(SND_BELL, sunkbd->dev->snd));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500239}
240
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700241static void sunkbd_enable(struct sunkbd *sunkbd, bool enable)
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500242{
243 serio_pause_rx(sunkbd->serio);
Fabrice Knevez9bc83dc2006-12-14 15:20:29 -0800244 sunkbd->enabled = enable;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500245 serio_continue_rx(sunkbd->serio);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246}
247
248/*
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700249 * sunkbd_connect() probes for a Sun keyboard and fills the necessary
250 * structures.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251 */
252
253static int sunkbd_connect(struct serio *serio, struct serio_driver *drv)
254{
255 struct sunkbd *sunkbd;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500256 struct input_dev *input_dev;
257 int err = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500260 sunkbd = kzalloc(sizeof(struct sunkbd), GFP_KERNEL);
261 input_dev = input_allocate_device();
262 if (!sunkbd || !input_dev)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500263 goto fail1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264
265 sunkbd->serio = serio;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500266 sunkbd->dev = input_dev;
267 init_waitqueue_head(&sunkbd->wait);
David Howellsc4028952006-11-22 14:57:56 +0000268 INIT_WORK(&sunkbd->tq, sunkbd_reinit);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500269 snprintf(sunkbd->phys, sizeof(sunkbd->phys), "%s/input0", serio->phys);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270
271 serio_set_drvdata(serio, sunkbd);
272
273 err = serio_open(serio, drv);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500274 if (err)
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500275 goto fail2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276
277 if (sunkbd_initialize(sunkbd) < 0) {
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500278 err = -ENODEV;
279 goto fail3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700280 }
281
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700282 snprintf(sunkbd->name, sizeof(sunkbd->name),
283 "Sun Type %d keyboard", sunkbd->type);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700284 memcpy(sunkbd->keycode, sunkbd_keycode, sizeof(sunkbd->keycode));
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500285
286 input_dev->name = sunkbd->name;
287 input_dev->phys = sunkbd->phys;
288 input_dev->id.bustype = BUS_RS232;
289 input_dev->id.vendor = SERIO_SUNKBD;
290 input_dev->id.product = sunkbd->type;
291 input_dev->id.version = 0x0100;
Dmitry Torokhov469ba4d2007-04-12 01:34:58 -0400292 input_dev->dev.parent = &serio->dev;
Dmitry Torokhovb3568722007-04-12 01:34:20 -0400293
294 input_set_drvdata(input_dev, sunkbd);
295
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500296 input_dev->event = sunkbd_event;
297
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700298 input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
299 BIT_MASK(EV_SND) | BIT_MASK(EV_REP);
300 input_dev->ledbit[0] = BIT_MASK(LED_CAPSL) | BIT_MASK(LED_COMPOSE) |
301 BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_NUML);
302 input_dev->sndbit[0] = BIT_MASK(SND_CLICK) | BIT_MASK(SND_BELL);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500303
304 input_dev->keycode = sunkbd->keycode;
305 input_dev->keycodesize = sizeof(unsigned char);
306 input_dev->keycodemax = ARRAY_SIZE(sunkbd_keycode);
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700307 for (i = 0; i < ARRAY_SIZE(sunkbd_keycode); i++)
308 __set_bit(sunkbd->keycode[i], input_dev->keybit);
309 __clear_bit(KEY_RESERVED, input_dev->keybit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700310
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700311 sunkbd_enable(sunkbd, true);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500312
313 err = input_register_device(sunkbd->dev);
314 if (err)
315 goto fail4;
316
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317 return 0;
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500318
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700319 fail4: sunkbd_enable(sunkbd, false);
Dmitry Torokhov2b03b602006-11-05 22:39:56 -0500320 fail3: serio_close(serio);
321 fail2: serio_set_drvdata(serio, NULL);
322 fail1: input_free_device(input_dev);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500323 kfree(sunkbd);
324 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700325}
326
327/*
328 * sunkbd_disconnect() unregisters and closes behind us.
329 */
330
331static void sunkbd_disconnect(struct serio *serio)
332{
333 struct sunkbd *sunkbd = serio_get_drvdata(serio);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500334
Dmitry Torokhov7cac9cd2009-09-03 21:57:48 -0700335 sunkbd_enable(sunkbd, false);
Dmitry Torokhov3c42f0c2005-09-15 02:01:45 -0500336 input_unregister_device(sunkbd->dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700337 serio_close(serio);
338 serio_set_drvdata(serio, NULL);
339 kfree(sunkbd);
340}
341
342static struct serio_device_id sunkbd_serio_ids[] = {
343 {
344 .type = SERIO_RS232,
345 .proto = SERIO_SUNKBD,
346 .id = SERIO_ANY,
347 .extra = SERIO_ANY,
348 },
349 {
350 .type = SERIO_RS232,
351 .proto = SERIO_UNKNOWN, /* sunkbd does probe */
352 .id = SERIO_ANY,
353 .extra = SERIO_ANY,
354 },
355 { 0 }
356};
357
358MODULE_DEVICE_TABLE(serio, sunkbd_serio_ids);
359
360static struct serio_driver sunkbd_drv = {
361 .driver = {
362 .name = "sunkbd",
363 },
364 .description = DRIVER_DESC,
365 .id_table = sunkbd_serio_ids,
366 .interrupt = sunkbd_interrupt,
367 .connect = sunkbd_connect,
368 .disconnect = sunkbd_disconnect,
369};
370
Axel Lin65ac9f72012-04-03 23:50:17 -0700371module_serio_driver(sunkbd_drv);