arm_coreboot: Add Chromebook keyboard driver.
This commit is contained in:
parent
b0b1b81a11
commit
5c3fd1b135
3 changed files with 384 additions and 0 deletions
125
grub-core/term/arm/cros.c
Normal file
125
grub-core/term/arm/cros.c
Normal 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);
|
||||
}
|
238
grub-core/term/arm/cros_ec.c
Normal file
238
grub-core/term/arm/cros_ec.c
Normal 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;
|
||||
}
|
||||
|
21
include/grub/arm/cros_ec.h
Normal file
21
include/grub/arm/cros_ec.h
Normal 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
|
Loading…
Reference in a new issue