arm_coreboot: Add Chromebook keyboard driver.

This commit is contained in:
Vladimir Serbinenko 2017-05-09 08:47:34 +02:00
parent b0b1b81a11
commit 5c3fd1b135
3 changed files with 384 additions and 0 deletions

125
grub-core/term/arm/cros.c Normal file
View file

@ -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);
}

View file

@ -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;
}

View file

@ -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