blob: c9c3046541b6d3deba10ea61015e129fad480dd5 [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
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 unsigned long flags = irq_save();
58
59 int i;
60 for (i=0; i<I8042_BUFFER_SIZE; i++) {
61 u8 status = inb(PORT_PS2_STATUS);
62 if (! (status & I8042_STR_OBF)) {
63 irq_restore(flags);
64 return 0;
65 }
66 udelay(50);
67 inb(PORT_PS2_DATA);
68 }
69
70 irq_restore(flags);
71 dprintf(1, "i8042 timeout on flush\n");
72 return -1;
73}
74
75static int
76__i8042_command(int command, u8 *param)
77{
78 int receive = (command >> 8) & 0xf;
79 int send = (command >> 12) & 0xf;
80
81 // Send the command.
82 int ret = i8042_wait_write();
83 if (ret)
84 return ret;
85 outb(command, PORT_PS2_STATUS);
86
87 // Send parameters (if any).
88 int i;
89 for (i = 0; i < send; i++) {
90 ret = i8042_wait_write();
91 if (ret)
92 return ret;
93 outb(param[i], PORT_PS2_DATA);
94 }
95
96 // Receive parameters (if any).
97 for (i = 0; i < receive; i++) {
98 ret = i8042_wait_read();
99 if (ret)
100 return ret;
101 param[i] = inb(PORT_PS2_DATA);
102 }
103
104 return 0;
105}
106
107int
108i8042_command(int command, u8 *param)
109{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500110 dprintf(7, "i8042_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400111 unsigned long flags = irq_save();
112 int ret = __i8042_command(command, param);
113 irq_restore(flags);
114 if (ret)
115 dprintf(2, "i8042 command %x failed\n", command);
116 return ret;
117}
118
119static int
120i8042_kbd_write(u8 c)
121{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500122 dprintf(7, "i8042_kbd_write c=%d\n", c);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400123 unsigned long flags = irq_save();
124
125 int ret = i8042_wait_write();
126 if (! ret)
127 outb(c, PORT_PS2_DATA);
128
129 irq_restore(flags);
130
131 return ret;
132}
133
134static int
135i8042_aux_write(u8 c)
136{
137 return i8042_command(I8042_CMD_AUX_SEND, &c);
138}
139
140
141/****************************************************************
142 * Device commands.
143 ****************************************************************/
144
145#define PS2_RET_ACK 0xfa
146#define PS2_RET_NAK 0xfe
147
148static int
149ps2_sendbyte(int aux, u8 command)
150{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500151 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400152 int ret;
153 if (aux)
154 ret = i8042_aux_write(command);
155 else
156 ret = i8042_kbd_write(command);
157 if (ret)
158 return ret;
159
160 // Read ack.
161 ret = i8042_wait_read();
162 if (ret)
163 return ret;
164 u8 ack = inb(PORT_PS2_DATA);
165 if (ack != PS2_RET_ACK) {
166 dprintf(1, "Missing ack (got %x not %x)\n", ack, PS2_RET_ACK);
167 return -1;
168 }
169
170 return 0;
171}
172
173static int
174ps2_command(int aux, int command, u8 *param)
175{
176 int ret2;
177 int receive = (command >> 8) & 0xf;
178 int send = (command >> 12) & 0xf;
179
180 // Disable interrupts and keyboard/mouse.
181 u8 ps2ctr = GET_EBDA(ps2ctr);
182 u8 newctr = ps2ctr;
183 if (aux)
184 newctr |= I8042_CTR_KBDDIS;
185 else
186 newctr |= I8042_CTR_AUXDIS;
187 newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
188 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
189 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
190 if (ret)
191 return ret;
192
193 // Send command.
194 ret = ps2_sendbyte(aux, command);
195 if (ret)
196 goto fail;
197
198 // Send parameters (if any).
199 int i;
200 for (i = 0; i < send; i++) {
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500201 ret = ps2_sendbyte(aux, param[i]);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400202 if (ret)
203 goto fail;
204 }
205
206 // Receive parameters (if any).
207 for (i = 0; i < receive; i++) {
208 ret = i8042_wait_read();
Kevin O'Connor7481a302008-07-21 23:42:34 -0400209 if (ret) {
210 // On a receive timeout, return the item number that the
211 // transfer failed on.
212 ret = i + 1;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400213 goto fail;
Kevin O'Connor7481a302008-07-21 23:42:34 -0400214 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400215 param[i] = inb(PORT_PS2_DATA);
216 }
217
218fail:
219 // Restore interrupts and keyboard/mouse.
220 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
221 if (ret2)
222 return ret2;
223
224 return ret;
225}
226
227int
228kbd_command(int command, u8 *param)
229{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500230 dprintf(7, "kbd_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400231 int ret = ps2_command(0, command, param);
232 if (ret)
Kevin O'Connor7481a302008-07-21 23:42:34 -0400233 dprintf(2, "keyboard command %x failed (ret=%d)\n", command, ret);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400234 return ret;
235}
236
237int
238aux_command(int command, u8 *param)
239{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500240 dprintf(7, "aux_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400241 int ret = ps2_command(1, command, param);
242 if (ret)
Kevin O'Connor7481a302008-07-21 23:42:34 -0400243 dprintf(2, "mouse command %x failed (ret=%d)\n", command, ret);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400244 return ret;
245}