blob: f7070641cf351348a27f71b2ba2ad3677eadd55a [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>
4// Based on code Copyright (c) 1999-2004 Vojtech Pavlik
5//
6// This file may be distributed under the terms of the GNU GPLv3 license.
7
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
23static void
24udelay(int usecs)
25{
26 // XXX - implement real udelay
27 outb(0x00, PORT_DIAG);
28}
29
30static int
31i8042_wait_read(void)
32{
33 int i;
34 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
35 u8 status = inb(PORT_PS2_STATUS);
36 if (status & I8042_STR_OBF)
37 return 0;
38 udelay(50);
39 }
40 dprintf(1, "i8042 timeout on wait read\n");
41 return -1;
42}
43
44static int
45i8042_wait_write(void)
46{
47 int i;
48 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
49 u8 status = inb(PORT_PS2_STATUS);
50 if (! (status & I8042_STR_IBF))
51 return 0;
52 udelay(50);
53 }
54 dprintf(1, "i8042 timeout on wait write\n");
55 return -1;
56}
57
58int
59i8042_flush(void)
60{
61 unsigned long flags = irq_save();
62
63 int i;
64 for (i=0; i<I8042_BUFFER_SIZE; i++) {
65 u8 status = inb(PORT_PS2_STATUS);
66 if (! (status & I8042_STR_OBF)) {
67 irq_restore(flags);
68 return 0;
69 }
70 udelay(50);
71 inb(PORT_PS2_DATA);
72 }
73
74 irq_restore(flags);
75 dprintf(1, "i8042 timeout on flush\n");
76 return -1;
77}
78
79static int
80__i8042_command(int command, u8 *param)
81{
82 int receive = (command >> 8) & 0xf;
83 int send = (command >> 12) & 0xf;
84
85 // Send the command.
86 int ret = i8042_wait_write();
87 if (ret)
88 return ret;
89 outb(command, PORT_PS2_STATUS);
90
91 // Send parameters (if any).
92 int i;
93 for (i = 0; i < send; i++) {
94 ret = i8042_wait_write();
95 if (ret)
96 return ret;
97 outb(param[i], PORT_PS2_DATA);
98 }
99
100 // Receive parameters (if any).
101 for (i = 0; i < receive; i++) {
102 ret = i8042_wait_read();
103 if (ret)
104 return ret;
105 param[i] = inb(PORT_PS2_DATA);
106 }
107
108 return 0;
109}
110
111int
112i8042_command(int command, u8 *param)
113{
114 unsigned long flags = irq_save();
115 int ret = __i8042_command(command, param);
116 irq_restore(flags);
117 if (ret)
118 dprintf(2, "i8042 command %x failed\n", command);
119 return ret;
120}
121
122static int
123i8042_kbd_write(u8 c)
124{
125 unsigned long flags = irq_save();
126
127 int ret = i8042_wait_write();
128 if (! ret)
129 outb(c, PORT_PS2_DATA);
130
131 irq_restore(flags);
132
133 return ret;
134}
135
136static int
137i8042_aux_write(u8 c)
138{
139 return i8042_command(I8042_CMD_AUX_SEND, &c);
140}
141
142
143/****************************************************************
144 * Device commands.
145 ****************************************************************/
146
147#define PS2_RET_ACK 0xfa
148#define PS2_RET_NAK 0xfe
149
150static int
151ps2_sendbyte(int aux, u8 command)
152{
153 int ret;
154 if (aux)
155 ret = i8042_aux_write(command);
156 else
157 ret = i8042_kbd_write(command);
158 if (ret)
159 return ret;
160
161 // Read ack.
162 ret = i8042_wait_read();
163 if (ret)
164 return ret;
165 u8 ack = inb(PORT_PS2_DATA);
166 if (ack != PS2_RET_ACK) {
167 dprintf(1, "Missing ack (got %x not %x)\n", ack, PS2_RET_ACK);
168 return -1;
169 }
170
171 return 0;
172}
173
174static int
175ps2_command(int aux, int command, u8 *param)
176{
177 int ret2;
178 int receive = (command >> 8) & 0xf;
179 int send = (command >> 12) & 0xf;
180
181 // Disable interrupts and keyboard/mouse.
182 u8 ps2ctr = GET_EBDA(ps2ctr);
183 u8 newctr = ps2ctr;
184 if (aux)
185 newctr |= I8042_CTR_KBDDIS;
186 else
187 newctr |= I8042_CTR_AUXDIS;
188 newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
189 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
190 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
191 if (ret)
192 return ret;
193
194 // Send command.
195 ret = ps2_sendbyte(aux, command);
196 if (ret)
197 goto fail;
198
199 // Send parameters (if any).
200 int i;
201 for (i = 0; i < send; i++) {
202 ret = ps2_sendbyte(aux, command);
203 if (ret)
204 goto fail;
205 }
206
207 // Receive parameters (if any).
208 for (i = 0; i < receive; i++) {
209 ret = i8042_wait_read();
210 if (ret)
211 goto fail;
212 param[i] = inb(PORT_PS2_DATA);
213 }
214
215fail:
216 // Restore interrupts and keyboard/mouse.
217 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
218 if (ret2)
219 return ret2;
220
221 return ret;
222}
223
224int
225kbd_command(int command, u8 *param)
226{
227 int ret = ps2_command(0, command, param);
228 if (ret)
229 dprintf(2, "keyboard command %x failed\n", command);
230 return ret;
231}
232
233int
234aux_command(int command, u8 *param)
235{
236 int ret = ps2_command(1, command, param);
237 if (ret)
238 dprintf(2, "mouse command %x failed\n", command);
239 return ret;
240}