b72def6cbfb1bfef28db72cf76d3e2e0377857e7
[seabios.git] / src / ps2port.c
1 // 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
23 static void
24 udelay(int usecs)
25 {
26     // XXX - implement real udelay
27     outb(0x00, PORT_DIAG);
28 }
29
30 static int
31 i8042_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
44 static int
45 i8042_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
58 int
59 i8042_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
79 static 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
111 int
112 i8042_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
122 static int
123 i8042_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
136 static int
137 i8042_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
150 static int
151 ps2_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
174 static int
175 ps2_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             // On a receive timeout, return the item number that the
212             // transfer failed on.
213             ret = i + 1;
214             goto fail;
215         }
216         param[i] = inb(PORT_PS2_DATA);
217     }
218
219 fail:
220     // Restore interrupts and keyboard/mouse.
221     ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
222     if (ret2)
223         return ret2;
224
225     return ret;
226 }
227
228 int
229 kbd_command(int command, u8 *param)
230 {
231     int ret = ps2_command(0, command, param);
232     if (ret)
233         dprintf(2, "keyboard command %x failed (ret=%d)\n", command, ret);
234     return ret;
235 }
236
237 int
238 aux_command(int command, u8 *param)
239 {
240     int ret = ps2_command(1, command, param);
241     if (ret)
242         dprintf(2, "mouse command %x failed (ret=%d)\n", command, ret);
243     return ret;
244 }