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