/* 
 | 
 * Chromium OS cros_ec driver - LPC interface 
 | 
 * 
 | 
 * Copyright (c) 2012 The Chromium OS Authors. 
 | 
 * 
 | 
 * SPDX-License-Identifier:    GPL-2.0+ 
 | 
 */ 
 | 
  
 | 
/* 
 | 
 * 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 <dm.h> 
 | 
#include <command.h> 
 | 
#include <cros_ec.h> 
 | 
#include <asm/io.h> 
 | 
  
 | 
#ifdef DEBUG_TRACE 
 | 
#define debug_trace(fmt, b...)    debug(fmt, ##b) 
 | 
#else 
 | 
#define debug_trace(fmt, b...) 
 | 
#endif 
 | 
  
 | 
static int wait_for_sync(struct cros_ec_dev *dev) 
 | 
{ 
 | 
    unsigned long start; 
 | 
  
 | 
    start = get_timer(0); 
 | 
    while (inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK) { 
 | 
        if (get_timer(start) > 1000) { 
 | 
            debug("%s: Timeout waiting for CROS_EC sync\n", 
 | 
                  __func__); 
 | 
            return -1; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int cros_ec_lpc_command(struct udevice *udev, uint8_t cmd, int cmd_version, 
 | 
             const uint8_t *dout, int dout_len, 
 | 
             uint8_t **dinp, int din_len) 
 | 
{ 
 | 
    struct cros_ec_dev *dev = dev_get_uclass_priv(udev); 
 | 
    const int cmd_addr = EC_LPC_ADDR_HOST_CMD; 
 | 
    const int data_addr = EC_LPC_ADDR_HOST_DATA; 
 | 
    const int args_addr = EC_LPC_ADDR_HOST_ARGS; 
 | 
    const int param_addr = EC_LPC_ADDR_HOST_PARAM; 
 | 
  
 | 
    struct ec_lpc_host_args args; 
 | 
    uint8_t *d; 
 | 
    int csum; 
 | 
    int i; 
 | 
  
 | 
    if (dout_len > EC_PROTO2_MAX_PARAM_SIZE) { 
 | 
        debug("%s: Cannot send %d bytes\n", __func__, dout_len); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    /* Fill in args */ 
 | 
    args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; 
 | 
    args.command_version = cmd_version; 
 | 
    args.data_size = dout_len; 
 | 
  
 | 
    /* Calculate checksum */ 
 | 
    csum = cmd + args.flags + args.command_version + args.data_size; 
 | 
    for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) 
 | 
        csum += *d; 
 | 
  
 | 
    args.checksum = (uint8_t)csum; 
 | 
  
 | 
    if (wait_for_sync(dev)) { 
 | 
        debug("%s: Timeout waiting ready\n", __func__); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    /* Write args */ 
 | 
    for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) 
 | 
        outb(*d, args_addr + i); 
 | 
  
 | 
    /* Write data, if any */ 
 | 
    debug_trace("cmd: %02x, ver: %02x", cmd, cmd_version); 
 | 
    for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) { 
 | 
        outb(*d, param_addr + i); 
 | 
        debug_trace("%02x ", *d); 
 | 
    } 
 | 
  
 | 
    outb(cmd, cmd_addr); 
 | 
    debug_trace("\n"); 
 | 
  
 | 
    if (wait_for_sync(dev)) { 
 | 
        debug("%s: Timeout waiting for response\n", __func__); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    /* Check result */ 
 | 
    i = inb(data_addr); 
 | 
    if (i) { 
 | 
        debug("%s: CROS_EC result code %d\n", __func__, i); 
 | 
        return -i; 
 | 
    } 
 | 
  
 | 
    /* Read back args */ 
 | 
    for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) 
 | 
        *d = inb(args_addr + i); 
 | 
  
 | 
    /* 
 | 
     * If EC didn't modify args flags, then somehow we sent a new-style 
 | 
     * command to an old EC, which means it would have read its params 
 | 
     * from the wrong place. 
 | 
     */ 
 | 
    if (!(args.flags & EC_HOST_ARGS_FLAG_TO_HOST)) { 
 | 
        debug("%s: CROS_EC protocol mismatch\n", __func__); 
 | 
        return -EC_RES_INVALID_RESPONSE; 
 | 
    } 
 | 
  
 | 
    if (args.data_size > din_len) { 
 | 
        debug("%s: CROS_EC returned too much data %d > %d\n", 
 | 
              __func__, args.data_size, din_len); 
 | 
        return -EC_RES_INVALID_RESPONSE; 
 | 
    } 
 | 
  
 | 
    /* Read data, if any */ 
 | 
    for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) { 
 | 
        *d = inb(param_addr + i); 
 | 
        debug_trace("%02x ", *d); 
 | 
    } 
 | 
    debug_trace("\n"); 
 | 
  
 | 
    /* Verify checksum */ 
 | 
    csum = cmd + args.flags + args.command_version + args.data_size; 
 | 
    for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) 
 | 
        csum += *d; 
 | 
  
 | 
    if (args.checksum != (uint8_t)csum) { 
 | 
        debug("%s: CROS_EC response has invalid checksum\n", __func__); 
 | 
        return -EC_RES_INVALID_CHECKSUM; 
 | 
    } 
 | 
    *dinp = dev->din; 
 | 
  
 | 
    /* Return actual amount of data received */ 
 | 
    return args.data_size; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Initialize LPC protocol. 
 | 
 * 
 | 
 * @param dev        CROS_EC device 
 | 
 * @param blob        Device tree blob 
 | 
 * @return 0 if ok, -1 on error 
 | 
 */ 
 | 
int cros_ec_lpc_init(struct cros_ec_dev *dev, const void *blob) 
 | 
{ 
 | 
    int byte, i; 
 | 
  
 | 
    /* See if we can find an EC at the other end */ 
 | 
    byte = 0xff; 
 | 
    byte &= inb(EC_LPC_ADDR_HOST_CMD); 
 | 
    byte &= inb(EC_LPC_ADDR_HOST_DATA); 
 | 
    for (i = 0; i < EC_PROTO2_MAX_PARAM_SIZE && (byte == 0xff); i++) 
 | 
        byte &= inb(EC_LPC_ADDR_HOST_PARAM + i); 
 | 
    if (byte == 0xff) { 
 | 
        debug("%s: CROS_EC device not found on LPC bus\n", 
 | 
            __func__); 
 | 
        return -1; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Test if LPC command args are supported. 
 | 
 * 
 | 
 * The cheapest way to do this is by looking for the memory-mapped 
 | 
 * flag.  This is faster than sending a new-style 'hello' command and 
 | 
 * seeing whether the EC sets the EC_HOST_ARGS_FLAG_FROM_HOST flag 
 | 
 * in args when it responds. 
 | 
 */ 
 | 
static int cros_ec_lpc_check_version(struct udevice *dev) 
 | 
{ 
 | 
    if (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) == 'E' && 
 | 
            inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) 
 | 
                == 'C' && 
 | 
            (inb(EC_LPC_ADDR_MEMMAP + 
 | 
                EC_MEMMAP_HOST_CMD_FLAGS) & 
 | 
                EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED)) { 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    printf("%s: ERROR: old EC interface not supported\n", __func__); 
 | 
    return -1; 
 | 
} 
 | 
  
 | 
static int cros_ec_probe(struct udevice *dev) 
 | 
{ 
 | 
    return cros_ec_register(dev); 
 | 
} 
 | 
  
 | 
static struct dm_cros_ec_ops cros_ec_ops = { 
 | 
    .command = cros_ec_lpc_command, 
 | 
    .check_version = cros_ec_lpc_check_version, 
 | 
}; 
 | 
  
 | 
static const struct udevice_id cros_ec_ids[] = { 
 | 
    { .compatible = "google,cros-ec-lpc" }, 
 | 
    { } 
 | 
}; 
 | 
  
 | 
U_BOOT_DRIVER(cros_ec_lpc) = { 
 | 
    .name        = "cros_ec_lpc", 
 | 
    .id        = UCLASS_CROS_EC, 
 | 
    .of_match    = cros_ec_ids, 
 | 
    .probe        = cros_ec_probe, 
 | 
    .ops        = &cros_ec_ops, 
 | 
}; 
 |