1/**
2 * \file
3 * \brief Legacy keyboard and mouse (i8042) driver.
4 */
5
6/*
7 * Copyright (c) 2007, 2008, 2009, 2011, ETH Zurich.
8 * All rights reserved.
9 *
10 * This file is distributed under the terms in the attached LICENSE file.
11 * If you do not find this file, copies can be found by writing to:
12 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
13 */
14
15#include <stdio.h>
16#include <barrelfish/barrelfish.h>
17#include <pci/pci.h>
18
19#include <dev/lpc_kbd_dev.h>
20#include "lpc_kbd.h"
21
22#define KEYBOARD_IRQ    1
23#define MOUSE_IRQ       12
24#define IOPORT_BASE     0x60
25#define IOPORT_MAX      0x64
26
27/// The keyboard
28static struct lpc_kbd_t kbd;
29static bool init_complete;
30
31static void handle_input(lpc_kbd_status_t st)
32{
33    assert(lpc_kbd_status_obf_extract(st));
34
35    uint8_t val = lpc_kbd_input_rd(&kbd);
36
37    if (lpc_kbd_status_aobf_extract(st)) {
38        // we have mouse data
39        mouse_data(val);
40    } else {
41        // we have keyboard data
42        static bool extended;
43        if (val == 0xe0) {
44            assert(!extended);
45            extended = true;
46        } else {
47            key_event(val, extended);
48            extended = false;
49        }
50    }
51}
52
53static void interrupt_handler(void *arg)
54{
55    // ignore interrupts while in initialisation
56    if (!init_complete) {
57        return;
58    }
59
60    // read status register
61    lpc_kbd_status_t st = lpc_kbd_status_rd(&kbd);
62    if (lpc_kbd_status_obf_extract(st)) {
63        handle_input(st);
64    } else {
65        // debug_printf("we took an interrupt, but there's nothing to read?\n");
66    }
67}
68
69static void send_command(lpc_kbd_cmd_t cmd)
70{
71    // ensure input buffer and output buffer are empty
72    lpc_kbd_status_t st;
73    int nloop = 0;
74
75    do {
76        st = lpc_kbd_status_rd(&kbd);
77        if (lpc_kbd_status_obf_extract(st)) {
78            handle_input(st);
79        }
80        if (++nloop == 1000) {
81            debug_printf("stuck in send_command: obf=%u aobf=%u ibf=%u\n",
82                         lpc_kbd_status_obf_extract(st),
83                         lpc_kbd_status_aobf_extract(st),
84                         lpc_kbd_status_ibf_extract(st));
85        }
86    } while (lpc_kbd_status_obf_extract(st) || lpc_kbd_status_ibf_extract(st));
87
88    // send the command
89    lpc_kbd_command_wr(&kbd, cmd);
90}
91
92static void send_data(uint8_t val)
93{
94    // ensure input buffer and output buffer are empty
95    lpc_kbd_status_t st;
96    do {
97        st = lpc_kbd_status_rd(&kbd);
98    } while (lpc_kbd_status_obf_extract(st) || lpc_kbd_status_ibf_extract(st));
99
100    lpc_kbd_output_wr(&kbd, val);
101}
102
103static void init(void)
104{
105    lpc_kbd_status_t st;
106
107    if (init_complete) {
108        return;
109    }
110
111    // init mackerel state
112    lpc_kbd_initialize(&kbd, IOPORT_BASE);
113
114    // our ultimate goal here is merely to enable mouse and keyboard interrupts
115    // to achieve this, however, we do a little dance with the 8042 controller
116
117    // disable keyboard and mouse, since we share the channel, and don't
118    // want device data misinterpreted as the command byte
119    send_command(lpc_kbd_kbd_disable);
120    send_command(lpc_kbd_aux_disable);
121
122    // issue command to read command byte
123    send_command(lpc_kbd_rd_ccmd);
124
125    // wait for the buffer to fill
126    do {
127        st = lpc_kbd_status_rd(&kbd);
128    } while(!lpc_kbd_status_obf_extract(st));
129    // XXX: we are supposed to wait for 7us before reading
130    lpc_kbd_ccmd_t ccmd = lpc_kbd_input_rd(&kbd);
131
132    // check that we really succeeded in disabling things beforehand
133    assert(lpc_kbd_ccmd_kbd_dis_extract(ccmd));
134    assert(lpc_kbd_ccmd_aux_dis_extract(ccmd));
135
136    // enable both devices and both interrupts, and enable translation
137    ccmd = lpc_kbd_ccmd_kbd_dis_insert(ccmd, 0);
138    ccmd = lpc_kbd_ccmd_kbd_int_insert(ccmd, 1);
139    ccmd = lpc_kbd_ccmd_aux_dis_insert(ccmd, 0);
140    ccmd = lpc_kbd_ccmd_aux_int_insert(ccmd, 1);
141    ccmd = lpc_kbd_ccmd_kbd_xl_insert(ccmd, 1);
142
143    // write back the new command byte
144    send_command(lpc_kbd_wr_ccmd);
145    send_data(ccmd);
146
147    init_complete = true;
148
149    mouse_init();
150}
151
152void send_mouse_cmd(uint8_t cmd)
153{
154    assert(init_complete);
155
156    // issue command to write to aux port
157    send_command(lpc_kbd_write_aux);
158
159    // send the actual command
160    send_data(cmd);
161}
162
163int drv_init(void)
164{
165    int r = pci_client_connect();
166    assert(r == 0); // XXX
167
168    r = pci_register_legacy_driver_irq(init, IOPORT_BASE, IOPORT_MAX,
169                                       KEYBOARD_IRQ, interrupt_handler, NULL);
170    if (r != 0) {
171        return r;
172    }
173
174    r = pci_register_legacy_driver_irq(init, IOPORT_BASE, IOPORT_MAX,
175                                       MOUSE_IRQ, interrupt_handler, NULL);
176    if (r != 0) {
177        return r;
178    }
179
180    return 0;
181}
182