blob: cafff135612c65bc7a1999ce299abe6113321116 [file] [log] [blame]
/*
* Chromium OS mkbp driver
*
* Copyright (c) 2012 The Chromium OS Authors.
* See file CREDITS for list of people who contributed to this
* project.
*
* This program 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 2 of
* the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
/*
* The Matrix Keyboard Protocol driver handles talking to the keyboard
* controller chip. Mostly this is for keyboard functions, but some other
* things have slipped in, so we provide generic services to talk to the
* KBC.
*/
#include <common.h>
#include <command.h>
#include <i2c.h>
#include <mkbp.h>
#include <fdtdec.h>
#include <malloc.h>
#include <spi.h>
#include <asm/io.h>
#include <asm-generic/gpio.h>
#ifdef DEBUG_TRACE
#define debug_trace(fmt, b...) debug(fmt, #b)
#else
#define debug_trace(fmt, b...)
#endif
static struct mkbp_dev static_dev, *last_dev;
void mkbp_dump_data(const char *name, int cmd, const uint8_t *data, int len)
{
#ifdef DEBUG
int i;
printf("%s: ", name);
if (cmd != -1)
printf("cmd=%#x: ", cmd);
for (i = 0; i < len; i++)
printf("%02x ", data[i]);
printf("\n");
#endif
}
/*
* 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)
*/
int mkbp_calc_checksum(const uint8_t *data, int size)
{
int csum, i;
for (i = csum = 0; i < size; i++)
csum += data[i];
return csum & 0xff;
}
/**
* Send a command to the MKBP device and return the reply.
*
* The device's internal input/output buffers are used.
*
* @param dev MKBP device
* @param cmd Command to send (EC_CMD_...)
* @param cmd_version Version of command to send (EC_VER_...)
* @param dout Output data (may be NULL If dout_len=0)
* @param dout_len Size of output data in bytes
* @param din Response data (may be NULL If din_len=0)
* @param din_len Maximum size of response in bytes
* @return number of bytes in response, or -1 on error
*/
static int ec_command(struct mkbp_dev *dev, uint8_t cmd, int cmd_version,
const void *dout, int dout_len,
void *dinp, int din_len)
{
uint8_t *din;
int len = 0;
if (cmd_version != 0 && !dev->cmd_version_is_supported) {
debug("%s: Command version >0 unsupported\n", __func__);
return -1;
}
switch (dev->interface) {
#ifdef CONFIG_MKBP_SPI
case MKBPIF_SPI:
return mkbp_spi_command(dev, cmd, cmd_version,
(const uint8_t *)dout, dout_len,
(uint8_t *)dinp, din_len);
break;
#endif
#ifdef CONFIG_MKBP_I2C
case MKBPIF_I2C:
len = mkbp_i2c_command(dev, cmd, cmd_version,
(const uint8_t *)dout, dout_len,
&din, din_len);
break;
#endif
#ifdef CONFIG_MKBP_LPC
case MKBPIF_LPC:
return mkbp_lpc_command(dev, cmd, cmd_version,
(const uint8_t *)dout, dout_len,
(uint8_t *)dinp, din_len);
break;
#endif
case MKBPIF_NONE:
default:
return -1;
}
debug("%s: len=%d, din=%p, dinp=%p\n", __func__, len, din, dinp);
/* If we have any data to return, it must be 64bit-aligned */
assert(len <= 0 || !((uintptr_t)din & 7));
if (len > 0) {
if (din)
memmove(dinp, din, len);
}
return len;
}
int mkbp_scan_keyboard(struct mkbp_dev *dev, struct mbkp_keyscan *scan)
{
if (ec_command(dev, EC_CMD_MKBP_STATE, 0, NULL, 0, scan->data,
sizeof(scan->data)) < sizeof(scan->data))
return -1;
return 0;
}
int mkbp_read_id(struct mkbp_dev *dev, char *id, int maxlen)
{
struct ec_response_get_version r;
if (ec_command(dev, EC_CMD_GET_VERSION, 0, NULL, 0, &r,
sizeof(r)) < sizeof(r))
return -1;
if (maxlen > sizeof(r.version_string_ro))
maxlen = sizeof(r.version_string_ro);
switch (r.current_image) {
case EC_IMAGE_RO:
memcpy(id, r.version_string_ro, maxlen);
break;
case EC_IMAGE_RW:
memcpy(id, r.version_string_rw, maxlen);
break;
default:
return -1;
}
id[maxlen - 1] = '\0';
return 0;
}
int mkbp_read_current_image(struct mkbp_dev *dev, enum ec_current_image *image)
{
struct ec_response_get_version r;
if (ec_command(dev, EC_CMD_GET_VERSION, 0, NULL, 0, &r,
sizeof(r)) < sizeof(r))
return -1;
*image = r.current_image;
return 0;
}
int mkbp_read_hash(struct mkbp_dev *dev, struct ec_response_vboot_hash *hash)
{
struct ec_params_vboot_hash p;
p.cmd = EC_VBOOT_HASH_GET;
if (ec_command(dev, EC_CMD_VBOOT_HASH, 0, &p, sizeof(p),
hash, sizeof(*hash)) < 0)
return -1;
if (hash->status != EC_VBOOT_HASH_STATUS_DONE) {
debug("%s: Hash status not done: %d\n", __func__,
hash->status);
return -1;
}
return 0;
}
int mkbp_reboot(struct mkbp_dev *dev, enum ec_reboot_cmd cmd, uint8_t flags)
{
struct ec_params_reboot_ec p;
p.cmd = cmd;
p.flags = flags;
if (ec_command(dev, EC_CMD_REBOOT_EC, 0, &p, sizeof(p), NULL, 0) < 0)
return -1;
if (!(flags & EC_REBOOT_FLAG_ON_AP_SHUTDOWN)) {
/*
* EC reboot will take place immediately so delay to allow it
* to complete. Note that some reboot types (EC_REBOOT_COLD)
* will reboot the AP as well, in which case we won't actually
* get to this point.
*/
/*
* TODO(rspangler@chromium.org): Would be nice if we had a
* better way to determine when the reboot is complete. Could
* we poll a memory-mapped LPC value?
*/
udelay(50000);
}
return 0;
}
int mkbp_interrupt_pending(struct mkbp_dev *dev)
{
/* no interrupt support : always poll */
if (!fdt_gpio_isvalid(&dev->ec_int))
return 1;
return !gpio_get_value(dev->ec_int.gpio);
}
int mkbp_info(struct mkbp_dev *dev, struct ec_response_mkbp_info *info)
{
if (ec_command(dev, EC_CMD_MKBP_INFO, 0,
NULL, 0, info, sizeof(*info)) < sizeof(*info))
return -1;
return 0;
}
int mkbp_get_host_events(struct mkbp_dev *dev, uint32_t *events_ptr)
{
struct ec_response_host_event_mask resp;
/*
* Use the B copy of the event flags, because the main copy is already
* used by ACPI/SMI.
*/
if (ec_command(dev, EC_CMD_HOST_EVENT_GET_B, 0, NULL, 0,
&resp, sizeof(resp)) < sizeof(resp))
return -1;
if (resp.mask & EC_HOST_EVENT_MASK(EC_HOST_EVENT_INVALID))
return -1;
*events_ptr = resp.mask;
return 0;
}
int mkbp_clear_host_events(struct mkbp_dev *dev, uint32_t events)
{
struct ec_params_host_event_mask params;
params.mask = events;
/*
* Use the B copy of the event flags, so it affects the data returned
* by mkbp_get_host_events().
*/
if (ec_command(dev, EC_CMD_HOST_EVENT_CLEAR_B, 0,
&params, sizeof(params), NULL, 0) < 0)
return -1;
return 0;
}
int mkbp_flash_protect(struct mkbp_dev *dev,
uint32_t set_mask, uint32_t set_flags,
struct ec_response_flash_protect *resp)
{
struct ec_params_flash_protect params;
params.mask = set_mask;
params.flags = set_flags;
if (ec_command(dev, EC_CMD_FLASH_PROTECT, EC_VER_FLASH_PROTECT,
&params, sizeof(params),
resp, sizeof(*resp)) < sizeof(*resp))
return -1;
return 0;
}
int mkbp_test(struct mkbp_dev *dev)
{
struct ec_params_hello req;
struct ec_response_hello resp;
req.in_data = 0x12345678;
if (ec_command(dev, EC_CMD_HELLO, 0, (uint8_t **)&req, sizeof(req),
&resp, sizeof(resp)) < sizeof(resp)) {
printf("ec_command() returned error\n");
return -1;
}
if (resp.out_data != req.in_data + 0x01020304) {
printf("Received invalid handshake %x\n", resp.out_data);
return -1;
}
return 0;
}
static int mkbp_flash_rw_offset(struct mkbp_dev *dev,
uint32_t *offset, uint32_t *size)
{
struct ec_params_flash_region_info p;
struct ec_response_flash_region_info r;
int ret;
p.region = EC_FLASH_REGION_RW;
ret = ec_command(dev, EC_CMD_FLASH_REGION_INFO,
EC_VER_FLASH_REGION_INFO,
&p, sizeof(p), &r, sizeof(r));
if (ret != sizeof(r))
return -1;
if (offset)
*offset = r.offset;
if (size)
*size = r.size;
return 0;
}
static int mkbp_flash_erase(struct mkbp_dev *dev,
uint32_t offset, uint32_t size)
{
struct ec_params_flash_erase p;
p.offset = offset;
p.size = size;
return ec_command(dev, EC_CMD_FLASH_ERASE, 0,
&p, sizeof(p), NULL, 0) >= 0 ? 0 : -1;
}
static int mkbp_flash_write(struct mkbp_dev *dev, const uint8_t *data,
uint32_t offset, uint32_t size)
{
struct ec_params_flash_write p;
p.offset = offset;
p.size = size;
memcpy(p.data, data, p.size);
return ec_command(dev, EC_CMD_FLASH_WRITE, 0,
&p, sizeof(p), NULL, 0) >= 0 ? 0 : -1;
}
/**
* Return optimal flash write burst size
*/
static int mkbp_flash_write_burst_size(struct mkbp_dev *dev)
{
struct ec_params_flash_write p;
return sizeof(p.data);
}
int mkbp_flash_update_rw(struct mkbp_dev *dev,
const uint8_t *image, int image_size)
{
uint32_t rw_offset, rw_size;
uint32_t end, off;
uint32_t burst = mkbp_flash_write_burst_size(dev);
int ret;
if (mkbp_flash_rw_offset(dev, &rw_offset, &rw_size))
return -1;
if (image_size > rw_size)
return -1;
/*
* Erase the entire RW section, so that the EC doesn't see any garbage
* past the new image if it's smaller than the current image.
*
* TODO: could optimize this to erase just the current image + the 0xea
* byte, since presumably everything past that is 0xff's. But would
* still need to round up to the nearest multiple of erase size.
*/
ret = mkbp_flash_erase(dev, rw_offset, rw_size);
if (ret)
return ret;
/*
* Write the image, followed by a 0xea byte so the EC can scan
* backwards from the end of the region to find the RW image size.
*
* TODO: clunky that MKBP needs to know that. Maybe should have an
* option to flash-write to indicate this is the last byte in the
* RW region? Fix in ver 2 of flash write command.
*/
/*
* TODO: round up to the nearest multiple of write size. Can get away
* without that on link right now because its write size is 4 bytes.
*/
end = rw_offset + image_size;
for (off = rw_offset; off < end; off += burst, image += burst) {
ret = mkbp_flash_write(dev, image, off, min(end - off, burst));
if (ret)
return ret;
}
/*
* If the image doesn't fill the entire section, write a 0xea byte
* following the image so that the EC can scan backwards to detect
* the image size.
*
* TODO: clunky that MKBP needs to know this. Maybe EC should have a
* finalize-rw-image command which appends the byte and erases
* everything past that? Or a prepare-rw-image command which starts
* with that? But if we need to round up to the nearest multiple of
* write size, the 0xea byte would need to go there, or be written
* after the image (because flash write can turn 1 bits into 0 bits but
* not vice versa).
*/
if (image_size < rw_size) {
uint8_t trailer[4] = {0xea, 0xff, 0xff, 0xff};
ret = mkbp_flash_write(dev, trailer, rw_offset + image_size,
sizeof(trailer));
if (ret)
return ret;
}
return 0;
}
/**
* Decode MBKP details from the device tree and allocate a suitable device.
*
* @param blob Device tree blob
* @param node Node to decode from
* @param devp Returns a pointer to the new allocated device
* @return 0 if ok, -1 on error
*/
static int mkbp_decode_fdt(const void *blob, int node, struct mkbp_dev **devp)
{
enum fdt_compat_id compat;
struct mkbp_dev *dev;
int parent;
/* See what type of parent we are inside (this is expensive) */
parent = fdt_parent_offset(blob, node);
if (parent < 0) {
debug("%s: Cannot find node parent\n", __func__);
return -1;
}
dev = &static_dev;
dev->node = node;
dev->parent_node = parent;
compat = fdtdec_lookup(blob, parent);
switch (compat) {
#ifdef CONFIG_MKBP_SPI
case COMPAT_SAMSUNG_EXYNOS_SPI:
dev->interface = MKBPIF_SPI;
break;
#endif
#ifdef CONFIG_MKBP_I2C
case COMPAT_SAMSUNG_S3C2440_I2C:
dev->interface = MKBPIF_I2C;
if (mkbp_i2c_decode_fdt(dev, blob))
return -1;
break;
#endif
#ifdef CONFIG_MKBP_LPC
case COMPAT_INTEL_LPC:
dev->interface = MKBPIF_LPC;
break;
#endif
default:
debug("%s: Unknown compat id %d\n", __func__, compat);
return -1;
}
fdtdec_decode_gpio(blob, node, "ec-interrupt", &dev->ec_int);
*devp = dev;
return 0;
}
struct mkbp_dev *mkbp_init(const void *blob)
{
char id[MSG_BYTES];
struct mkbp_dev *dev;
int node;
node = fdtdec_next_compatible(blob, 0, COMPAT_GOOGLE_MKBP);
if (node < 0) {
debug("%s: Node not found\n", __func__);
return NULL;
}
if (mkbp_decode_fdt(blob, node, &dev)) {
debug("%s: Failed to decode device.\n", __func__);
return NULL;
}
/* Remember this device for use by the mkbp command */
last_dev = dev;
switch (dev->interface) {
#ifdef CONFIG_MKBP_SPI
case MKBPIF_SPI:
if (mkbp_spi_init(dev, blob))
return NULL;
break;
#endif
#ifdef CONFIG_MKBP_I2C
case MKBPIF_I2C:
if (mkbp_i2c_init(dev, blob))
return NULL;
break;
#endif
#ifdef CONFIG_MKBP_LPC
case MKBPIF_LPC:
if (mkbp_lpc_init(dev, blob))
return NULL;
break;
#endif
case MKBPIF_NONE:
default:
return NULL;
}
/* TODO(sjg@chromium.org): Wait for Bill's x86 GPIO patch */
#if defined(CONFIG_MKBP_SPI) || defined(CONFIG_MKBP_I2C)
/* we will poll the EC interrupt line */
fdtdec_setup_gpio(&dev->ec_int);
if (fdt_gpio_isvalid(&dev->ec_int))
gpio_direction_input(dev->ec_int.gpio);
#endif
if (mkbp_read_id(dev, id, sizeof(id))) {
debug("%s: Could not read KBC ID\n", __func__);
return NULL;
}
debug("Google Chrome EC MKBP driver ready, id '%s'\n", id);
return dev;
}
#ifdef CONFIG_CMD_MKBP
static int do_mkbp(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
struct mkbp_dev *dev = last_dev;
const char *cmd;
if (argc < 2)
return CMD_RET_USAGE;
/* Just use the last allocated device; there should be only one */
if (!last_dev) {
printf("No MKBP device available\n");
return 1;
}
cmd = argv[1];
if (0 == strcmp("id", cmd)) {
char id[MSG_BYTES];
if (mkbp_read_id(dev, id, sizeof(id))) {
debug("%s: Could not read KBC ID\n", __func__);
return 1;
}
printf("%s\n", id);
} else if (0 == strcmp("info", cmd)) {
struct ec_response_mkbp_info info;
if (mkbp_info(dev, &info)) {
debug("%s: Could not read KBC info\n", __func__);
return 1;
}
printf("rows = %u\n", info.rows);
printf("cols = %u\n", info.cols);
printf("switches = %#x\n", info.switches);
} else if (0 == strcmp("curimage", cmd)) {
enum ec_current_image image;
if (mkbp_read_current_image(dev, &image)) {
debug("%s: Could not read KBC image\n", __func__);
return 1;
}
printf("%d\n", image);
} else if (0 == strcmp("hash", cmd)) {
struct ec_response_vboot_hash hash;
int i;
if (mkbp_read_hash(dev, &hash)) {
debug("%s: Could not read KBC hash\n", __func__);
return 1;
}
if (hash.hash_type == EC_VBOOT_HASH_TYPE_SHA256)
printf("type: SHA-256\n");
else
printf("type: %d\n", hash.hash_type);
printf("offset: 0x%08x\n", hash.offset);
printf("size: 0x%08x\n", hash.size);
printf("digest: ");
for (i = 0; i < hash.digest_size; i++)
printf("%02x", hash.hash_digest[i]);
printf("\n");
} else if (0 == strcmp("reboot", cmd)) {
enum ec_reboot_cmd cmd = EC_REBOOT_COLD;
if (argc >= 3 && !strcmp(argv[2], "rw"))
cmd = EC_REBOOT_JUMP_RW;
if (mkbp_reboot(dev, cmd, 0)) {
debug("%s: Could not reboot KBC\n", __func__);
return 1;
}
} else if (0 == strcmp("events", cmd)) {
uint32_t events;
if (mkbp_get_host_events(dev, &events)) {
debug("%s: Could not read host events\n", __func__);
return 1;
}
printf("0x%08x\n", events);
} else if (0 == strcmp("clrevents", cmd)) {
uint32_t events = 0x7fffffff;
if (argc >= 3)
events = simple_strtol(argv[2], NULL, 0);
if (mkbp_clear_host_events(dev, events)) {
debug("%s: Could not clear host events\n", __func__);
return 1;
}
} else if (0 == strcmp("test", cmd)) {
int result = mkbp_test(dev);
if (result)
printf("Test failed with error %d\n", result);
else
puts("Test passed\n");
} else
return CMD_RET_USAGE;
return 0;
}
U_BOOT_CMD(
mkbp, 5, 1, do_mkbp,
"MKBP utility command",
"id Read MKBP ID\n"
"mkbp info Read MKBP info\n"
"mkbp curimage Read MKBP current image\n"
"mkbp hash Read MKBP hash\n"
"mkbp reboot [rw | cold] Reboot MKBP\n"
"mkbp events Read MKBP host events\n"
"mkbp clrevents [mask] Clear MKBP host events\n"
"mkbp test run tests on mkbp"
);
#endif