blob: d1e6d48ab51a10a183a4dce6b610c2cc588ceb03 [file] [log] [blame]
Kevin O'Connor3b897192008-07-20 10:08:59 -04001// Support for handling the PS/2 mouse/keyboard ports.
2//
3// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05004// Several ideas taken from code Copyright (c) 1999-2004 Vojtech Pavlik
Kevin O'Connor3b897192008-07-20 10:08:59 -04005//
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05006// This file may be distributed under the terms of the GNU LGPLv3 license.
Kevin O'Connor3b897192008-07-20 10:08:59 -04007
8#include "ioport.h" // inb
9#include "util.h" // dprintf
10#include "biosvar.h" // GET_EBDA
Kevin O'Connordd5a8a62010-05-01 19:59:34 -040011#include "ps2port.h" // ps2_kbd_command
Kevin O'Connor57877482009-12-09 21:00:41 -050012#include "pic.h" // eoi_pic1
Kevin O'Connor3b897192008-07-20 10:08:59 -040013
14
15/****************************************************************
16 * Low level i8042 commands.
17 ****************************************************************/
18
19// Timeout value.
20#define I8042_CTL_TIMEOUT 10000
21
22#define I8042_BUFFER_SIZE 16
23
Kevin O'Connor3b897192008-07-20 10:08:59 -040024static int
25i8042_wait_read(void)
26{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050027 dprintf(7, "i8042_wait_read\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040028 int i;
29 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
30 u8 status = inb(PORT_PS2_STATUS);
31 if (status & I8042_STR_OBF)
32 return 0;
33 udelay(50);
34 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050035 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040036 return -1;
37}
38
39static int
40i8042_wait_write(void)
41{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050042 dprintf(7, "i8042_wait_write\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040043 int i;
44 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
45 u8 status = inb(PORT_PS2_STATUS);
46 if (! (status & I8042_STR_IBF))
47 return 0;
48 udelay(50);
49 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050050 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040051 return -1;
52}
53
Kevin O'Connoradaf3732010-09-13 20:22:07 -040054static int
Kevin O'Connor3b897192008-07-20 10:08:59 -040055i8042_flush(void)
56{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050057 dprintf(7, "i8042_flush\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040058 int i;
59 for (i=0; i<I8042_BUFFER_SIZE; i++) {
60 u8 status = inb(PORT_PS2_STATUS);
Kevin O'Connor10ad7992009-10-24 11:06:08 -040061 if (! (status & I8042_STR_OBF))
Kevin O'Connor3b897192008-07-20 10:08:59 -040062 return 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -040063 udelay(50);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050064 u8 data = inb(PORT_PS2_DATA);
Kevin O'Connor9e91c7b2009-01-17 02:30:21 -050065 dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
Kevin O'Connor3b897192008-07-20 10:08:59 -040066 }
67
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050068 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040069 return -1;
70}
71
72static int
73__i8042_command(int command, u8 *param)
74{
75 int receive = (command >> 8) & 0xf;
76 int send = (command >> 12) & 0xf;
77
78 // Send the command.
79 int ret = i8042_wait_write();
80 if (ret)
81 return ret;
82 outb(command, PORT_PS2_STATUS);
83
84 // Send parameters (if any).
85 int i;
86 for (i = 0; i < send; i++) {
87 ret = i8042_wait_write();
88 if (ret)
89 return ret;
90 outb(param[i], PORT_PS2_DATA);
91 }
92
93 // Receive parameters (if any).
94 for (i = 0; i < receive; i++) {
95 ret = i8042_wait_read();
96 if (ret)
97 return ret;
98 param[i] = inb(PORT_PS2_DATA);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050099 dprintf(7, "i8042 param=%x\n", param[i]);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400100 }
101
102 return 0;
103}
104
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400105static int
Kevin O'Connor3b897192008-07-20 10:08:59 -0400106i8042_command(int command, u8 *param)
107{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500108 dprintf(7, "i8042_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400109 int ret = __i8042_command(command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400110 if (ret)
111 dprintf(2, "i8042 command %x failed\n", command);
112 return ret;
113}
114
115static int
116i8042_kbd_write(u8 c)
117{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500118 dprintf(7, "i8042_kbd_write c=%d\n", c);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400119 int ret = i8042_wait_write();
120 if (! ret)
121 outb(c, PORT_PS2_DATA);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400122 return ret;
123}
124
125static int
126i8042_aux_write(u8 c)
127{
128 return i8042_command(I8042_CMD_AUX_SEND, &c);
129}
130
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400131void
132i8042_reboot(void)
133{
134 int i;
135 for (i=0; i<10; i++) {
136 i8042_wait_write();
137 udelay(50);
138 outb(0xfe, PORT_PS2_STATUS); /* pulse reset low */
139 udelay(50);
140 }
141}
142
Kevin O'Connor3b897192008-07-20 10:08:59 -0400143
144/****************************************************************
145 * Device commands.
146 ****************************************************************/
147
148#define PS2_RET_ACK 0xfa
149#define PS2_RET_NAK 0xfe
150
151static int
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500152ps2_recvbyte(int aux, int needack, int timeout)
153{
154 u64 end = calc_future_tsc(timeout);
155 for (;;) {
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400156 u8 status = inb(PORT_PS2_STATUS);
157 if (status & I8042_STR_OBF) {
158 u8 data = inb(PORT_PS2_DATA);
159 dprintf(7, "ps2 read %x\n", data);
160
161 if (!!(status & I8042_STR_AUXDATA) == aux) {
162 if (!needack)
163 return data;
164 if (data == PS2_RET_ACK)
165 return data;
166 if (data == PS2_RET_NAK) {
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500167 dprintf(1, "Got ps2 nak (status=%x)\n", status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400168 return data;
169 }
170 }
171
Kevin O'Connorf6282442010-03-13 21:05:12 -0500172 // This data not part of command - just discard it.
173 dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400174 }
175
Kevin O'Connor144817b2010-05-23 10:46:49 -0400176 if (check_tsc(end)) {
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -0500177 // Don't warn on second byte of a reset
178 if (timeout > 100)
179 warn_timeout();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500180 return -1;
181 }
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400182 yield();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500183 }
184}
185
186static int
Kevin O'Connor235df202009-01-18 12:54:51 -0500187ps2_sendbyte(int aux, u8 command, int timeout)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400188{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500189 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400190 int ret;
191 if (aux)
192 ret = i8042_aux_write(command);
193 else
194 ret = i8042_kbd_write(command);
195 if (ret)
196 return ret;
197
198 // Read ack.
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500199 ret = ps2_recvbyte(aux, 1, timeout);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500200 if (ret < 0)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400201 return ret;
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500202 if (ret != PS2_RET_ACK)
203 return -1;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400204
205 return 0;
206}
207
208static int
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400209__ps2_command(int aux, int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400210{
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500211 int ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400212 int receive = (command >> 8) & 0xf;
213 int send = (command >> 12) & 0xf;
214
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500215 // Disable interrupts and keyboard/mouse.
216 u8 ps2ctr = GET_EBDA(ps2ctr);
Kevin O'Connorf6282442010-03-13 21:05:12 -0500217 u8 newctr = ((ps2ctr | I8042_CTR_AUXDIS | I8042_CTR_KBDDIS)
218 & ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT));
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500219 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
220 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
221 if (ret)
222 return ret;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400223
Kevin O'Connor67a9eec2010-03-13 19:00:02 -0500224 // Flush any interrupts already pending.
225 yield();
226
Kevin O'Connorf6282442010-03-13 21:05:12 -0500227 // Enable port command is being sent to.
228 if (aux)
229 newctr &= ~I8042_CTR_AUXDIS;
230 else
231 newctr &= ~I8042_CTR_KBDDIS;
232 ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
233 if (ret)
234 goto fail;
235
Kevin O'Connor235df202009-01-18 12:54:51 -0500236 if (command == ATKBD_CMD_RESET_BAT) {
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500237 // Reset is special wrt timeouts and bytes received.
Kevin O'Connor3b897192008-07-20 10:08:59 -0400238
Kevin O'Connor235df202009-01-18 12:54:51 -0500239 // Send command.
240 ret = ps2_sendbyte(aux, command, 1000);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400241 if (ret)
242 goto fail;
Kevin O'Connor235df202009-01-18 12:54:51 -0500243
244 // Receive parameters.
245 ret = ps2_recvbyte(aux, 0, 4000);
246 if (ret < 0)
247 goto fail;
248 param[0] = ret;
249 ret = ps2_recvbyte(aux, 0, 100);
250 if (ret < 0)
251 // Some devices only respond with one byte on reset.
252 ret = 0;
253 param[1] = ret;
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500254 } else if (command == ATKBD_CMD_GETID) {
255 // Getid is special wrt bytes received.
256
257 // Send command.
258 ret = ps2_sendbyte(aux, command, 200);
259 if (ret)
260 goto fail;
261
262 // Receive parameters.
263 ret = ps2_recvbyte(aux, 0, 500);
264 if (ret < 0)
265 goto fail;
266 param[0] = ret;
267 if (ret == 0xab || ret == 0xac || ret == 0x2b || ret == 0x5d
268 || ret == 0x60 || ret == 0x47) {
269 // These ids (keyboards) return two bytes.
270 ret = ps2_recvbyte(aux, 0, 500);
271 if (ret < 0)
272 goto fail;
273 param[1] = ret;
274 } else {
275 param[1] = 0;
276 }
Kevin O'Connor235df202009-01-18 12:54:51 -0500277 } else {
278 // Send command.
279 ret = ps2_sendbyte(aux, command, 200);
280 if (ret)
281 goto fail;
282
283 // Send parameters (if any).
284 int i;
285 for (i = 0; i < send; i++) {
286 ret = ps2_sendbyte(aux, param[i], 200);
287 if (ret)
288 goto fail;
289 }
290
291 // Receive parameters (if any).
292 for (i = 0; i < receive; i++) {
293 ret = ps2_recvbyte(aux, 0, 500);
294 if (ret < 0)
295 goto fail;
296 param[i] = ret;
297 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400298 }
299
Kevin O'Connor235df202009-01-18 12:54:51 -0500300 ret = 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400301
302fail:
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500303 // Restore interrupts and keyboard/mouse.
304 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
305 if (ret2)
306 return ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400307
308 return ret;
309}
310
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400311static int
312ps2_command(int aux, int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400313{
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400314 dprintf(7, "ps2_command aux=%d cmd=%x\n", aux, command);
315 int ret = __ps2_command(aux, command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400316 if (ret)
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400317 dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400318 return ret;
319}
320
321int
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400322ps2_kbd_command(int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400323{
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400324 return ps2_command(0, command, param);
325}
326
327int
328ps2_mouse_command(int command, u8 *param)
329{
330 return ps2_command(1, command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400331}
Kevin O'Connor57877482009-12-09 21:00:41 -0500332
333
334/****************************************************************
335 * IRQ handlers
336 ****************************************************************/
337
338// INT74h : PS/2 mouse hardware interrupt
339void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500340handle_74(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500341{
342 if (! CONFIG_PS2PORT)
343 return;
344
345 debug_isr(DEBUG_ISR_74);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500346
347 u8 v = inb(PORT_PS2_STATUS);
348 if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
349 != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
Kevin O'Connorf6282442010-03-13 21:05:12 -0500350 dprintf(1, "ps2 mouse irq but no mouse data.\n");
Kevin O'Connora5d84582010-03-13 20:06:34 -0500351 goto done;
352 }
353 v = inb(PORT_PS2_DATA);
354
Kevin O'Connorf6282442010-03-13 21:05:12 -0500355 if (!(GET_EBDA(ps2ctr) & I8042_CTR_AUXINT))
356 // Interrupts not enabled.
357 goto done;
358
Kevin O'Connora5d84582010-03-13 20:06:34 -0500359 process_mouse(v);
360
361done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500362 eoi_pic2();
363}
364
365// INT09h : Keyboard Hardware Service Entry Point
366void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500367handle_09(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500368{
369 if (! CONFIG_PS2PORT)
370 return;
371
372 debug_isr(DEBUG_ISR_09);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500373
374 // read key from keyboard controller
375 u8 v = inb(PORT_PS2_STATUS);
Kevin O'Connorf6282442010-03-13 21:05:12 -0500376 if (v & I8042_STR_AUXDATA) {
377 dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
Kevin O'Connora5d84582010-03-13 20:06:34 -0500378 goto done;
379 }
380 v = inb(PORT_PS2_DATA);
381
Kevin O'Connorf6282442010-03-13 21:05:12 -0500382 if (!(GET_EBDA(ps2ctr) & I8042_CTR_KBDINT))
383 // Interrupts not enabled.
384 goto done;
385
Kevin O'Connora5d84582010-03-13 20:06:34 -0500386 process_key(v);
387
388done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500389 eoi_pic1();
390}
391
392
393/****************************************************************
394 * Setup
395 ****************************************************************/
396
397static void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500398keyboard_init(void *data)
Kevin O'Connor57877482009-12-09 21:00:41 -0500399{
400 /* flush incoming keys */
401 int ret = i8042_flush();
402 if (ret)
403 return;
404
405 // Controller self-test.
406 u8 param[2];
407 ret = i8042_command(I8042_CMD_CTL_TEST, param);
408 if (ret)
409 return;
410 if (param[0] != 0x55) {
411 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
412 return;
413 }
414
415 // Controller keyboard test.
416 ret = i8042_command(I8042_CMD_KBD_TEST, param);
417 if (ret)
418 return;
419 if (param[0] != 0x00) {
420 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
421 return;
422 }
423
Kevin O'Connorf6282442010-03-13 21:05:12 -0500424 // Disable keyboard and mouse events.
425 SET_EBDA(ps2ctr, I8042_CTR_KBDDIS | I8042_CTR_AUXDIS);
Kevin O'Connor57877482009-12-09 21:00:41 -0500426
427
428 /* ------------------- keyboard side ------------------------*/
429 /* reset keyboard and self test (keyboard side) */
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400430 ret = ps2_kbd_command(ATKBD_CMD_RESET_BAT, param);
Kevin O'Connor57877482009-12-09 21:00:41 -0500431 if (ret)
432 return;
433 if (param[0] != 0xaa) {
434 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
435 return;
436 }
437
438 /* Disable keyboard */
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400439 ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
Kevin O'Connor57877482009-12-09 21:00:41 -0500440 if (ret)
441 return;
442
443 // Set scancode command (mode 2)
444 param[0] = 0x02;
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400445 ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
Kevin O'Connor57877482009-12-09 21:00:41 -0500446 if (ret)
447 return;
448
Kevin O'Connorf6282442010-03-13 21:05:12 -0500449 // Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500450 SET_EBDA(ps2ctr, I8042_CTR_AUXDIS | I8042_CTR_XLATE | I8042_CTR_KBDINT);
Kevin O'Connor57877482009-12-09 21:00:41 -0500451
452 /* Enable keyboard */
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400453 ret = ps2_kbd_command(ATKBD_CMD_ENABLE, NULL);
Kevin O'Connor57877482009-12-09 21:00:41 -0500454 if (ret)
455 return;
456
Kevin O'Connor357bdfa2010-02-26 08:57:13 -0500457 dprintf(1, "PS2 keyboard initialized\n");
Kevin O'Connor57877482009-12-09 21:00:41 -0500458}
459
460void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500461ps2port_setup(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500462{
Kevin O'Connor59c75742010-02-13 18:49:24 -0500463 ASSERT32FLAT();
Kevin O'Connor57877482009-12-09 21:00:41 -0500464 if (! CONFIG_PS2PORT)
465 return;
466 dprintf(3, "init ps2port\n");
467
Kevin O'Connorcc9e1bf2010-07-28 21:31:38 -0400468 enable_hwirq(1, FUNC16(entry_09));
469 enable_hwirq(12, FUNC16(entry_74));
Kevin O'Connor57877482009-12-09 21:00:41 -0500470
471 run_thread(keyboard_init, NULL);
472}