From 5afc2936b83c744e0a0d3ab92e7a98b2aa163df4 Mon Sep 17 00:00:00 2001
From: Patrick Rudolph <siro@das-labor.org>
Date: Mon, 6 Feb 2017 15:26:58 +0100
Subject: libpayload-x86: Add PS2 mouse driver

Make use of i8042 driver to add PS2 mouse driver support.

Tested on Lenovot T500.
The touchpad can be used to drive the mouse cursor.

Change-Id: I4be9c74467596b94d64dfa510824d8722108fe9c
Signed-off-by: Patrick Rudolph <siro@das-labor.org>
Reviewed-on: https://review.coreboot.org/18597
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
---
 payloads/libpayload/drivers/Makefile.inc   |   1 +
 payloads/libpayload/drivers/i8042/mouse.c  | 292 +++++++++++++++++++++++++++++
 payloads/libpayload/drivers/mouse_cursor.c |   4 +-
 3 files changed, 296 insertions(+), 1 deletion(-)
 create mode 100644 payloads/libpayload/drivers/i8042/mouse.c

(limited to 'payloads/libpayload/drivers')

diff --git a/payloads/libpayload/drivers/Makefile.inc b/payloads/libpayload/drivers/Makefile.inc
index 6eb9f0c4fc..2d70856f0a 100644
--- a/payloads/libpayload/drivers/Makefile.inc
+++ b/payloads/libpayload/drivers/Makefile.inc
@@ -39,6 +39,7 @@ libc-$(CONFIG_LP_IPQ806X_SERIAL_CONSOLE) += serial/ipq806x.c serial/serial.c
 libc-$(CONFIG_LP_IPQ40XX_SERIAL_CONSOLE) += serial/ipq40xx.c serial/serial.c
 libc-$(CONFIG_LP_BG4CD_SERIAL_CONSOLE) += serial/bg4cd.c serial/serial.c
 libc-$(CONFIG_LP_PC_KEYBOARD) += i8042/keyboard.c
+libc-$(CONFIG_LP_PC_MOUSE) += i8042/mouse.c
 libc-$(CONFIG_LP_PC_I8042) += i8042/i8042.c
 
 libc-$(CONFIG_LP_CBMEM_CONSOLE) += cbmem_console.c
diff --git a/payloads/libpayload/drivers/i8042/mouse.c b/payloads/libpayload/drivers/i8042/mouse.c
new file mode 100644
index 0000000000..21096d18c6
--- /dev/null
+++ b/payloads/libpayload/drivers/i8042/mouse.c
@@ -0,0 +1,292 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2017 Patrick Rudolph <siro@das-labor.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include <libpayload-config.h>
+#include <libpayload.h>
+
+static int x_axis;
+static int y_axis;
+static int z_axis;
+static u32 buttons;
+static u8 is_intellimouse;
+static u8 is_explorer_intellimouse;
+static u8 initialized;
+static unsigned char mouse_buf[4];
+static unsigned char mouse_buf_idx;
+
+static u8 mouse_cmd(unsigned char cmd)
+{
+	i8042_cmd(0xd4);
+
+	i8042_write_data(cmd);
+
+	return i8042_wait_read_aux() == 0xfa;
+}
+
+static u8 mouse_cmd_data(u8 cmd, u8 val)
+{
+	if (!mouse_cmd(cmd))
+		return 0;
+
+	return mouse_cmd(val);
+}
+
+/** Try to detect Microsoft Intelli mouse */
+static u8 mouse_is_intellimouse(void)
+{
+	/* Silence mouse. */
+	if (!mouse_cmd(0xf5))
+		return 0;
+
+	/* Set standard. */
+	if (!mouse_cmd(0xf6))
+		return 0;
+
+	/* Magic sequence. */
+	if (!mouse_cmd_data(0xf3, 0xc8))
+		return 0;
+	if (!mouse_cmd_data(0xf3, 0x64))
+		return 0;
+	if (!mouse_cmd_data(0xf3, 0x50))
+		return 0;
+
+	/* Get mouse id */
+	if (!mouse_cmd(0xf2))
+		return 0;
+
+	if (i8042_wait_read_aux() != 0x03)
+		return 0;
+
+	return 1;
+}
+
+/** Try to detect Microsoft Explorer mouse */
+static u8 mouse_is_intellimouse_explorer(void)
+{
+	/* Silence mouse. */
+	if (!mouse_cmd(0xf5))
+		return 0;
+
+	/* Set standard. */
+	if (!mouse_cmd(0xf6))
+		return 0;
+
+	/* Magic sequence. */
+	if (!mouse_cmd_data(0xf3, 0xc8))
+		return 0;
+	if (!mouse_cmd_data(0xf3, 0xc8))
+		return 0;
+	if (!mouse_cmd_data(0xf3, 0x50))
+		return 0;
+
+	/* Get mouse id */
+	if (!mouse_cmd(0xf2))
+		return 0;
+
+	if (i8042_wait_read_aux() != 4)
+		return 0;
+
+	return 1;
+}
+
+/** Decode temporary buffer
+ * Sanity check to prevent out of order decode.
+ * Decode PS/2 data.
+ * Supported devices:
+ *  Generic 3 button mouse
+ *  Microsoft Intelli mouse
+ *  Microsoft Explorer mouse
+ */
+static void mouse_decode(void)
+{
+	/* Buffer full check and sanity check */
+	if (is_intellimouse) {
+		if (mouse_buf_idx < 4)
+			return;
+		if ((mouse_buf[3] & 0x10) != (mouse_buf[3] & 0x08)) {
+			mouse_buf_idx = 0;
+			return;
+		}
+	} else if (is_explorer_intellimouse) {
+		if (mouse_buf_idx < 4)
+			return;
+		if (mouse_buf[3] & 0xc0) {
+			mouse_buf_idx = 0;
+			return;
+		}
+	} else {
+		if (mouse_buf_idx < 3)
+			return;
+	}
+
+	/* Common protocol */
+	x_axis += mouse_buf[1] ? mouse_buf[1] - ((mouse_buf[0] << 4) & 0x100) : 0;
+	y_axis += mouse_buf[2] ? ((mouse_buf[0] << 3) & 0x100) - mouse_buf[2] : 0;
+	buttons = mouse_buf[0] & 0x7;
+
+	/* Extended protocol */
+	if (is_intellimouse) {
+		z_axis += (mouse_buf[3] & 0x7) - (mouse_buf[3] & 0x08) ? 8 : 0;
+	} else if (is_explorer_intellimouse) {
+		z_axis += (mouse_buf[3] & 0x7) - (mouse_buf[3] & 0x08) ? 8 : 0;
+		buttons = (mouse_buf[0] & 0x7) | (mouse_buf[3] & 0x30) >> 1;
+	}
+
+	mouse_buf_idx = 0;
+}
+
+/** Insert data into internal temporary buffer. */
+static void insert_buf(unsigned char c)
+{
+	/* Validate input:
+	 * First byte shall have bit 3 set ! */
+	if (!mouse_buf_idx && !(c & 8))
+		return;
+
+	mouse_buf[mouse_buf_idx++] = c;
+}
+
+/** Probe i8042 for new aux data and try to decode it. */
+static void mouse_sample(void)
+{
+	if (!initialized)
+		return;
+
+	while (i8042_data_ready_aux()) {
+		insert_buf(i8042_read_data_aux());
+		mouse_decode();
+	}
+}
+
+/** Mouse cursor interface method
+ * Return and reset internal state.
+ */
+static void mouse_state(int *x, int *y, int *z, u32 *b)
+{
+	if (!initialized)
+		return;
+
+	mouse_sample();
+
+	if (x) {
+		*x = x_axis;
+		x_axis = 0;
+	}
+	if (y) {
+		*y = y_axis;
+		y_axis = 0;
+	}
+	if (z) {
+		*z = z_axis;
+		z_axis = 0;
+	}
+	if (b)
+		*b = buttons;
+}
+
+static struct mouse_cursor_input_driver curs = {
+	.get_state = mouse_state,
+	.input_type = CURSOR_INPUT_TYPE_PS2,
+};
+
+/** Probe for PS/2 mouse */
+void i8042_mouse_init(void)
+{
+	int ret;
+
+	/**
+	 * Initialize keyboard controller.
+	 * Might fail in case no AUX port or firmware disabled the AUX port.
+	 */
+	if (!i8042_probe() || !i8042_has_aux())
+		return;
+
+	/* Empty mouse buffer. */
+	while (i8042_data_ready_aux())
+		i8042_read_data_aux();
+
+	/* Enable mouse.
+	 * Documentation is unclear at this point.
+	 * Some recommend to wait for response, some claim there's none.
+	 * No response on Lenovo H8 EC.
+	 * Ignore it ... */
+	ret = i8042_cmd(0xa8);
+	if (ret == -1)
+		return;
+
+	/* Silence mouse. */
+	if (!mouse_cmd(0xf5))
+		return;
+
+	/* Read mouse id. */
+	if (!mouse_cmd(0xf2))
+		return;
+
+	ret = i8042_wait_read_aux();
+	if (ret)
+		return;
+
+	/* Get and enable features (scroll wheel and 5 buttons) */
+	is_intellimouse = mouse_is_intellimouse();
+	is_explorer_intellimouse = mouse_is_intellimouse_explorer();
+
+	/* Set defaults. */
+	if (!mouse_cmd(0xf6))
+		return;
+
+	/* Enable data transmission. */
+	if (!mouse_cmd(0xf4))
+		return;
+
+	initialized = 1;
+
+	/* Register mouse cursor driver */
+	mouse_cursor_add_input_driver(&curs);
+}
+
+/* Disable PS/2 mouse. */
+void i8042_mouse_disconnect(void)
+{
+	/* If 0x64 returns 0xff, then we have no keyboard
+	 * controller */
+	if (inb(0x64) == 0xFF || !initialized)
+		return;
+
+	/* Empty keyboard buffer */
+	while (i8042_data_ready_aux())
+		i8042_read_data_aux();
+
+	/* Disable mouse. */
+	i8042_cmd(0xa7);
+
+	initialized = 0;
+
+	/* Release keyboard controller driver */
+	i8042_close();
+}
diff --git a/payloads/libpayload/drivers/mouse_cursor.c b/payloads/libpayload/drivers/mouse_cursor.c
index 781845caaf..40c8934889 100644
--- a/payloads/libpayload/drivers/mouse_cursor.c
+++ b/payloads/libpayload/drivers/mouse_cursor.c
@@ -60,7 +60,9 @@ void mouse_cursor_add_input_driver(struct mouse_cursor_input_driver *const in)
 /** Init enabled mouse cursor drivers */
 void mouse_cursor_init(void)
 {
-/* FIXME */
+#if IS_ENABLED(CONFIG_LP_PC_MOUSE)
+	i8042_mouse_init();
+#endif
 }
 
 static u32 mouse_buttons;
-- 
cgit v1.2.3