blob: 87aa02ee11dbbf97534e512a2908dc3c97827c99 [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
12
13
14/****************************************************************
15 * Low level i8042 commands.
16 ****************************************************************/
17
18// Timeout value.
19#define I8042_CTL_TIMEOUT 10000
20
21#define I8042_BUFFER_SIZE 16
22
Kevin O'Connor3b897192008-07-20 10:08:59 -040023static int
24i8042_wait_read(void)
25{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050026 dprintf(7, "i8042_wait_read\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040027 int i;
28 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
29 u8 status = inb(PORT_PS2_STATUS);
30 if (status & I8042_STR_OBF)
31 return 0;
32 udelay(50);
33 }
34 dprintf(1, "i8042 timeout on wait read\n");
35 return -1;
36}
37
38static int
39i8042_wait_write(void)
40{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050041 dprintf(7, "i8042_wait_write\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040042 int i;
43 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
44 u8 status = inb(PORT_PS2_STATUS);
45 if (! (status & I8042_STR_IBF))
46 return 0;
47 udelay(50);
48 }
49 dprintf(1, "i8042 timeout on wait write\n");
50 return -1;
51}
52
53int
54i8042_flush(void)
55{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050056 dprintf(7, "i8042_flush\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040057 int i;
58 for (i=0; i<I8042_BUFFER_SIZE; i++) {
59 u8 status = inb(PORT_PS2_STATUS);
Kevin O'Connor10ad7992009-10-24 11:06:08 -040060 if (! (status & I8042_STR_OBF))
Kevin O'Connor3b897192008-07-20 10:08:59 -040061 return 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -040062 udelay(50);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050063 u8 data = inb(PORT_PS2_DATA);
Kevin O'Connor9e91c7b2009-01-17 02:30:21 -050064 dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
Kevin O'Connor3b897192008-07-20 10:08:59 -040065 }
66
Kevin O'Connor3b897192008-07-20 10:08:59 -040067 dprintf(1, "i8042 timeout on flush\n");
68 return -1;
69}
70
71static int
72__i8042_command(int command, u8 *param)
73{
74 int receive = (command >> 8) & 0xf;
75 int send = (command >> 12) & 0xf;
76
77 // Send the command.
78 int ret = i8042_wait_write();
79 if (ret)
80 return ret;
81 outb(command, PORT_PS2_STATUS);
82
83 // Send parameters (if any).
84 int i;
85 for (i = 0; i < send; i++) {
86 ret = i8042_wait_write();
87 if (ret)
88 return ret;
89 outb(param[i], PORT_PS2_DATA);
90 }
91
92 // Receive parameters (if any).
93 for (i = 0; i < receive; i++) {
94 ret = i8042_wait_read();
95 if (ret)
96 return ret;
97 param[i] = inb(PORT_PS2_DATA);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050098 dprintf(7, "i8042 param=%x\n", param[i]);
Kevin O'Connor3b897192008-07-20 10:08:59 -040099 }
100
101 return 0;
102}
103
104int
105i8042_command(int command, u8 *param)
106{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500107 dprintf(7, "i8042_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400108 int ret = __i8042_command(command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400109 if (ret)
110 dprintf(2, "i8042 command %x failed\n", command);
111 return ret;
112}
113
114static int
115i8042_kbd_write(u8 c)
116{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500117 dprintf(7, "i8042_kbd_write c=%d\n", c);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400118 int ret = i8042_wait_write();
119 if (! ret)
120 outb(c, PORT_PS2_DATA);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400121 return ret;
122}
123
124static int
125i8042_aux_write(u8 c)
126{
127 return i8042_command(I8042_CMD_AUX_SEND, &c);
128}
129
130
131/****************************************************************
132 * Device commands.
133 ****************************************************************/
134
135#define PS2_RET_ACK 0xfa
136#define PS2_RET_NAK 0xfe
137
138static int
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500139ps2_recvbyte(int aux, int needack, int timeout)
140{
141 u64 end = calc_future_tsc(timeout);
142 for (;;) {
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400143 u8 status = inb(PORT_PS2_STATUS);
144 if (status & I8042_STR_OBF) {
145 u8 data = inb(PORT_PS2_DATA);
146 dprintf(7, "ps2 read %x\n", data);
147
148 if (!!(status & I8042_STR_AUXDATA) == aux) {
149 if (!needack)
150 return data;
151 if (data == PS2_RET_ACK)
152 return data;
153 if (data == PS2_RET_NAK) {
154 dprintf(1, "Got ps2 nak (status=%x); continuing\n", status);
155 return data;
156 }
157 }
158
159 // This data not for us - XXX - just discard it for now.
160 dprintf(1, "Discarding ps2 data %x (status=%x)\n", data, status);
161 }
162
Kevin O'Connor89eb6242009-10-22 22:30:37 -0400163 if (check_time(end)) {
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500164 dprintf(1, "ps2_recvbyte timeout\n");
165 return -1;
166 }
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400167 yield();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500168 }
169}
170
171static int
Kevin O'Connor235df202009-01-18 12:54:51 -0500172ps2_sendbyte(int aux, u8 command, int timeout)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400173{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500174 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400175 int ret;
176 if (aux)
177 ret = i8042_aux_write(command);
178 else
179 ret = i8042_kbd_write(command);
180 if (ret)
181 return ret;
182
183 // Read ack.
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500184 ret = ps2_recvbyte(aux, 1, timeout);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500185 if (ret < 0)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400186 return ret;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400187
188 return 0;
189}
190
191static int
192ps2_command(int aux, int command, u8 *param)
193{
194 int ret2;
195 int receive = (command >> 8) & 0xf;
196 int send = (command >> 12) & 0xf;
197
198 // Disable interrupts and keyboard/mouse.
199 u8 ps2ctr = GET_EBDA(ps2ctr);
200 u8 newctr = ps2ctr;
201 if (aux)
202 newctr |= I8042_CTR_KBDDIS;
203 else
204 newctr |= I8042_CTR_AUXDIS;
205 newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
206 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
207 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
208 if (ret)
209 return ret;
210
Kevin O'Connor235df202009-01-18 12:54:51 -0500211 if (command == ATKBD_CMD_RESET_BAT) {
212 // Reset is special wrt timeouts.
Kevin O'Connor3b897192008-07-20 10:08:59 -0400213
Kevin O'Connor235df202009-01-18 12:54:51 -0500214 // Send command.
215 ret = ps2_sendbyte(aux, command, 1000);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400216 if (ret)
217 goto fail;
Kevin O'Connor235df202009-01-18 12:54:51 -0500218
219 // Receive parameters.
220 ret = ps2_recvbyte(aux, 0, 4000);
221 if (ret < 0)
222 goto fail;
223 param[0] = ret;
224 ret = ps2_recvbyte(aux, 0, 100);
225 if (ret < 0)
226 // Some devices only respond with one byte on reset.
227 ret = 0;
228 param[1] = ret;
229 } else {
230 // Send command.
231 ret = ps2_sendbyte(aux, command, 200);
232 if (ret)
233 goto fail;
234
235 // Send parameters (if any).
236 int i;
237 for (i = 0; i < send; i++) {
238 ret = ps2_sendbyte(aux, param[i], 200);
239 if (ret)
240 goto fail;
241 }
242
243 // Receive parameters (if any).
244 for (i = 0; i < receive; i++) {
245 ret = ps2_recvbyte(aux, 0, 500);
246 if (ret < 0)
247 goto fail;
248 param[i] = ret;
249 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400250 }
251
Kevin O'Connor235df202009-01-18 12:54:51 -0500252 ret = 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400253
254fail:
255 // Restore interrupts and keyboard/mouse.
256 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
257 if (ret2)
258 return ret2;
259
260 return ret;
261}
262
263int
264kbd_command(int command, u8 *param)
265{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500266 dprintf(7, "kbd_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400267 int ret = ps2_command(0, command, param);
268 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500269 dprintf(2, "keyboard command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400270 return ret;
271}
272
273int
274aux_command(int command, u8 *param)
275{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500276 dprintf(7, "aux_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400277 int ret = ps2_command(1, command, param);
278 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500279 dprintf(2, "mouse command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400280 return ret;
281}