Blob Blame History Raw
From 5c3fd1b135bd73c8e3467b9b635b25ca4d360a19 Mon Sep 17 00:00:00 2001
From: Vladimir Serbinenko <phcoder@gmail.com>
Date: Tue, 9 May 2017 08:47:34 +0200
Subject: [PATCH 033/216] arm_coreboot: Add Chromebook keyboard driver.

---
 grub-core/term/arm/cros.c    | 125 +++++++++++++++++++++++
 grub-core/term/arm/cros_ec.c | 238 +++++++++++++++++++++++++++++++++++++++++++
 include/grub/arm/cros_ec.h   |  21 ++++
 3 files changed, 384 insertions(+)
 create mode 100644 grub-core/term/arm/cros.c
 create mode 100644 grub-core/term/arm/cros_ec.c
 create mode 100644 include/grub/arm/cros_ec.h

diff --git a/grub-core/term/arm/cros.c b/grub-core/term/arm/cros.c
new file mode 100644
index 00000000000..1ff9f8ccfb8
--- /dev/null
+++ b/grub-core/term/arm/cros.c
@@ -0,0 +1,125 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *
+ *  Copyright (C) 2012  Google Inc.
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
+ *
+ *  This is based on depthcharge code.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/ps2.h>
+#include <grub/fdtbus.h>
+#include <grub/err.h>
+#include <grub/machine/kernel.h>
+#include <grub/misc.h>
+#include <grub/term.h>
+#include <grub/time.h>
+#include <grub/fdtbus.h>
+#include <grub/arm/cros_ec.h>
+
+struct grub_ps2_state ps2_state;
+
+struct grub_cros_ec_keyscan old_scan;
+
+static const struct grub_fdtbus_dev *cros_ec;
+
+static grub_uint8_t map_code[GRUB_CROS_EC_KEYSCAN_COLS][GRUB_CROS_EC_KEYSCAN_ROWS];
+
+static grub_uint8_t e0_translate[16] =
+  {
+    0x1c, 0x1d, 0x35, 0x00,
+    0x38, 0x00, 0x47, 0x48,
+    0x49, 0x4b, 0x4d, 0x4f,
+    0x50, 0x51, 0x52, 0x53,
+  };
+
+/* If there is a character pending, return it;
+   otherwise return GRUB_TERM_NO_KEY.  */
+static int
+grub_cros_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused)))
+{
+  struct grub_cros_ec_keyscan scan;
+  int i, j;
+  if (grub_cros_ec_scan_keyboard (cros_ec, &scan) < 0)
+    return GRUB_TERM_NO_KEY;
+  for (i = 0; i < GRUB_CROS_EC_KEYSCAN_COLS; i++)
+    if (scan.data[i] ^ old_scan.data[i])
+      for (j = 0; j < GRUB_CROS_EC_KEYSCAN_ROWS; j++)
+	if ((scan.data[i] ^ old_scan.data[i]) & (1 << j))
+	  {
+	    grub_uint8_t code = map_code[i][j];
+	    int ret;
+	    grub_uint8_t brk = 0;
+	    if (!(scan.data[i] & (1 << j)))
+	      brk = 0x80;
+	    grub_dprintf ("cros_keyboard", "key <%d, %d> code %x\n", i, j, code);
+	    if (code < 0x60)
+	      ret = grub_ps2_process_incoming_byte (&ps2_state, code | brk);
+	    else if (code >= 0x60 && code < 0x70 && e0_translate[code - 0x60])
+	      {
+		grub_ps2_process_incoming_byte (&ps2_state, 0xe0);
+		ret = grub_ps2_process_incoming_byte (&ps2_state, e0_translate[code - 0x60] | brk);
+	      }
+	    else
+	      ret = GRUB_TERM_NO_KEY;
+	    old_scan.data[i] ^= (1 << j);
+	    if (ret != GRUB_TERM_NO_KEY)
+	      return ret;
+	  }
+  return GRUB_TERM_NO_KEY;
+}
+
+static struct grub_term_input grub_cros_keyboard_term =
+  {
+    .name = "cros_keyboard",
+    .getkey = grub_cros_keyboard_getkey
+  };
+
+static grub_err_t
+cros_attach (const struct grub_fdtbus_dev *dev)
+{
+  grub_size_t keymap_size, i;
+  const grub_uint8_t *keymap = grub_fdtbus_get_prop (dev, "linux,keymap", &keymap_size);
+
+  if (!dev->parent || !grub_cros_ec_validate (dev->parent))
+    return GRUB_ERR_IO;
+
+  if (keymap)
+    {
+      for (i = 0; i + 3 < keymap_size; i += 4)
+	if (keymap[i+1] < GRUB_CROS_EC_KEYSCAN_COLS && keymap[i] < GRUB_CROS_EC_KEYSCAN_ROWS
+	    && keymap[i+2] == 0 && keymap[i+3] < 0x80)
+	  map_code[keymap[i+1]][keymap[i]] = keymap[i+3];
+    }
+
+  cros_ec = dev->parent;
+  ps2_state.current_set = 1;
+  ps2_state.at_keyboard_status = 0;
+  grub_term_register_input ("cros_keyboard", &grub_cros_keyboard_term);
+  return GRUB_ERR_NONE;
+}
+
+static struct grub_fdtbus_driver cros =
+{
+  .compatible = "google,cros-ec-keyb",
+  .attach = cros_attach
+};
+
+void
+grub_cros_init (void)
+{
+  grub_fdtbus_register (&cros);
+}
diff --git a/grub-core/term/arm/cros_ec.c b/grub-core/term/arm/cros_ec.c
new file mode 100644
index 00000000000..f4144818b5b
--- /dev/null
+++ b/grub-core/term/arm/cros_ec.c
@@ -0,0 +1,238 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *
+ *  Copyright (C) 2012  Google Inc.
+ *  Copyright (C) 2016  Free Software Foundation, Inc.
+ *
+ *  This is based on depthcharge code.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/misc.h>
+#include <grub/arm/cros_ec.h>
+#include <grub/fdtbus.h>
+
+static const grub_uint64_t FRAMING_TIMEOUT_MS = 300;
+
+static const grub_uint8_t EC_FRAMING_BYTE = 0xec;
+
+#define EC_CMD_MKBP_STATE 0x60
+#define EC_CMD_VERSION0 0xdc
+
+static grub_uint64_t last_transfer;
+
+static void
+stop_bus (const struct grub_fdtbus_dev *spi)
+{
+  spi->driver->stop (spi);
+  last_transfer = grub_get_time_ms ();
+}
+
+static int
+wait_for_frame (const struct grub_fdtbus_dev *spi)
+{
+  grub_uint64_t start = grub_get_time_ms ();
+  grub_uint8_t byte;
+  do
+    {
+      if (spi->driver->receive (spi, &byte, 1))
+	return -1;
+      if (byte != EC_FRAMING_BYTE &&
+	  grub_get_time_ms () - start > FRAMING_TIMEOUT_MS)
+	{
+	  grub_dprintf ("cros", "Timeout waiting for framing byte.\n");
+	  return -1;
+	}
+    }
+  while (byte != EC_FRAMING_BYTE);
+  return 0;
+}
+
+/*
+ * Calculate a simple 8-bit checksum of a data block
+ *
+ * @param data	Data block to checksum
+ * @param size	Size of data block in bytes
+ * @return checksum value (0 to 255)
+ */
+static grub_uint8_t
+cros_ec_calc_checksum (const void *data, int size)
+{
+  grub_uint8_t csum;
+  const grub_uint8_t *bytes = data;
+  int i;
+
+  for (i = csum = 0; i < size; i++)
+    csum += bytes[i];
+  return csum & 0xff;
+}
+
+enum
+{
+  /* response, arglen */
+  CROS_EC_SPI_IN_HDR_SIZE = 2,
+  /* version, cmd, arglen */
+  CROS_EC_SPI_OUT_HDR_SIZE = 3
+};
+
+static grub_uint8_t busbuf[256];
+#define MSG_BYTES ((int)sizeof (busbuf))
+
+static int
+ec_command (const struct grub_fdtbus_dev *dev, int cmd, int cmd_version,
+	    const void *dout, int dout_len, void *din, int din_len)
+{
+  const struct grub_fdtbus_dev *spi = dev->parent;
+  grub_uint8_t *bytes;
+
+  /* Header + data + checksum. */
+  grub_uint32_t out_bytes = CROS_EC_SPI_OUT_HDR_SIZE + dout_len + 1;
+  grub_uint32_t in_bytes = CROS_EC_SPI_IN_HDR_SIZE + din_len + 1;
+
+  /*
+   * Sanity-check I/O sizes given transaction overhead in internal
+   * buffers.
+   */
+  if (out_bytes > MSG_BYTES)
+    {
+      grub_dprintf ("cros", "Cannot send %d bytes\n", dout_len);
+      return -1;
+    }
+  if (in_bytes > MSG_BYTES)
+    {
+      grub_dprintf ("cros", "Cannot receive %d bytes\n", din_len);
+      return -1;
+    }
+
+  /* Prepare the output. */
+  bytes = busbuf;
+  *bytes++ = EC_CMD_VERSION0 + cmd_version;
+  *bytes++ = cmd;
+  *bytes++ = dout_len;
+  grub_memcpy (bytes, dout, dout_len);
+  bytes += dout_len;
+
+  *bytes++ = cros_ec_calc_checksum (busbuf,
+				    CROS_EC_SPI_OUT_HDR_SIZE + dout_len);
+
+  /* Depthcharge uses 200 us here but GRUB timer resolution is only 1ms,
+     decrease this when we increase timer resolution.  */
+  while (grub_get_time_ms () - last_transfer < 1)
+    ;
+
+  if (spi->driver->start (spi))
+    return -1;
+
+  /* Allow EC to ramp up clock after being awoken. */
+  /* Depthcharge only waits 100 us here but GRUB timer resolution is only 1ms,
+     decrease this when we increase timer resolution.  */
+  grub_millisleep (1);
+
+  if (spi->driver->send (spi, busbuf, out_bytes))
+    {
+      stop_bus (spi);
+      return -1;
+    }
+
+  /* Wait until the EC is ready. */
+  if (wait_for_frame (spi))
+    {
+      stop_bus (spi);
+      return -1;
+    }
+
+  /* Read the response code and the data length. */
+  bytes = busbuf;
+  if (spi->driver->receive (spi, bytes, 2))
+    {
+      stop_bus (spi);
+      return -1;
+    }
+  grub_uint8_t result = *bytes++;
+  grub_uint8_t length = *bytes++;
+
+  /* Make sure there's enough room for the data. */
+  if (CROS_EC_SPI_IN_HDR_SIZE + length + 1 > MSG_BYTES)
+    {
+      grub_dprintf ("cros", "Received length %#02x too large\n", length);
+      stop_bus (spi);
+      return -1;
+    }
+
+  /* Read the data and the checksum, and finish up. */
+  if (spi->driver->receive (spi, bytes, length + 1))
+    {
+      stop_bus (spi);
+      return -1;
+    }
+  bytes += length;
+  int expected = *bytes++;
+  stop_bus (spi);
+
+  /* Check the integrity of the response. */
+  if (result != 0)
+    {
+      grub_dprintf ("cros", "Received bad result code %d\n", result);
+      return -result;
+    }
+
+  int csum = cros_ec_calc_checksum (busbuf,
+				    CROS_EC_SPI_IN_HDR_SIZE + length);
+
+  if (csum != expected)
+    {
+      grub_dprintf ("cros", "Invalid checksum rx %#02x, calced %#02x\n",
+		    expected, csum);
+      return -1;
+    }
+
+  /* If the caller wants the response, copy it out for them. */
+  if (length < din_len)
+    din_len = length;
+  if (din)
+    {
+      grub_memcpy (din, (grub_uint8_t *) busbuf + CROS_EC_SPI_IN_HDR_SIZE, din_len);
+    }
+
+  return din_len;
+}
+
+int
+grub_cros_ec_scan_keyboard (const struct grub_fdtbus_dev *dev, struct grub_cros_ec_keyscan *scan)
+{
+  if (ec_command (dev, EC_CMD_MKBP_STATE, 0, NULL, 0, scan,
+		  sizeof (*scan)) < (int) sizeof (*scan))
+    return -1;
+
+  return 0;
+}
+
+int
+grub_cros_ec_validate (const struct grub_fdtbus_dev *dev)
+{
+  if (!grub_fdtbus_is_compatible("google,cros-ec-spi", dev))
+    return 0;
+  if (!dev->parent)
+    return 0;
+  if (!dev->parent->driver)
+    return 0;
+  if (!dev->parent->driver->send
+      || !dev->parent->driver->receive)
+    return 0;
+  return 1;
+}
+
diff --git a/include/grub/arm/cros_ec.h b/include/grub/arm/cros_ec.h
new file mode 100644
index 00000000000..45a372572a5
--- /dev/null
+++ b/include/grub/arm/cros_ec.h
@@ -0,0 +1,21 @@
+#ifndef GRUB_ARM_CROS_EC_H
+#define GRUB_ARM_CROS_EC_H 1
+
+#include <grub/types.h>
+#include <grub/fdtbus.h>
+
+#define GRUB_CROS_EC_KEYSCAN_COLS 13
+#define GRUB_CROS_EC_KEYSCAN_ROWS 8
+
+struct grub_cros_ec_keyscan {
+  grub_uint8_t data[GRUB_CROS_EC_KEYSCAN_COLS];
+};
+
+int
+grub_cros_ec_scan_keyboard (const struct grub_fdtbus_dev *dev,
+			    struct grub_cros_ec_keyscan *scan);
+
+int
+grub_cros_ec_validate (const struct grub_fdtbus_dev *dev);
+
+#endif
-- 
2.15.0