From 6c176113bbdd598231ec47d161d4c3714997169b Mon Sep 17 00:00:00 2001
From: Michael Gmelin <grembo@FreeBSD.org>
Date: Sat, 14 Feb 2015 22:12:17 +0000
Subject: [PATCH] Quirk based support of Chromebook keyboard found in Acer C720

This probably supports other devices based on SeaBIOS, which need
to be added to the smbios based quirks table.

The functionality has been ported from DragonFlyBSD and adapted
to FreeBSD's more general purpose environment.

Devices not covered by a quirk shouldn't be affected at all. Thanks
to jhb and kostikbel for reviewing the code.

Reviewed by:	kostikbel, jhb
Approved by:	jhb, kostikbel
Differential Revision: https://reviews.freebsd.org/D1802
---
 sys/dev/atkbdc/atkbd.c     | 92 ++++++++++++++++++++++++++------------
 sys/dev/atkbdc/atkbdc.c    | 39 +++++++++++++++-
 sys/dev/atkbdc/atkbdcreg.h |  5 +++
 sys/dev/atkbdc/psm.c       | 22 ++++++---
 4 files changed, 123 insertions(+), 35 deletions(-)

diff --git a/sys/dev/atkbdc/atkbd.c b/sys/dev/atkbdc/atkbd.c
index 0d2b44ba5182..d93c1c6da01b 100644
--- a/sys/dev/atkbdc/atkbd.c
+++ b/sys/dev/atkbdc/atkbd.c
@@ -77,6 +77,10 @@ typedef struct atkbd_state {
 
 static void		atkbd_timeout(void *arg);
 static void		atkbd_shutdown_final(void *v);
+static int		atkbd_reset(KBDC kbdc, int flags, int c);
+
+#define HAS_QUIRK(p, q)		(((atkbdc_softc_t *)(p))->quirks & q)
+#define ALLOW_DISABLE_KBD(kbdc)	!HAS_QUIRK(kbdc, KBDC_QUIRK_KEEP_ACTIVATED)
 
 int
 atkbd_probe_unit(device_t dev, int irq, int flags)
@@ -1095,6 +1099,39 @@ atkbd_shutdown_final(void *v)
 #endif
 }
 
+static int
+atkbd_reset(KBDC kbdc, int flags, int c)
+{
+	/* reset keyboard hardware */
+	if (!(flags & KB_CONF_NO_RESET) && !reset_kbd(kbdc)) {
+		/*
+		 * KEYBOARD ERROR
+		 * Keyboard reset may fail either because the keyboard
+		 * doen't exist, or because the keyboard doesn't pass
+		 * the self-test, or the keyboard controller on the
+		 * motherboard and the keyboard somehow fail to shake hands.
+		 * It is just possible, particularly in the last case,
+		 * that the keyboard controller may be left in a hung state.
+		 * test_controller() and test_kbd_port() appear to bring
+		 * the keyboard controller back (I don't know why and how,
+		 * though.)
+		 */
+		empty_both_buffers(kbdc, 10);
+		test_controller(kbdc);
+		test_kbd_port(kbdc);
+		/*
+		 * We could disable the keyboard port and interrupt... but, 
+		 * the keyboard may still exist (see above). 
+		 */
+		set_controller_command_byte(kbdc,
+		    ALLOW_DISABLE_KBD(kbdc) ? 0xff : KBD_KBD_CONTROL_BITS, c);
+		if (bootverbose)
+			printf("atkbd: failed to reset the keyboard.\n");
+		return (EIO);
+	}
+	return (0);
+}
+
 /* local functions */
 
 static int
@@ -1250,13 +1287,14 @@ probe_keyboard(KBDC kbdc, int flags)
 		kbdc_set_device_mask(kbdc, m | KBD_KBD_CONTROL_BITS);
 	} else {
 		/* try to restore the command byte as before */
-		set_controller_command_byte(kbdc, 0xff, c);
+		set_controller_command_byte(kbdc,
+		    ALLOW_DISABLE_KBD(kbdc) ? 0xff : KBD_KBD_CONTROL_BITS, c);
 		kbdc_set_device_mask(kbdc, m);
 	}
 #endif
 
 	kbdc_lock(kbdc, FALSE);
-	return err;
+	return (HAS_QUIRK(kbdc, KBDC_QUIRK_IGNORE_PROBE_RESULT) ? 0 : err);
 }
 
 static int
@@ -1299,6 +1337,12 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 		return EIO;
 	}
 
+	if (HAS_QUIRK(kbdc, KBDC_QUIRK_RESET_AFTER_PROBE) &&
+	    atkbd_reset(kbdc, flags, c)) {
+		kbdc_lock(kbdc, FALSE);
+		return EIO;
+	}
+
 	/* 
 	 * Check if we have an XT keyboard before we attempt to reset it. 
 	 * The procedure assumes that the keyboard and the controller have 
@@ -1343,31 +1387,9 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 	if (bootverbose)
 		printf("atkbd: keyboard ID 0x%x (%d)\n", id, *type);
 
-	/* reset keyboard hardware */
-	if (!(flags & KB_CONF_NO_RESET) && !reset_kbd(kbdc)) {
-		/*
-		 * KEYBOARD ERROR
-		 * Keyboard reset may fail either because the keyboard
-		 * doen't exist, or because the keyboard doesn't pass
-		 * the self-test, or the keyboard controller on the
-		 * motherboard and the keyboard somehow fail to shake hands.
-		 * It is just possible, particularly in the last case,
-		 * that the keyboard controller may be left in a hung state.
-		 * test_controller() and test_kbd_port() appear to bring
-		 * the keyboard controller back (I don't know why and how,
-		 * though.)
-		 */
-		empty_both_buffers(kbdc, 10);
-		test_controller(kbdc);
-		test_kbd_port(kbdc);
-		/*
-		 * We could disable the keyboard port and interrupt... but, 
-		 * the keyboard may still exist (see above). 
-		 */
-		set_controller_command_byte(kbdc, 0xff, c);
+	if (!HAS_QUIRK(kbdc, KBDC_QUIRK_RESET_AFTER_PROBE) &&
+	    atkbd_reset(kbdc, flags, c)) {
 		kbdc_lock(kbdc, FALSE);
-		if (bootverbose)
-			printf("atkbd: failed to reset the keyboard.\n");
 		return EIO;
 	}
 
@@ -1387,7 +1409,8 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 			 * The XT kbd isn't usable unless the proper scan
 			 * code set is selected. 
 			 */
-			set_controller_command_byte(kbdc, 0xff, c);
+			set_controller_command_byte(kbdc, ALLOW_DISABLE_KBD(kbdc)
+			    ? 0xff : KBD_KBD_CONTROL_BITS, c);
 			kbdc_lock(kbdc, FALSE);
 			printf("atkbd: unable to set the XT keyboard mode.\n");
 			return EIO;
@@ -1402,6 +1425,17 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 	c |= KBD_TRANSLATION;
 #endif
 
+	/*
+	 * Some keyboards require a SETLEDS command to be sent after
+	 * the reset command before they will send keystrokes to us
+	 */
+	if (HAS_QUIRK(kbdc, KBDC_QUIRK_SETLEDS_ON_INIT) &&
+	    send_kbd_command_and_data(kbdc, KBDC_SET_LEDS, 0) != KBD_ACK) {
+		printf("atkbd: setleds failed\n");
+	}
+	if (!ALLOW_DISABLE_KBD(kbdc))
+	    send_kbd_command(kbdc, KBDC_ENABLE_KBD);
+
 	/* enable the keyboard port and intr. */
 	if (!set_controller_command_byte(kbdc, 
 		KBD_KBD_CONTROL_BITS | KBD_TRANSLATION | KBD_OVERRIDE_KBD_LOCK,
@@ -1412,7 +1446,9 @@ init_keyboard(KBDC kbdc, int *type, int flags)
 		 * This is serious; we are left with the disabled
 		 * keyboard intr. 
 		 */
-		set_controller_command_byte(kbdc, 0xff, c);
+		set_controller_command_byte(kbdc, ALLOW_DISABLE_KBD(kbdc)
+		    ? 0xff : (KBD_KBD_CONTROL_BITS | KBD_TRANSLATION |
+			KBD_OVERRIDE_KBD_LOCK), c);
 		kbdc_lock(kbdc, FALSE);
 		printf("atkbd: unable to enable the keyboard port and intr.\n");
 		return EIO;
diff --git a/sys/dev/atkbdc/atkbdc.c b/sys/dev/atkbdc/atkbdc.c
index 9368dbeb1018..69ffa63f338e 100644
--- a/sys/dev/atkbdc/atkbdc.c
+++ b/sys/dev/atkbdc/atkbdc.c
@@ -114,6 +114,41 @@ static int wait_for_kbd_ack(atkbdc_softc_t *kbdc);
 static int wait_for_aux_data(atkbdc_softc_t *kbdc);
 static int wait_for_aux_ack(atkbdc_softc_t *kbdc);
 
+struct atkbdc_quirks {
+    const char* bios_vendor;
+    const char*	maker;
+    const char*	product;
+    int		quirk;
+};
+
+static struct atkbdc_quirks quirks[] = {
+    {"coreboot", "Acer", "Peppy",
+	KBDC_QUIRK_KEEP_ACTIVATED | KBDC_QUIRK_IGNORE_PROBE_RESULT |
+	KBDC_QUIRK_RESET_AFTER_PROBE | KBDC_QUIRK_SETLEDS_ON_INIT},
+
+    {NULL, NULL, NULL, 0}
+};
+
+#define QUIRK_STR_MATCH(s1, s2) (s1 == NULL || \
+    (s2 != NULL && !strcmp(s1, s2)))
+
+static int
+atkbdc_getquirks(void)
+{
+    int i;
+    char* bios_vendor = kern_getenv("smbios.bios.vendor");
+    char* maker = kern_getenv("smbios.system.maker");
+    char* product = kern_getenv("smbios.system.product");
+
+    for (i=0; quirks[i].quirk != 0; ++i)
+	if (QUIRK_STR_MATCH(quirks[i].bios_vendor, bios_vendor) &&
+	    QUIRK_STR_MATCH(quirks[i].maker, maker) &&
+	    QUIRK_STR_MATCH(quirks[i].product, product))
+		return (quirks[i].quirk);
+
+    return (0);
+}
+
 atkbdc_softc_t
 *atkbdc_get_softc(int unit)
 {
@@ -295,6 +330,7 @@ atkbdc_setup(atkbdc_softc_t *sc, bus_space_tag_t tag, bus_space_handle_t h0,
 #else
 	sc->retry = 5000;
 #endif
+	sc->quirks = atkbdc_getquirks();
 
 	return 0;
 }
@@ -1124,7 +1160,8 @@ void
 kbdc_set_device_mask(KBDC p, int mask)
 {
     kbdcp(p)->command_mask = 
-	mask & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS);
+	mask & (((kbdcp(p)->quirks & KBDC_QUIRK_KEEP_ACTIVATED)
+	    ? 0 : KBD_KBD_CONTROL_BITS) | KBD_AUX_CONTROL_BITS);
 }
 
 int
diff --git a/sys/dev/atkbdc/atkbdcreg.h b/sys/dev/atkbdc/atkbdcreg.h
index 44a9801cf8df..db590b96cc9f 100644
--- a/sys/dev/atkbdc/atkbdcreg.h
+++ b/sys/dev/atkbdc/atkbdcreg.h
@@ -202,6 +202,11 @@ typedef struct atkbdc_softc {
     kqueue kbd;			/* keyboard data queue */
     kqueue aux;			/* auxiliary data queue */
     int retry;
+    int quirks;			/* controller doesn't like deactivate */
+#define KBDC_QUIRK_KEEP_ACTIVATED	(1 << 0)
+#define KBDC_QUIRK_IGNORE_PROBE_RESULT	(1 << 1)
+#define KBDC_QUIRK_RESET_AFTER_PROBE	(1 << 2)
+#define KBDC_QUIRK_SETLEDS_ON_INIT	(1 << 3)
 } atkbdc_softc_t; 
 
 enum kbdc_device_ivar {
diff --git a/sys/dev/atkbdc/psm.c b/sys/dev/atkbdc/psm.c
index 9a6ae72ae415..94cf880f14e8 100644
--- a/sys/dev/atkbdc/psm.c
+++ b/sys/dev/atkbdc/psm.c
@@ -371,6 +371,10 @@ static devclass_t psm_devclass;
 /* other flags (flags) */
 #define	PSM_FLAGS_FINGERDOWN	0x0001	/* VersaPad finger down */
 
+#define kbdcp(p)			((atkbdc_softc_t *)(p))
+#define ALWAYS_RESTORE_CONTROLLER(kbdc)	!(kbdcp(kbdc)->quirks \
+    & KBDC_QUIRK_KEEP_ACTIVATED)
+
 /* Tunables */
 static int tap_enabled = -1;
 TUNABLE_INT("hw.psm.tap_enabled", &tap_enabled);
@@ -1231,7 +1235,8 @@ psmprobe(device_t dev)
 		 * this is CONTROLLER ERROR; I don't know how to recover
 		 * from this error...
 		 */
-		restore_controller(sc->kbdc, command_byte);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		printf("psm%d: unable to set the command byte.\n", unit);
 		endprobe(ENXIO);
 	}
@@ -1270,7 +1275,8 @@ psmprobe(device_t dev)
 		recover_from_error(sc->kbdc);
 		if (sc->config & PSM_CONFIG_IGNPORTERROR)
 			break;
-		restore_controller(sc->kbdc, command_byte);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		if (verbose)
 			printf("psm%d: the aux port is not functioning (%d).\n",
 			    unit, i);
@@ -1293,7 +1299,8 @@ psmprobe(device_t dev)
 		 */
 		if (!reset_aux_dev(sc->kbdc)) {
 			recover_from_error(sc->kbdc);
-			restore_controller(sc->kbdc, command_byte);
+			if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+				restore_controller(sc->kbdc, command_byte);
 			if (verbose)
 				printf("psm%d: failed to reset the aux "
 				    "device.\n", unit);
@@ -1315,7 +1322,8 @@ psmprobe(device_t dev)
 	if (!enable_aux_dev(sc->kbdc) || !disable_aux_dev(sc->kbdc)) {
 		/* MOUSE ERROR */
 		recover_from_error(sc->kbdc);
-		restore_controller(sc->kbdc, command_byte);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		if (verbose)
 			printf("psm%d: failed to enable the aux device.\n",
 			    unit);
@@ -1337,7 +1345,8 @@ psmprobe(device_t dev)
 	/* verify the device is a mouse */
 	sc->hw.hwid = get_aux_id(sc->kbdc);
 	if (!is_a_mouse(sc->hw.hwid)) {
-		restore_controller(sc->kbdc, command_byte);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		if (verbose)
 			printf("psm%d: unknown device type (%d).\n", unit,
 			    sc->hw.hwid);
@@ -1443,7 +1452,8 @@ psmprobe(device_t dev)
 		 * this is CONTROLLER ERROR; I don't know the proper way to
 		 * recover from this error...
 		 */
-		restore_controller(sc->kbdc, command_byte);
+		if (ALWAYS_RESTORE_CONTROLLER(sc->kbdc))
+			restore_controller(sc->kbdc, command_byte);
 		printf("psm%d: unable to set the command byte.\n", unit);
 		endprobe(ENXIO);
 	}