blob: 963cae9ec80b68421cf1775942b935a2bbb090e8 [file] [log] [blame]
/*
* This file is part of the flashrom project.
*
* Copyright (C) 2012 The Chromium OS Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Google or the names of contributors or
* licensors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind.
* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
* GOOGLE INC AND ITS LICENSORS SHALL NOT BE LIABLE
* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
* GOOGLE OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
* OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
* LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF GOOGLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#include "flashchips.h"
#include "fmap.h"
#if USE_GEC_LOCK == 1
#include "gec_lock.h"
#endif
#include "gec_ec_commands.h"
#include "programmer.h"
#include "gec.h"
#include "spi.h"
#include "writeprotect.h"
#define INITIAL_UDELAY 10 /* 10 us */
#define MAXIMUM_UDELAY 10000 /* 10 ms */
static int ec_timeout_usec = 1000000;
static int lpc_cmd_args_supported;
#define GEC_LOCK_TIMEOUT_SECS 30 /* 30 secs */
/*
* Wait for the EC to be unbusy. Returns 0 if unbusy, non-zero if
* timeout.
*/
static int wait_for_ec(int status_addr, int timeout_usec)
{
int i;
int delay = INITIAL_UDELAY;
for (i = 0; i < timeout_usec; i += delay) {
/*
* Delay first, in case we just sent out a command but the EC
* hasn't raise the busy flag. However, I think this doesn't
* happen since the LPC commands are executed in order and the
* busy flag is set by hardware.
*
* TODO: move this delay after inb(status).
*/
usleep(MIN(delay, timeout_usec - i));
if (!(inb(status_addr) & EC_LPC_STATUS_BUSY_MASK))
return 0;
/* Increase the delay interval after a few rapid checks */
if (i > 20)
delay = MIN(delay * 2, MAXIMUM_UDELAY);
}
return -1; /* Timeout */
}
/*
**************************** EC API v0 ****************************
*/
/* preserve legacy naming to be consistent with legacy implementation. */
#define EC_LPC_ADDR_OLD_PARAM EC_HOST_CMD_REGION1
#define EC_OLD_PARAM_SIZE EC_HOST_CMD_REGION_SIZE
static enum ec_status gec_get_result() {
return inb(EC_LPC_ADDR_HOST_DATA);
}
/* Sends a command to the EC. Returns the command status code, or
* -1 if other error. */
static int gec_command_lpc_old(int command, const void *outdata, int outsize,
void *indata, int insize) {
uint8_t *d;
int i;
if ((outsize > EC_OLD_PARAM_SIZE) ||
(insize > EC_OLD_PARAM_SIZE)) {
msg_pdbg2("Data size too big for buffer.\n");
return -EC_RES_INVALID_PARAM;
}
if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
msg_pdbg2("Timeout waiting for EC ready\n");
return -EC_RES_ERROR;
}
/* Write data, if any */
/* TODO: optimized copy using outl() */
for (i = 0, d = (uint8_t *)outdata; i < outsize; i++, d++) {
msg_pdbg2("GEC: Port[0x%x] <-- 0x%x\n",
EC_LPC_ADDR_OLD_PARAM + i, *d);
outb(*d, EC_LPC_ADDR_OLD_PARAM + i);
}
msg_pdbg2("GEC: Run EC Command: 0x%x ----\n", command);
outb(command, EC_LPC_ADDR_HOST_CMD);
if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
msg_pdbg2("Timeout waiting for EC response\n");
return -EC_RES_ERROR;
}
/* Check status */
if ((i = gec_get_result()) != EC_RES_SUCCESS) {
msg_pdbg2("EC returned error status %d\n", i);
return -i;
}
/* Read data, if any */
for (i = 0, d = (uint8_t *)indata; i < insize; i++, d++) {
*d = inb(EC_LPC_ADDR_OLD_PARAM + i);
msg_pdbg2("GEC: Port[0x%x] ---> 0x%x\n",
EC_LPC_ADDR_OLD_PARAM + i, *d);
}
return 0;
}
/*
**************************** EC API v1 ****************************
*/
static int gec_command_lpc(int command, int version,
const void *outdata, int outsize,
void *indata, int insize) {
struct ec_lpc_host_args args;
const uint8_t *dout;
uint8_t *din;
int csum;
int i;
/* Fall back to old-style command interface if args aren't supported */
if (!lpc_cmd_args_supported)
return gec_command_lpc_old(command, outdata, outsize,
indata, insize);
/* Fill in args */
args.flags = EC_HOST_ARGS_FLAG_FROM_HOST;
args.command_version = version;
args.data_size = outsize;
/* Calculate checksum */
csum = command + args.flags + args.command_version + args.data_size;
for (i = 0, dout = (const uint8_t *)outdata; i < outsize; i++, dout++)
csum += *dout;
args.checksum = (uint8_t)csum;
/* Wait EC is ready to accept command before writing anything to
* parameter.
*/
if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
msg_pdbg("Timeout waiting for EC ready\n");
return -EC_RES_ERROR;
}
/* Write args */
for (i = 0, dout = (const uint8_t *)&args;
i < sizeof(args);
i++, dout++)
outb(*dout, EC_LPC_ADDR_HOST_ARGS + i);
/* Write data, if any */
/* TODO: optimized copy using outl() */
for (i = 0, dout = (uint8_t *)outdata; i < outsize; i++, dout++)
outb(*dout, EC_LPC_ADDR_HOST_PARAM + i);
outb(command, EC_LPC_ADDR_HOST_CMD);
if (wait_for_ec(EC_LPC_ADDR_HOST_CMD, ec_timeout_usec)) {
msg_perr("Timeout waiting for EC response\n");
return -EC_RES_ERROR;
}
/* Check result */
i = inb(EC_LPC_ADDR_HOST_DATA);
if (i) {
msg_pdbg2("EC returned error result code %d\n", i);
return -i;
}
/* Read back args */
for (i = 0, din = (uint8_t *)&args; i < sizeof(args); i++, din++)
*din = inb(EC_LPC_ADDR_HOST_ARGS + 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)) {
msg_perr("EC protocol mismatch\n");
return -EC_RES_INVALID_RESPONSE;
}
if (args.data_size > insize) {
msg_perr("EC returned too much data\n");
return -EC_RES_INVALID_RESPONSE;
}
/* Read data, if any */
/* TODO: optimized copy using outl() */
for (i = 0, din = (uint8_t *)indata; i < args.data_size; i++, din++)
*din = inb(EC_LPC_ADDR_HOST_PARAM + i);
/* Verify checksum */
csum = command + args.flags + args.command_version + args.data_size;
for (i = 0, din = (uint8_t *)indata; i < args.data_size; i++, din++)
csum += *din;
if (args.checksum != (uint8_t)csum) {
msg_perr("EC response has invalid checksum\n");
return -EC_RES_INVALID_CHECKSUM;
}
/* Return actual amount of data received */
return args.data_size;
}
static struct gec_priv gec_lpc_priv = {
.detected = 0,
.ec_command = gec_command_lpc,
};
/*
* The algorithm is following:
*
* 1. If you detect EC command args support, success.
* 2. If all ports read 0xff, fail.
* 3. Try hello command (for API v0).
*
* TODO: This is an intrusive command for non-Google ECs. Needs a more proper
* and more friendly way to detect.
*/
static int detect_ec(void) {
int i;
int byte = 0xff;
struct ec_params_hello request;
struct ec_response_hello response;
int rc = 0;
int old_timeout = ec_timeout_usec;
#if USE_GEC_LOCK == 1
msg_gdbg("Acquiring GEC lock (timeout=%d sec)...\n",
GEC_LOCK_TIMEOUT_SECS);
if (acquire_gec_lock(GEC_LOCK_TIMEOUT_SECS) < 0) {
msg_gerr("Could not acquire GEC lock.\n");
return 1;
}
#endif
/*
* 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.
*/
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)) {
msg_pdbg("%s(): new EC API is detected.\n", __func__);
lpc_cmd_args_supported = 1;
return 0;
} else {
msg_pdbg("%s(): use legacy EC API.\n", __func__);
}
/*
* Test if the I/O port has been configured for Chromium EC LPC
* interface. If all the bytes are 0xff, very likely that Chromium EC
* is not present.
*
* TODO: (crosbug.com/p/10963) Should only need to look at the command
* byte, since we don't support ACPI burst mode and thus bit 4 should
* be 0.
*/
byte &= inb(EC_LPC_ADDR_HOST_CMD);
byte &= inb(EC_LPC_ADDR_HOST_DATA);
for (i = 0; i < EC_OLD_PARAM_SIZE && byte == 0xff; ++i)
byte &= inb(EC_LPC_ADDR_OLD_PARAM + i);
if (byte == 0xff) {
msg_pdbg("Port 0x%x,0x%x,0x%x-0x%x are all 0xFF.\n",
EC_LPC_ADDR_HOST_CMD, EC_LPC_ADDR_HOST_DATA,
EC_LPC_ADDR_OLD_PARAM,
EC_LPC_ADDR_OLD_PARAM + EC_OLD_PARAM_SIZE - 1);
msg_pdbg(
"Very likely this board doesn't have a Chromium EC.\n");
return 1;
}
/* Try hello command -- for EC only supports API v0
* TODO: (crosbug.com/p/33102) Remove after MP.
*/
/* reduce timeout period temporarily in case EC is not present */
ec_timeout_usec = 25000;
if (gec_test(&gec_lpc_priv))
return 1;
ec_timeout_usec = old_timeout;
return 0;
}
static struct opaque_programmer opaque_programmer_gec = {
.max_data_read = EC_HOST_CMD_REGION_SIZE,
.max_data_write = 64,
.probe = gec_probe_size,
.read = gec_read,
.write = gec_write,
.erase = gec_block_erase,
.data = &gec_lpc_priv,
};
static int gec_lpc_shutdown(void *data)
{
#if USE_GEC_LOCK == 1
release_gec_lock();
#endif
return 0;
}
int gec_probe_lpc(const char *name) {
msg_pdbg("%s():%d ...\n", __func__, __LINE__);
if (alias && alias->type != ALIAS_EC)
return 1;
if (detect_ec()) return 1;
msg_pdbg("GEC detected on LPC bus\n");
gec_lpc_priv.detected = 1;
if (buses_supported & BUS_SPI) {
msg_pdbg("%s():%d remove BUS_SPI from buses_supported.\n",
__func__, __LINE__);
buses_supported &= ~BUS_SPI;
}
register_opaque_programmer(&opaque_programmer_gec);
buses_supported |= BUS_LPC;
if (register_shutdown(gec_lpc_shutdown, NULL)) {
msg_perr("Cannot register LPC shutdown function.\n");
return 1;
}
return 0;
}