summary refs log tree commit diff
path: root/drivers/ps2
diff options
context:
space:
mode:
authorJoel Challis <git@zvecr.com>2021-10-20 21:18:49 +0100
committerGitHub <noreply@github.com>2021-10-20 21:18:49 +0100
commitd4be4b67a251ecc046d857c5cd00cfb37c394ab7 (patch)
tree15f6dd05ec293081782a9b42a30e1a81b33b6aa0 /drivers/ps2
parent5500c428dd41348243e8a1695986b0da070e2ffa (diff)
Relocate PS2 code (#14895)
* Relocate ps2 protocol code

* clang

* Move makefile logic
Diffstat (limited to 'drivers/ps2')
-rw-r--r--drivers/ps2/ps2.h139
-rw-r--r--drivers/ps2/ps2_busywait.c187
-rw-r--r--drivers/ps2/ps2_interrupt.c340
-rw-r--r--drivers/ps2/ps2_io.h11
-rw-r--r--drivers/ps2/ps2_mouse.c270
-rw-r--r--drivers/ps2/ps2_mouse.h177
6 files changed, 1124 insertions, 0 deletions
diff --git a/drivers/ps2/ps2.h b/drivers/ps2/ps2.h
new file mode 100644
index 0000000000..f123192852
--- /dev/null
+++ b/drivers/ps2/ps2.h
@@ -0,0 +1,139 @@
+/*
+Copyright 2010,2011,2012,2013 Jun WAKO <wakojun@gmail.com>
+
+This software is licensed with a Modified BSD License.
+All of this is supposed to be Free Software, Open Source, DFSG-free,
+GPL-compatible, and OK to use in both free and proprietary applications.
+Additions and corrections to this file are welcome.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+* Neither the name of the copyright holders nor the names of
+  contributors may be used to endorse or promote products derived
+  from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include "wait.h"
+#include "ps2_io.h"
+#include "print.h"
+
+/*
+ * Primitive PS/2 Library for AVR
+ *
+ * PS/2 Resources
+ * --------------
+ * [1] The PS/2 Mouse/Keyboard Protocol
+ * http://www.computer-engineering.org/ps2protocol/
+ * Concise and thorough primer of PS/2 protocol.
+ *
+ * [2] Keyboard and Auxiliary Device Controller
+ * http://www.mcamafia.de/pdf/ibm_hitrc07.pdf
+ * Signal Timing and Format
+ *
+ * [3] Keyboards(101- and 102-key)
+ * http://www.mcamafia.de/pdf/ibm_hitrc11.pdf
+ * Keyboard Layout, Scan Code Set, POR, and Commands.
+ *
+ * [4] PS/2 Reference Manuals
+ * http://www.mcamafia.de/pdf/ibm_hitrc07.pdf
+ * Collection of IBM Personal System/2 documents.
+ *
+ * [5] TrackPoint Engineering Specifications for version 3E
+ * https://web.archive.org/web/20100526161812/http://wwwcssrv.almaden.ibm.com/trackpoint/download.html
+ */
+#define PS2_ACK 0xFA
+#define PS2_RESEND 0xFE
+#define PS2_SET_LED 0xED
+
+// TODO: error numbers
+#define PS2_ERR_NONE 0
+#define PS2_ERR_STARTBIT1 1
+#define PS2_ERR_STARTBIT2 2
+#define PS2_ERR_STARTBIT3 3
+#define PS2_ERR_PARITY 0x10
+#define PS2_ERR_NODATA 0x20
+
+#define PS2_LED_SCROLL_LOCK 0
+#define PS2_LED_NUM_LOCK 1
+#define PS2_LED_CAPS_LOCK 2
+
+extern uint8_t ps2_error;
+
+void    ps2_host_init(void);
+uint8_t ps2_host_send(uint8_t data);
+uint8_t ps2_host_recv_response(void);
+uint8_t ps2_host_recv(void);
+void    ps2_host_set_led(uint8_t usb_led);
+
+/*--------------------------------------------------------------------
+ * static functions
+ *------------------------------------------------------------------*/
+static inline uint16_t wait_clock_lo(uint16_t us) {
+    while (clock_in() && us) {
+        asm("");
+        wait_us(1);
+        us--;
+    }
+    return us;
+}
+static inline uint16_t wait_clock_hi(uint16_t us) {
+    while (!clock_in() && us) {
+        asm("");
+        wait_us(1);
+        us--;
+    }
+    return us;
+}
+static inline uint16_t wait_data_lo(uint16_t us) {
+    while (data_in() && us) {
+        asm("");
+        wait_us(1);
+        us--;
+    }
+    return us;
+}
+static inline uint16_t wait_data_hi(uint16_t us) {
+    while (!data_in() && us) {
+        asm("");
+        wait_us(1);
+        us--;
+    }
+    return us;
+}
+
+/* idle state that device can send */
+static inline void idle(void) {
+    clock_hi();
+    data_hi();
+}
+
+/* inhibit device to send */
+static inline void inhibit(void) {
+    clock_lo();
+    data_hi();
+}
diff --git a/drivers/ps2/ps2_busywait.c b/drivers/ps2/ps2_busywait.c
new file mode 100644
index 0000000000..983194eea8
--- /dev/null
+++ b/drivers/ps2/ps2_busywait.c
@@ -0,0 +1,187 @@
+/*
+Copyright 2010,2011,2012,2013 Jun WAKO <wakojun@gmail.com>
+
+This software is licensed with a Modified BSD License.
+All of this is supposed to be Free Software, Open Source, DFSG-free,
+GPL-compatible, and OK to use in both free and proprietary applications.
+Additions and corrections to this file are welcome.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+* Neither the name of the copyright holders nor the names of
+  contributors may be used to endorse or promote products derived
+  from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * PS/2 protocol busywait version
+ */
+
+#include <stdbool.h>
+#include "wait.h"
+#include "ps2.h"
+#include "ps2_io.h"
+#include "debug.h"
+
+#define WAIT(stat, us, err)     \
+    do {                        \
+        if (!wait_##stat(us)) { \
+            ps2_error = err;    \
+            goto ERROR;         \
+        }                       \
+    } while (0)
+
+uint8_t ps2_error = PS2_ERR_NONE;
+
+void ps2_host_init(void) {
+    clock_init();
+    data_init();
+
+    // POR(150-2000ms) plus BAT(300-500ms) may take 2.5sec([3]p.20)
+    wait_ms(2500);
+
+    inhibit();
+}
+
+uint8_t ps2_host_send(uint8_t data) {
+    bool parity = true;
+    ps2_error   = PS2_ERR_NONE;
+
+    /* terminate a transmission if we have */
+    inhibit();
+    wait_us(100);  // 100us [4]p.13, [5]p.50
+
+    /* 'Request to Send' and Start bit */
+    data_lo();
+    clock_hi();
+    WAIT(clock_lo, 10000, 10);  // 10ms [5]p.50
+
+    /* Data bit */
+    for (uint8_t i = 0; i < 8; i++) {
+        wait_us(15);
+        if (data & (1 << i)) {
+            parity = !parity;
+            data_hi();
+        } else {
+            data_lo();
+        }
+        WAIT(clock_hi, 50, 2);
+        WAIT(clock_lo, 50, 3);
+    }
+
+    /* Parity bit */
+    wait_us(15);
+    if (parity) {
+        data_hi();
+    } else {
+        data_lo();
+    }
+    WAIT(clock_hi, 50, 4);
+    WAIT(clock_lo, 50, 5);
+
+    /* Stop bit */
+    wait_us(15);
+    data_hi();
+
+    /* Ack */
+    WAIT(data_lo, 50, 6);
+    WAIT(clock_lo, 50, 7);
+
+    /* wait for idle state */
+    WAIT(clock_hi, 50, 8);
+    WAIT(data_hi, 50, 9);
+
+    inhibit();
+    return ps2_host_recv_response();
+ERROR:
+    inhibit();
+    return 0;
+}
+
+/* receive data when host want else inhibit communication */
+uint8_t ps2_host_recv_response(void) {
+    // Command may take 25ms/20ms at most([5]p.46, [3]p.21)
+    // 250 * 100us(wait for start bit in ps2_host_recv)
+    uint8_t data = 0;
+    uint8_t try
+        = 250;
+    do {
+        data = ps2_host_recv();
+    } while (try --&&ps2_error);
+    return data;
+}
+
+/* called after start bit comes */
+uint8_t ps2_host_recv(void) {
+    uint8_t data   = 0;
+    bool    parity = true;
+    ps2_error      = PS2_ERR_NONE;
+
+    /* release lines(idle state) */
+    idle();
+
+    /* start bit [1] */
+    WAIT(clock_lo, 100, 1);  // TODO: this is enough?
+    WAIT(data_lo, 1, 2);
+    WAIT(clock_hi, 50, 3);
+
+    /* data [2-9] */
+    for (uint8_t i = 0; i < 8; i++) {
+        WAIT(clock_lo, 50, 4);
+        if (data_in()) {
+            parity = !parity;
+            data |= (1 << i);
+        }
+        WAIT(clock_hi, 50, 5);
+    }
+
+    /* parity [10] */
+    WAIT(clock_lo, 50, 6);
+    if (data_in() != parity) {
+        ps2_error = PS2_ERR_PARITY;
+        goto ERROR;
+    }
+    WAIT(clock_hi, 50, 7);
+
+    /* stop bit [11] */
+    WAIT(clock_lo, 50, 8);
+    WAIT(data_hi, 1, 9);
+    WAIT(clock_hi, 50, 10);
+
+    inhibit();
+    return data;
+ERROR:
+    if (ps2_error > PS2_ERR_STARTBIT3) {
+        xprintf("x%02X\n", ps2_error);
+    }
+    inhibit();
+    return 0;
+}
+
+/* send LED state to keyboard */
+void ps2_host_set_led(uint8_t led) {
+    ps2_host_send(0xED);
+    ps2_host_send(led);
+}
diff --git a/drivers/ps2/ps2_interrupt.c b/drivers/ps2/ps2_interrupt.c
new file mode 100644
index 0000000000..70debd02f7
--- /dev/null
+++ b/drivers/ps2/ps2_interrupt.c
@@ -0,0 +1,340 @@
+/*
+Copyright 2010,2011,2012,2013 Jun WAKO <wakojun@gmail.com>
+
+This software is licensed with a Modified BSD License.
+All of this is supposed to be Free Software, Open Source, DFSG-free,
+GPL-compatible, and OK to use in both free and proprietary applications.
+Additions and corrections to this file are welcome.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+* Neither the name of the copyright holders nor the names of
+  contributors may be used to endorse or promote products derived
+  from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * PS/2 protocol Pin interrupt version
+ */
+
+#include <stdbool.h>
+
+#if defined(__AVR__)
+#    include <avr/interrupt.h>
+#elif defined(PROTOCOL_CHIBIOS)  // TODO: or STM32 ?
+// chibiOS headers
+#    include "ch.h"
+#    include "hal.h"
+#endif
+
+#include "ps2.h"
+#include "ps2_io.h"
+#include "print.h"
+#include "wait.h"
+
+#define WAIT(stat, us, err)     \
+    do {                        \
+        if (!wait_##stat(us)) { \
+            ps2_error = err;    \
+            goto ERROR;         \
+        }                       \
+    } while (0)
+
+uint8_t ps2_error = PS2_ERR_NONE;
+
+static inline uint8_t pbuf_dequeue(void);
+static inline void    pbuf_enqueue(uint8_t data);
+static inline bool    pbuf_has_data(void);
+static inline void    pbuf_clear(void);
+
+#if defined(PROTOCOL_CHIBIOS)
+void ps2_interrupt_service_routine(void);
+void palCallback(void *arg) { ps2_interrupt_service_routine(); }
+
+#    define PS2_INT_INIT()                                 \
+        { palSetLineMode(PS2_CLOCK_PIN, PAL_MODE_INPUT); } \
+        while (0)
+#    define PS2_INT_ON()                                                    \
+        {                                                                   \
+            palEnableLineEvent(PS2_CLOCK_PIN, PAL_EVENT_MODE_FALLING_EDGE); \
+            palSetLineCallback(PS2_CLOCK_PIN, palCallback, NULL);           \
+        }                                                                   \
+        while (0)
+#    define PS2_INT_OFF()                       \
+        { palDisableLineEvent(PS2_CLOCK_PIN); } \
+        while (0)
+#endif  // PROTOCOL_CHIBIOS
+
+void ps2_host_init(void) {
+    idle();
+    PS2_INT_INIT();
+    PS2_INT_ON();
+    // POR(150-2000ms) plus BAT(300-500ms) may take 2.5sec([3]p.20)
+    // wait_ms(2500);
+}
+
+uint8_t ps2_host_send(uint8_t data) {
+    bool parity = true;
+    ps2_error   = PS2_ERR_NONE;
+
+    PS2_INT_OFF();
+
+    /* terminate a transmission if we have */
+    inhibit();
+    wait_us(100);  // 100us [4]p.13, [5]p.50
+
+    /* 'Request to Send' and Start bit */
+    data_lo();
+    clock_hi();
+    WAIT(clock_lo, 10000, 10);  // 10ms [5]p.50
+
+    /* Data bit[2-9] */
+    for (uint8_t i = 0; i < 8; i++) {
+        if (data & (1 << i)) {
+            parity = !parity;
+            data_hi();
+        } else {
+            data_lo();
+        }
+        WAIT(clock_hi, 50, 2);
+        WAIT(clock_lo, 50, 3);
+    }
+
+    /* Parity bit */
+    wait_us(15);
+    if (parity) {
+        data_hi();
+    } else {
+        data_lo();
+    }
+    WAIT(clock_hi, 50, 4);
+    WAIT(clock_lo, 50, 5);
+
+    /* Stop bit */
+    wait_us(15);
+    data_hi();
+
+    /* Ack */
+    WAIT(data_lo, 50, 6);
+    WAIT(clock_lo, 50, 7);
+
+    /* wait for idle state */
+    WAIT(clock_hi, 50, 8);
+    WAIT(data_hi, 50, 9);
+
+    idle();
+    PS2_INT_ON();
+    return ps2_host_recv_response();
+ERROR:
+    idle();
+    PS2_INT_ON();
+    return 0;
+}
+
+uint8_t ps2_host_recv_response(void) {
+    // Command may take 25ms/20ms at most([5]p.46, [3]p.21)
+    uint8_t retry = 25;
+    while (retry-- && !pbuf_has_data()) {
+        wait_ms(1);
+    }
+    return pbuf_dequeue();
+}
+
+/* get data received by interrupt */
+uint8_t ps2_host_recv(void) {
+    if (pbuf_has_data()) {
+        ps2_error = PS2_ERR_NONE;
+        return pbuf_dequeue();
+    } else {
+        ps2_error = PS2_ERR_NODATA;
+        return 0;
+    }
+}
+
+void ps2_interrupt_service_routine(void) {
+    static enum {
+        INIT,
+        START,
+        BIT0,
+        BIT1,
+        BIT2,
+        BIT3,
+        BIT4,
+        BIT5,
+        BIT6,
+        BIT7,
+        PARITY,
+        STOP,
+    } state               = INIT;
+    static uint8_t data   = 0;
+    static uint8_t parity = 1;
+
+    // TODO: abort if elapse 100us from previous interrupt
+
+    // return unless falling edge
+    if (clock_in()) {
+        goto RETURN;
+    }
+
+    state++;
+    switch (state) {
+        case START:
+            if (data_in()) goto ERROR;
+            break;
+        case BIT0:
+        case BIT1:
+        case BIT2:
+        case BIT3:
+        case BIT4:
+        case BIT5:
+        case BIT6:
+        case BIT7:
+            data >>= 1;
+            if (data_in()) {
+                data |= 0x80;
+                parity++;
+            }
+            break;
+        case PARITY:
+            if (data_in()) {
+                if (!(parity & 0x01)) goto ERROR;
+            } else {
+                if (parity & 0x01) goto ERROR;
+            }
+            break;
+        case STOP:
+            if (!data_in()) goto ERROR;
+            pbuf_enqueue(data);
+            goto DONE;
+            break;
+        default:
+            goto ERROR;
+    }
+    goto RETURN;
+ERROR:
+    ps2_error = state;
+DONE:
+    state  = INIT;
+    data   = 0;
+    parity = 1;
+RETURN:
+    return;
+}
+
+#if defined(__AVR__)
+ISR(PS2_INT_VECT) { ps2_interrupt_service_routine(); }
+#endif
+
+/* send LED state to keyboard */
+void ps2_host_set_led(uint8_t led) {
+    ps2_host_send(0xED);
+    ps2_host_send(led);
+}
+
+/*--------------------------------------------------------------------
+ * Ring buffer to store scan codes from keyboard
+ *------------------------------------------------------------------*/
+#define PBUF_SIZE 32
+static uint8_t     pbuf[PBUF_SIZE];
+static uint8_t     pbuf_head = 0;
+static uint8_t     pbuf_tail = 0;
+static inline void pbuf_enqueue(uint8_t data) {
+#if defined(__AVR__)
+    uint8_t sreg = SREG;
+    cli();
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysLockFromISR();
+#endif
+
+    uint8_t next = (pbuf_head + 1) % PBUF_SIZE;
+    if (next != pbuf_tail) {
+        pbuf[pbuf_head] = data;
+        pbuf_head       = next;
+    } else {
+        print("pbuf: full\n");
+    }
+
+#if defined(__AVR__)
+    SREG = sreg;
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysUnlockFromISR();
+#endif
+}
+static inline uint8_t pbuf_dequeue(void) {
+    uint8_t val = 0;
+
+#if defined(__AVR__)
+    uint8_t sreg = SREG;
+    cli();
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysLock();
+#endif
+
+    if (pbuf_head != pbuf_tail) {
+        val       = pbuf[pbuf_tail];
+        pbuf_tail = (pbuf_tail + 1) % PBUF_SIZE;
+    }
+
+#if defined(__AVR__)
+    SREG = sreg;
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysUnlock();
+#endif
+
+    return val;
+}
+static inline bool pbuf_has_data(void) {
+#if defined(__AVR__)
+    uint8_t sreg = SREG;
+    cli();
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysLock();
+#endif
+
+    bool has_data = (pbuf_head != pbuf_tail);
+
+#if defined(__AVR__)
+    SREG = sreg;
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysUnlock();
+#endif
+    return has_data;
+}
+static inline void pbuf_clear(void) {
+#if defined(__AVR__)
+    uint8_t sreg = SREG;
+    cli();
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysLock();
+#endif
+
+    pbuf_head = pbuf_tail = 0;
+
+#if defined(__AVR__)
+    SREG = sreg;
+#elif defined(PROTOCOL_CHIBIOS)
+    chSysUnlock();
+#endif
+}
diff --git a/drivers/ps2/ps2_io.h b/drivers/ps2/ps2_io.h
new file mode 100644
index 0000000000..de93cb7a39
--- /dev/null
+++ b/drivers/ps2/ps2_io.h
@@ -0,0 +1,11 @@
+#pragma once
+
+void clock_init(void);
+void clock_lo(void);
+void clock_hi(void);
+bool clock_in(void);
+
+void data_init(void);
+void data_lo(void);
+void data_hi(void);
+bool data_in(void);
diff --git a/drivers/ps2/ps2_mouse.c b/drivers/ps2/ps2_mouse.c
new file mode 100644
index 0000000000..8a6668b410
--- /dev/null
+++ b/drivers/ps2/ps2_mouse.c
@@ -0,0 +1,270 @@
+/*
+Copyright 2011,2013 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdbool.h>
+#include "ps2_mouse.h"
+#include "wait.h"
+#include "gpio.h"
+#include "host.h"
+#include "timer.h"
+#include "print.h"
+#include "report.h"
+#include "debug.h"
+#include "ps2.h"
+
+/* ============================= MACROS ============================ */
+
+static report_mouse_t mouse_report = {};
+
+static inline void ps2_mouse_print_report(report_mouse_t *mouse_report);
+static inline void ps2_mouse_convert_report_to_hid(report_mouse_t *mouse_report);
+static inline void ps2_mouse_clear_report(report_mouse_t *mouse_report);
+static inline void ps2_mouse_enable_scrolling(void);
+static inline void ps2_mouse_scroll_button_task(report_mouse_t *mouse_report);
+
+/* ============================= IMPLEMENTATION ============================ */
+
+/* supports only 3 button mouse at this time */
+void ps2_mouse_init(void) {
+    ps2_host_init();
+
+    wait_ms(PS2_MOUSE_INIT_DELAY);  // wait for powering up
+
+    PS2_MOUSE_SEND(PS2_MOUSE_RESET, "ps2_mouse_init: sending reset");
+
+    PS2_MOUSE_RECEIVE("ps2_mouse_init: read BAT");
+    PS2_MOUSE_RECEIVE("ps2_mouse_init: read DevID");
+
+#ifdef PS2_MOUSE_USE_REMOTE_MODE
+    ps2_mouse_set_remote_mode();
+#else
+    ps2_mouse_enable_data_reporting();
+#endif
+
+#ifdef PS2_MOUSE_ENABLE_SCROLLING
+    ps2_mouse_enable_scrolling();
+#endif
+
+#ifdef PS2_MOUSE_USE_2_1_SCALING
+    ps2_mouse_set_scaling_2_1();
+#endif
+
+    ps2_mouse_init_user();
+}
+
+__attribute__((weak)) void ps2_mouse_init_user(void) {}
+
+__attribute__((weak)) void ps2_mouse_moved_user(report_mouse_t *mouse_report) {}
+
+void ps2_mouse_task(void) {
+    static uint8_t buttons_prev = 0;
+    extern int     tp_buttons;
+
+    /* receives packet from mouse */
+    uint8_t rcv;
+    rcv = ps2_host_send(PS2_MOUSE_READ_DATA);
+    if (rcv == PS2_ACK) {
+        mouse_report.buttons = ps2_host_recv_response() | tp_buttons;
+        mouse_report.x       = ps2_host_recv_response() * PS2_MOUSE_X_MULTIPLIER;
+        mouse_report.y       = ps2_host_recv_response() * PS2_MOUSE_Y_MULTIPLIER;
+#ifdef PS2_MOUSE_ENABLE_SCROLLING
+        mouse_report.v = -(ps2_host_recv_response() & PS2_MOUSE_SCROLL_MASK) * PS2_MOUSE_V_MULTIPLIER;
+#endif
+    } else {
+        if (debug_mouse) print("ps2_mouse: fail to get mouse packet\n");
+        return;
+    }
+
+    /* if mouse moves or buttons state changes */
+    if (mouse_report.x || mouse_report.y || mouse_report.v || ((mouse_report.buttons ^ buttons_prev) & PS2_MOUSE_BTN_MASK)) {
+#ifdef PS2_MOUSE_DEBUG_RAW
+        // Used to debug raw ps2 bytes from mouse
+        ps2_mouse_print_report(&mouse_report);
+#endif
+        buttons_prev = mouse_report.buttons;
+        ps2_mouse_convert_report_to_hid(&mouse_report);
+#if PS2_MOUSE_SCROLL_BTN_MASK
+        ps2_mouse_scroll_button_task(&mouse_report);
+#endif
+        if (mouse_report.x || mouse_report.y || mouse_report.v) {
+            ps2_mouse_moved_user(&mouse_report);
+        }
+#ifdef PS2_MOUSE_DEBUG_HID
+        // Used to debug the bytes sent to the host
+        ps2_mouse_print_report(&mouse_report);
+#endif
+        host_mouse_send(&mouse_report);
+    }
+
+    ps2_mouse_clear_report(&mouse_report);
+}
+
+void ps2_mouse_disable_data_reporting(void) { PS2_MOUSE_SEND(PS2_MOUSE_DISABLE_DATA_REPORTING, "ps2 mouse disable data reporting"); }
+
+void ps2_mouse_enable_data_reporting(void) { PS2_MOUSE_SEND(PS2_MOUSE_ENABLE_DATA_REPORTING, "ps2 mouse enable data reporting"); }
+
+void ps2_mouse_set_remote_mode(void) {
+    PS2_MOUSE_SEND_SAFE(PS2_MOUSE_SET_REMOTE_MODE, "ps2 mouse set remote mode");
+    ps2_mouse_mode = PS2_MOUSE_REMOTE_MODE;
+}
+
+void ps2_mouse_set_stream_mode(void) {
+    PS2_MOUSE_SEND_SAFE(PS2_MOUSE_SET_STREAM_MODE, "ps2 mouse set stream mode");
+    ps2_mouse_mode = PS2_MOUSE_STREAM_MODE;
+}
+
+void ps2_mouse_set_scaling_2_1(void) { PS2_MOUSE_SEND_SAFE(PS2_MOUSE_SET_SCALING_2_1, "ps2 mouse set scaling 2:1"); }
+
+void ps2_mouse_set_scaling_1_1(void) { PS2_MOUSE_SEND_SAFE(PS2_MOUSE_SET_SCALING_1_1, "ps2 mouse set scaling 1:1"); }
+
+void ps2_mouse_set_resolution(ps2_mouse_resolution_t resolution) { PS2_MOUSE_SET_SAFE(PS2_MOUSE_SET_RESOLUTION, resolution, "ps2 mouse set resolution"); }
+
+void ps2_mouse_set_sample_rate(ps2_mouse_sample_rate_t sample_rate) { PS2_MOUSE_SET_SAFE(PS2_MOUSE_SET_SAMPLE_RATE, sample_rate, "ps2 mouse set sample rate"); }
+
+/* ============================= HELPERS ============================ */
+
+#define X_IS_NEG (mouse_report->buttons & (1 << PS2_MOUSE_X_SIGN))
+#define Y_IS_NEG (mouse_report->buttons & (1 << PS2_MOUSE_Y_SIGN))
+#define X_IS_OVF (mouse_report->buttons & (1 << PS2_MOUSE_X_OVFLW))
+#define Y_IS_OVF (mouse_report->buttons & (1 << PS2_MOUSE_Y_OVFLW))
+static inline void ps2_mouse_convert_report_to_hid(report_mouse_t *mouse_report) {
+    // PS/2 mouse data is '9-bit integer'(-256 to 255) which is comprised of sign-bit and 8-bit value.
+    // bit: 8    7 ... 0
+    //      sign \8-bit/
+    //
+    // Meanwhile USB HID mouse indicates 8bit data(-127 to 127), note that -128 is not used.
+    //
+    // This converts PS/2 data into HID value. Use only -127-127 out of PS/2 9-bit.
+    mouse_report->x = X_IS_NEG ? ((!X_IS_OVF && -127 <= mouse_report->x && mouse_report->x <= -1) ? mouse_report->x : -127) : ((!X_IS_OVF && 0 <= mouse_report->x && mouse_report->x <= 127) ? mouse_report->x : 127);
+    mouse_report->y = Y_IS_NEG ? ((!Y_IS_OVF && -127 <= mouse_report->y && mouse_report->y <= -1) ? mouse_report->y : -127) : ((!Y_IS_OVF && 0 <= mouse_report->y && mouse_report->y <= 127) ? mouse_report->y : 127);
+
+#ifdef PS2_MOUSE_INVERT_BUTTONS
+    // swap left & right buttons
+    uint8_t needs_left    = mouse_report->buttons & PS2_MOUSE_BTN_RIGHT;
+    uint8_t needs_right   = mouse_report->buttons & PS2_MOUSE_BTN_LEFT;
+    mouse_report->buttons = (mouse_report->buttons & ~(PS2_MOUSE_BTN_MASK)) | (needs_left ? PS2_MOUSE_BTN_LEFT : 0) | (needs_right ? PS2_MOUSE_BTN_RIGHT : 0);
+#else
+    // remove sign and overflow flags
+    mouse_report->buttons &= PS2_MOUSE_BTN_MASK;
+#endif
+
+#ifdef PS2_MOUSE_INVERT_X
+    mouse_report->x = -mouse_report->x;
+#endif
+#ifndef PS2_MOUSE_INVERT_Y  // NOTE if not!
+    // invert coordinate of y to conform to USB HID mouse
+    mouse_report->y = -mouse_report->y;
+#endif
+
+#ifdef PS2_MOUSE_ROTATE
+    int8_t x = mouse_report->x;
+    int8_t y = mouse_report->y;
+#    if PS2_MOUSE_ROTATE == 90
+    mouse_report->x = y;
+    mouse_report->y = -x;
+#    elif PS2_MOUSE_ROTATE == 180
+    mouse_report->x = -x;
+    mouse_report->y = -y;
+#    elif PS2_MOUSE_ROTATE == 270
+    mouse_report->x = -y;
+    mouse_report->y = x;
+#    endif
+#endif
+}
+
+static inline void ps2_mouse_clear_report(report_mouse_t *mouse_report) {
+    mouse_report->x       = 0;
+    mouse_report->y       = 0;
+    mouse_report->v       = 0;
+    mouse_report->h       = 0;
+    mouse_report->buttons = 0;
+}
+
+static inline void ps2_mouse_print_report(report_mouse_t *mouse_report) {
+    if (!debug_mouse) return;
+    print("ps2_mouse: [");
+    print_hex8(mouse_report->buttons);
+    print("|");
+    print_hex8((uint8_t)mouse_report->x);
+    print(" ");
+    print_hex8((uint8_t)mouse_report->y);
+    print(" ");
+    print_hex8((uint8_t)mouse_report->v);
+    print(" ");
+    print_hex8((uint8_t)mouse_report->h);
+    print("]\n");
+}
+
+static inline void ps2_mouse_enable_scrolling(void) {
+    PS2_MOUSE_SEND(PS2_MOUSE_SET_SAMPLE_RATE, "Initiaing scroll wheel enable: Set sample rate");
+    PS2_MOUSE_SEND(200, "200");
+    PS2_MOUSE_SEND(PS2_MOUSE_SET_SAMPLE_RATE, "Set sample rate");
+    PS2_MOUSE_SEND(100, "100");
+    PS2_MOUSE_SEND(PS2_MOUSE_SET_SAMPLE_RATE, "Set sample rate");
+    PS2_MOUSE_SEND(80, "80");
+    PS2_MOUSE_SEND(PS2_MOUSE_GET_DEVICE_ID, "Finished enabling scroll wheel");
+    wait_ms(20);
+}
+
+#define PRESS_SCROLL_BUTTONS mouse_report->buttons |= (PS2_MOUSE_SCROLL_BTN_MASK)
+#define RELEASE_SCROLL_BUTTONS mouse_report->buttons &= ~(PS2_MOUSE_SCROLL_BTN_MASK)
+static inline void ps2_mouse_scroll_button_task(report_mouse_t *mouse_report) {
+    static enum {
+        SCROLL_NONE,
+        SCROLL_BTN,
+        SCROLL_SENT,
+    } scroll_state                     = SCROLL_NONE;
+    static uint16_t scroll_button_time = 0;
+
+    if (PS2_MOUSE_SCROLL_BTN_MASK == (mouse_report->buttons & (PS2_MOUSE_SCROLL_BTN_MASK))) {
+        // All scroll buttons are pressed
+
+        if (scroll_state == SCROLL_NONE) {
+            scroll_button_time = timer_read();
+            scroll_state       = SCROLL_BTN;
+        }
+
+        // If the mouse has moved, update the report to scroll instead of move the mouse
+        if (mouse_report->x || mouse_report->y) {
+            scroll_state    = SCROLL_SENT;
+            mouse_report->v = -mouse_report->y / (PS2_MOUSE_SCROLL_DIVISOR_V);
+            mouse_report->h = mouse_report->x / (PS2_MOUSE_SCROLL_DIVISOR_H);
+            mouse_report->x = 0;
+            mouse_report->y = 0;
+#ifdef PS2_MOUSE_INVERT_H
+            mouse_report->h = -mouse_report->h;
+#endif
+#ifdef PS2_MOUSE_INVERT_V
+            mouse_report->v = -mouse_report->v;
+#endif
+        }
+    } else if (0 == (PS2_MOUSE_SCROLL_BTN_MASK & mouse_report->buttons)) {
+        // None of the scroll buttons are pressed
+
+#if PS2_MOUSE_SCROLL_BTN_SEND
+        if (scroll_state == SCROLL_BTN && timer_elapsed(scroll_button_time) < PS2_MOUSE_SCROLL_BTN_SEND) {
+            PRESS_SCROLL_BUTTONS;
+            host_mouse_send(mouse_report);
+            wait_ms(100);
+            RELEASE_SCROLL_BUTTONS;
+        }
+#endif
+        scroll_state = SCROLL_NONE;
+    }
+
+    RELEASE_SCROLL_BUTTONS;
+}
diff --git a/drivers/ps2/ps2_mouse.h b/drivers/ps2/ps2_mouse.h
new file mode 100644
index 0000000000..c97c6c893a
--- /dev/null
+++ b/drivers/ps2/ps2_mouse.h
@@ -0,0 +1,177 @@
+/*
+Copyright 2011 Jun Wako <wakojun@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include "debug.h"
+#include "report.h"
+
+#define PS2_MOUSE_SEND(command, message)                                                \
+    do {                                                                                \
+        __attribute__((unused)) uint8_t rcv = ps2_host_send(command);                   \
+        if (debug_mouse) {                                                              \
+            print((message));                                                           \
+            xprintf(" command: %X, result: %X, error: %X \n", command, rcv, ps2_error); \
+        }                                                                               \
+    } while (0)
+
+#define PS2_MOUSE_SEND_SAFE(command, message)          \
+    do {                                               \
+        if (PS2_MOUSE_STREAM_MODE == ps2_mouse_mode) { \
+            ps2_mouse_disable_data_reporting();        \
+        }                                              \
+        PS2_MOUSE_SEND(command, message);              \
+        if (PS2_MOUSE_STREAM_MODE == ps2_mouse_mode) { \
+            ps2_mouse_enable_data_reporting();         \
+        }                                              \
+    } while (0)
+
+#define PS2_MOUSE_SET_SAFE(command, value, message)    \
+    do {                                               \
+        if (PS2_MOUSE_STREAM_MODE == ps2_mouse_mode) { \
+            ps2_mouse_disable_data_reporting();        \
+        }                                              \
+        PS2_MOUSE_SEND(command, message);              \
+        PS2_MOUSE_SEND(value, "Sending value");        \
+        if (PS2_MOUSE_STREAM_MODE == ps2_mouse_mode) { \
+            ps2_mouse_enable_data_reporting();         \
+        }                                              \
+    } while (0)
+
+#define PS2_MOUSE_RECEIVE(message)                                      \
+    do {                                                                \
+        __attribute__((unused)) uint8_t rcv = ps2_host_recv_response(); \
+        if (debug_mouse) {                                              \
+            print((message));                                           \
+            xprintf(" result: %X, error: %X \n", rcv, ps2_error);       \
+        }                                                               \
+    } while (0)
+
+__attribute__((unused)) static enum ps2_mouse_mode_e {
+    PS2_MOUSE_STREAM_MODE,
+    PS2_MOUSE_REMOTE_MODE,
+} ps2_mouse_mode = PS2_MOUSE_STREAM_MODE;
+
+/*
+ * Data format:
+ * byte|7       6       5       4       3       2       1       0
+ * ----+----------------------------------------------------------------
+ *    0|[Yovflw][Xovflw][Ysign ][Xsign ][ 1    ][Middle][Right ][Left  ]
+ *    1|[                    X movement(0-255)                         ]
+ *    2|[                    Y movement(0-255)                         ]
+ */
+#define PS2_MOUSE_BTN_MASK 0x07
+#define PS2_MOUSE_BTN_LEFT 0
+#define PS2_MOUSE_BTN_RIGHT 1
+#define PS2_MOUSE_BTN_MIDDLE 2
+#define PS2_MOUSE_X_SIGN 4
+#define PS2_MOUSE_Y_SIGN 5
+#define PS2_MOUSE_X_OVFLW 6
+#define PS2_MOUSE_Y_OVFLW 7
+
+/* mouse button to start scrolling; set 0 to disable scroll */
+#ifndef PS2_MOUSE_SCROLL_BTN_MASK
+#    define PS2_MOUSE_SCROLL_BTN_MASK (1 << PS2_MOUSE_BTN_MIDDLE)
+#endif
+/* send button event when button is released within this value(ms); set 0 to disable  */
+#ifndef PS2_MOUSE_SCROLL_BTN_SEND
+#    define PS2_MOUSE_SCROLL_BTN_SEND 300
+#endif
+/* divide virtical and horizontal mouse move by this to convert to scroll move */
+#ifndef PS2_MOUSE_SCROLL_DIVISOR_V
+#    define PS2_MOUSE_SCROLL_DIVISOR_V 2
+#endif
+#ifndef PS2_MOUSE_SCROLL_DIVISOR_H
+#    define PS2_MOUSE_SCROLL_DIVISOR_H 2
+#endif
+/* multiply reported mouse values by these */
+#ifndef PS2_MOUSE_X_MULTIPLIER
+#    define PS2_MOUSE_X_MULTIPLIER 1
+#endif
+#ifndef PS2_MOUSE_Y_MULTIPLIER
+#    define PS2_MOUSE_Y_MULTIPLIER 1
+#endif
+#ifndef PS2_MOUSE_V_MULTIPLIER
+#    define PS2_MOUSE_V_MULTIPLIER 1
+#endif
+/* For some mice this will need to be 0x0F */
+#ifndef PS2_MOUSE_SCROLL_MASK
+#    define PS2_MOUSE_SCROLL_MASK 0xFF
+#endif
+#ifndef PS2_MOUSE_INIT_DELAY
+#    define PS2_MOUSE_INIT_DELAY 1000
+#endif
+
+enum ps2_mouse_command_e {
+    PS2_MOUSE_RESET                  = 0xFF,
+    PS2_MOUSE_RESEND                 = 0xFE,
+    PS2_MOSUE_SET_DEFAULTS           = 0xF6,
+    PS2_MOUSE_DISABLE_DATA_REPORTING = 0xF5,
+    PS2_MOUSE_ENABLE_DATA_REPORTING  = 0xF4,
+    PS2_MOUSE_SET_SAMPLE_RATE        = 0xF3,
+    PS2_MOUSE_GET_DEVICE_ID          = 0xF2,
+    PS2_MOUSE_SET_REMOTE_MODE        = 0xF0,
+    PS2_MOUSE_SET_WRAP_MODE          = 0xEC,
+    PS2_MOUSE_READ_DATA              = 0xEB,
+    PS2_MOUSE_SET_STREAM_MODE        = 0xEA,
+    PS2_MOUSE_STATUS_REQUEST         = 0xE9,
+    PS2_MOUSE_SET_RESOLUTION         = 0xE8,
+    PS2_MOUSE_SET_SCALING_2_1        = 0xE7,
+    PS2_MOUSE_SET_SCALING_1_1        = 0xE6,
+};
+
+typedef enum ps2_mouse_resolution_e {
+    PS2_MOUSE_1_COUNT_MM,
+    PS2_MOUSE_2_COUNT_MM,
+    PS2_MOUSE_4_COUNT_MM,
+    PS2_MOUSE_8_COUNT_MM,
+} ps2_mouse_resolution_t;
+
+typedef enum ps2_mouse_sample_rate_e {
+    PS2_MOUSE_10_SAMPLES_SEC  = 10,
+    PS2_MOUSE_20_SAMPLES_SEC  = 20,
+    PS2_MOUSE_40_SAMPLES_SEC  = 40,
+    PS2_MOUSE_60_SAMPLES_SEC  = 60,
+    PS2_MOUSE_80_SAMPLES_SEC  = 80,
+    PS2_MOUSE_100_SAMPLES_SEC = 100,
+    PS2_MOUSE_200_SAMPLES_SEC = 200,
+} ps2_mouse_sample_rate_t;
+
+void ps2_mouse_init(void);
+
+void ps2_mouse_init_user(void);
+
+void ps2_mouse_task(void);
+
+void ps2_mouse_disable_data_reporting(void);
+
+void ps2_mouse_enable_data_reporting(void);
+
+void ps2_mouse_set_remote_mode(void);
+
+void ps2_mouse_set_stream_mode(void);
+
+void ps2_mouse_set_scaling_2_1(void);
+
+void ps2_mouse_set_scaling_1_1(void);
+
+void ps2_mouse_set_resolution(ps2_mouse_resolution_t resolution);
+
+void ps2_mouse_set_sample_rate(ps2_mouse_sample_rate_t sample_rate);
+
+void ps2_mouse_moved_user(report_mouse_t *mouse_report);