blob: 59541315fd7f8a6f8968bdc6a226894b3167ef06 [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
11#include "ps2port.h" // 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
54int
55i8042_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
105int
106i8042_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
131
132/****************************************************************
133 * Device commands.
134 ****************************************************************/
135
136#define PS2_RET_ACK 0xfa
137#define PS2_RET_NAK 0xfe
138
139static int
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500140ps2_recvbyte(int aux, int needack, int timeout)
141{
142 u64 end = calc_future_tsc(timeout);
143 for (;;) {
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400144 u8 status = inb(PORT_PS2_STATUS);
145 if (status & I8042_STR_OBF) {
146 u8 data = inb(PORT_PS2_DATA);
147 dprintf(7, "ps2 read %x\n", data);
148
149 if (!!(status & I8042_STR_AUXDATA) == aux) {
150 if (!needack)
151 return data;
152 if (data == PS2_RET_ACK)
153 return data;
154 if (data == PS2_RET_NAK) {
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500155 dprintf(1, "Got ps2 nak (status=%x)\n", status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400156 return data;
157 }
158 }
159
Kevin O'Connora5d84582010-03-13 20:06:34 -0500160 // This data not for us - XXX - just discard it for now.
161 dprintf(1, "Discarding ps2 data %x (status=%x)\n", data, status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400162 }
163
Kevin O'Connor89eb6242009-10-22 22:30:37 -0400164 if (check_time(end)) {
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -0500165 // Don't warn on second byte of a reset
166 if (timeout > 100)
167 warn_timeout();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500168 return -1;
169 }
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400170 yield();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500171 }
172}
173
174static int
Kevin O'Connor235df202009-01-18 12:54:51 -0500175ps2_sendbyte(int aux, u8 command, int timeout)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400176{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500177 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400178 int ret;
179 if (aux)
180 ret = i8042_aux_write(command);
181 else
182 ret = i8042_kbd_write(command);
183 if (ret)
184 return ret;
185
186 // Read ack.
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500187 ret = ps2_recvbyte(aux, 1, timeout);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500188 if (ret < 0)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400189 return ret;
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500190 if (ret != PS2_RET_ACK)
191 return -1;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400192
193 return 0;
194}
195
196static int
197ps2_command(int aux, int command, u8 *param)
198{
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500199 int ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400200 int receive = (command >> 8) & 0xf;
201 int send = (command >> 12) & 0xf;
202
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500203 // Disable interrupts and keyboard/mouse.
204 u8 ps2ctr = GET_EBDA(ps2ctr);
205 u8 newctr = ps2ctr;
206 if (aux)
207 newctr |= I8042_CTR_KBDDIS;
208 else
209 newctr |= I8042_CTR_AUXDIS;
210 newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
211 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
212 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
213 if (ret)
214 return ret;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400215
Kevin O'Connor67a9eec2010-03-13 19:00:02 -0500216 // Flush any interrupts already pending.
217 yield();
218
Kevin O'Connor235df202009-01-18 12:54:51 -0500219 if (command == ATKBD_CMD_RESET_BAT) {
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500220 // Reset is special wrt timeouts and bytes received.
Kevin O'Connor3b897192008-07-20 10:08:59 -0400221
Kevin O'Connor235df202009-01-18 12:54:51 -0500222 // Send command.
223 ret = ps2_sendbyte(aux, command, 1000);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400224 if (ret)
225 goto fail;
Kevin O'Connor235df202009-01-18 12:54:51 -0500226
227 // Receive parameters.
228 ret = ps2_recvbyte(aux, 0, 4000);
229 if (ret < 0)
230 goto fail;
231 param[0] = ret;
232 ret = ps2_recvbyte(aux, 0, 100);
233 if (ret < 0)
234 // Some devices only respond with one byte on reset.
235 ret = 0;
236 param[1] = ret;
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500237 } else if (command == ATKBD_CMD_GETID) {
238 // Getid is special wrt bytes received.
239
240 // Send command.
241 ret = ps2_sendbyte(aux, command, 200);
242 if (ret)
243 goto fail;
244
245 // Receive parameters.
246 ret = ps2_recvbyte(aux, 0, 500);
247 if (ret < 0)
248 goto fail;
249 param[0] = ret;
250 if (ret == 0xab || ret == 0xac || ret == 0x2b || ret == 0x5d
251 || ret == 0x60 || ret == 0x47) {
252 // These ids (keyboards) return two bytes.
253 ret = ps2_recvbyte(aux, 0, 500);
254 if (ret < 0)
255 goto fail;
256 param[1] = ret;
257 } else {
258 param[1] = 0;
259 }
Kevin O'Connor235df202009-01-18 12:54:51 -0500260 } else {
261 // Send command.
262 ret = ps2_sendbyte(aux, command, 200);
263 if (ret)
264 goto fail;
265
266 // Send parameters (if any).
267 int i;
268 for (i = 0; i < send; i++) {
269 ret = ps2_sendbyte(aux, param[i], 200);
270 if (ret)
271 goto fail;
272 }
273
274 // Receive parameters (if any).
275 for (i = 0; i < receive; i++) {
276 ret = ps2_recvbyte(aux, 0, 500);
277 if (ret < 0)
278 goto fail;
279 param[i] = ret;
280 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400281 }
282
Kevin O'Connor235df202009-01-18 12:54:51 -0500283 ret = 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400284
285fail:
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500286 // Restore interrupts and keyboard/mouse.
287 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
288 if (ret2)
289 return ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400290
291 return ret;
292}
293
294int
295kbd_command(int command, u8 *param)
296{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500297 dprintf(7, "kbd_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400298 int ret = ps2_command(0, command, param);
299 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500300 dprintf(2, "keyboard command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400301 return ret;
302}
303
304int
305aux_command(int command, u8 *param)
306{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500307 dprintf(7, "aux_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400308 int ret = ps2_command(1, command, param);
309 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500310 dprintf(2, "mouse command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400311 return ret;
312}
Kevin O'Connor57877482009-12-09 21:00:41 -0500313
314
315/****************************************************************
316 * IRQ handlers
317 ****************************************************************/
318
319// INT74h : PS/2 mouse hardware interrupt
320void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500321handle_74(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500322{
323 if (! CONFIG_PS2PORT)
324 return;
325
326 debug_isr(DEBUG_ISR_74);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500327
328 u8 v = inb(PORT_PS2_STATUS);
329 if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
330 != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
331 dprintf(1, "mouse irq but no mouse data.\n");
332 goto done;
333 }
334 v = inb(PORT_PS2_DATA);
335
336 process_mouse(v);
337
338done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500339 eoi_pic2();
340}
341
342// INT09h : Keyboard Hardware Service Entry Point
343void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500344handle_09(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500345{
346 if (! CONFIG_PS2PORT)
347 return;
348
349 debug_isr(DEBUG_ISR_09);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500350
351 // read key from keyboard controller
352 u8 v = inb(PORT_PS2_STATUS);
353 if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA)) != I8042_STR_OBF) {
354 dprintf(1, "keyboard irq but no keyboard data.\n");
355 goto done;
356 }
357 v = inb(PORT_PS2_DATA);
358
359 process_key(v);
360
361done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500362 eoi_pic1();
363}
364
365
366/****************************************************************
367 * Setup
368 ****************************************************************/
369
370static void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500371keyboard_init(void *data)
Kevin O'Connor57877482009-12-09 21:00:41 -0500372{
373 /* flush incoming keys */
374 int ret = i8042_flush();
375 if (ret)
376 return;
377
378 // Controller self-test.
379 u8 param[2];
380 ret = i8042_command(I8042_CMD_CTL_TEST, param);
381 if (ret)
382 return;
383 if (param[0] != 0x55) {
384 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
385 return;
386 }
387
388 // Controller keyboard test.
389 ret = i8042_command(I8042_CMD_KBD_TEST, param);
390 if (ret)
391 return;
392 if (param[0] != 0x00) {
393 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
394 return;
395 }
396
397 // Enable keyboard and mouse ports.
398 ret = i8042_command(I8042_CMD_KBD_ENABLE, NULL);
399 if (ret)
400 return;
401 ret = i8042_command(I8042_CMD_AUX_ENABLE, NULL);
402 if (ret)
403 return;
404
405
406 /* ------------------- keyboard side ------------------------*/
407 /* reset keyboard and self test (keyboard side) */
408 ret = kbd_command(ATKBD_CMD_RESET_BAT, param);
409 if (ret)
410 return;
411 if (param[0] != 0xaa) {
412 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
413 return;
414 }
415
416 /* Disable keyboard */
417 ret = kbd_command(ATKBD_CMD_RESET_DIS, NULL);
418 if (ret)
419 return;
420
421 // Set scancode command (mode 2)
422 param[0] = 0x02;
423 ret = kbd_command(ATKBD_CMD_SSCANSET, param);
424 if (ret)
425 return;
426
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500427 // Keyboard Mode: scan code convert, disable mouse, enable IRQ 1
428 SET_EBDA(ps2ctr, I8042_CTR_AUXDIS | I8042_CTR_XLATE | I8042_CTR_KBDINT);
Kevin O'Connor57877482009-12-09 21:00:41 -0500429
430 /* Enable keyboard */
431 ret = kbd_command(ATKBD_CMD_ENABLE, NULL);
432 if (ret)
433 return;
434
Kevin O'Connor357bdfa2010-02-26 08:57:13 -0500435 dprintf(1, "PS2 keyboard initialized\n");
Kevin O'Connor57877482009-12-09 21:00:41 -0500436}
437
438void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500439ps2port_setup(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500440{
Kevin O'Connor59c75742010-02-13 18:49:24 -0500441 ASSERT32FLAT();
Kevin O'Connor57877482009-12-09 21:00:41 -0500442 if (! CONFIG_PS2PORT)
443 return;
444 dprintf(3, "init ps2port\n");
445
446 enable_hwirq(1, entry_09);
447 enable_hwirq(12, entry_74);
448
449 run_thread(keyboard_init, NULL);
450}