blob: 23551f0e3cfa80b53828e1573bc2a8c627edc51d [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <arch/io.h>
#include <console/console.h>
#include <delay.h>
#include <ec/google/common/mec.h>
#include <string.h>
#include <timer.h>
#include <types.h>
#include "ec.h"
/* Mailbox ID */
#define EC_MAILBOX_ID 0x00f0
/* Version of mailbox interface */
#define EC_MAILBOX_VERSION 0
/* Command to start mailbox transaction */
#define EC_MAILBOX_START_COMMAND 0xda
/* Version of EC protocol */
#define EC_MAILBOX_PROTO_VERSION 3
/* Max number of bytes in protocol data payload */
#define EC_MAILBOX_DATA_SIZE 32
/* Number of header bytes to be counted as data bytes */
#define EC_MAILBOX_DATA_EXTRA 2
/* Maximum timeout */
#define EC_MAILBOX_TIMEOUT_MS MSECS_PER_SEC
/* EC response flags */
#define EC_CMDR_DATA BIT(0) /* Data ready for host to read */
#define EC_CMDR_PENDING BIT(1) /* Write pending to EC */
#define EC_CMDR_BUSY BIT(2) /* EC is busy processing a command */
#define EC_CMDR_CMD BIT(3) /* Last host write was a command */
/* Request to EC */
struct wilco_ec_request {
uint8_t struct_version; /* version (=3) */
uint8_t checksum; /* sum of all bytes must be 0 */
uint16_t mailbox_id; /* mailbox identifier */
uint8_t mailbox_version; /* mailbox version (=0) */
uint8_t reserved1; /* unused (=0) */
uint16_t data_size; /* length (data + 2 bytes of header) */
uint8_t command; /* mailbox command */
uint8_t reserved2; /* unused (=0) */
} __packed;
/* Response from EC */
struct wilco_ec_response {
uint8_t struct_version; /* version (=3) */
uint8_t checksum; /* sum of all bytes must be 0 */
uint16_t result; /* result code */
uint16_t data_size; /* length of data buffer (always 32) */
uint8_t reserved[3]; /* unused (=0) */
uint8_t data[EC_MAILBOX_DATA_SIZE];
} __packed;
struct wilco_ec_message {
uint8_t command; /* mailbox command code */
uint8_t result; /* request result */
size_t request_size; /* bytes to send to the EC */
size_t response_size; /* bytes expected from the EC */
enum wilco_ec_msg_type type; /* message type */
/*
* This data buffer will contain the request data when passed to
* wilco_ec_message() and will contain the response data on return.
*/
uint8_t data[EC_MAILBOX_DATA_SIZE];
};
static bool wilco_ec_response_timed_out(void)
{
uint8_t mask = EC_CMDR_PENDING | EC_CMDR_BUSY;
struct stopwatch sw;
stopwatch_init_msecs_expire(&sw, EC_MAILBOX_TIMEOUT_MS);
while (inb(CONFIG_EC_BASE_HOST_COMMAND) & mask) {
if (stopwatch_expired(&sw)) {
printk(BIOS_ERR, "%s: Command timeout\n", __func__);
return true; /* Timed out */
}
mdelay(1);
}
return false; /* Did not time out */
}
static uint8_t wilco_ec_checksum(void *data, size_t size)
{
uint8_t *data_bytes = (uint8_t *)data;
uint8_t checksum = 0;
size_t i;
for (i = 0; i < size; i++)
checksum += data_bytes[i];
return checksum;
}
static void wilco_ec_prepare(struct wilco_ec_message *msg,
struct wilco_ec_request *rq)
{
memset(rq, 0, sizeof(*rq));
/* Fill in request packet */
rq->struct_version = EC_MAILBOX_PROTO_VERSION;
rq->mailbox_id = EC_MAILBOX_ID;
rq->mailbox_version = EC_MAILBOX_VERSION;
rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA;
rq->command = msg->command;
/* Checksum header and data */
rq->checksum = wilco_ec_checksum(rq, sizeof(*rq));
rq->checksum += wilco_ec_checksum(msg->data, msg->request_size);
rq->checksum = -rq->checksum;
}
static int wilco_ec_transfer(struct wilco_ec_message *msg)
{
struct wilco_ec_request rq;
struct wilco_ec_response rs;
uint8_t checksum;
size_t skip_size;
/* Prepare request packet */
wilco_ec_prepare(msg, &rq);
/* Write request header */
mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, 0, &rq, sizeof(rq));
/* Write request data */
mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, sizeof(rq),
msg->data, msg->request_size);
/* Start the command */
outb(EC_MAILBOX_START_COMMAND, CONFIG_EC_BASE_HOST_COMMAND);
/* Some commands will put the EC into a state where it cannot respond */
if (msg->type == WILCO_EC_MSG_NO_RESPONSE) {
printk(BIOS_DEBUG, "%s: EC does not respond to this command\n",
__func__);
return 0;
}
/* Wait for it to complete */
if (wilco_ec_response_timed_out()) {
printk(BIOS_ERR, "%s: response timed out\n", __func__);
return -1;
}
/* Check result */
msg->result = inb(CONFIG_EC_BASE_HOST_DATA);
if (msg->result != 0) {
printk(BIOS_ERR, "%s: bad response: 0x%02x\n",
__func__, msg->result);
return -1;
}
/* Read back response */
checksum = mec_io_bytes(MEC_IO_READ, CONFIG_EC_BASE_PACKET, 0,
&rs, sizeof(rs));
if (checksum) {
printk(BIOS_ERR, "%s: bad checksum %02x\n", __func__, checksum);
return -1;
}
msg->result = rs.result;
/* EC always returns EC_MAILBOX_DATA_SIZE bytes */
if (rs.data_size > EC_MAILBOX_DATA_SIZE) {
printk(BIOS_ERR, "%s: packet too long (%d bytes, expected %d)",
__func__, rs.data_size, EC_MAILBOX_DATA_SIZE);
return -1;
}
/* Skip response data bytes as requested */
skip_size = (msg->type == WILCO_EC_MSG_DEFAULT) ? 1 : 0;
if (msg->response_size > rs.data_size - skip_size) {
printk(BIOS_ERR, "%s: data too short (%zu bytes, expected %zu)",
__func__, rs.data_size - skip_size, msg->response_size);
return -1;
}
memcpy(msg->data, rs.data + skip_size, msg->response_size);
/* Return actual amount of data received */
return msg->response_size;
}
int wilco_ec_mailbox(enum wilco_ec_msg_type type, uint8_t command,
const void *request_data, size_t request_size,
void *response_data, size_t response_size)
{
struct wilco_ec_message msg = {
.command = command,
.request_size = request_size,
.response_size = response_size,
.type = type,
};
int ret;
if (request_size > EC_MAILBOX_DATA_SIZE) {
printk(BIOS_ERR, "%s: provided request data too large: %zu\n",
__func__, request_size);
return -1;
}
if (response_size > EC_MAILBOX_DATA_SIZE) {
printk(BIOS_ERR, "%s: expected response data too large: %zu\n",
__func__, response_size);
return -1;
}
if (request_size && !request_data) {
printk(BIOS_ERR, "%s: request data missing\n", __func__);
return -1;
}
if (response_size && !response_data) {
printk(BIOS_ERR, "%s: request data missing\n", __func__);
return -1;
}
/* Copy request data if present */
if (request_size)
memcpy(msg.data, request_data, request_size);
/* Do the EC transfer */
ret = wilco_ec_transfer(&msg);
/* Copy response data if present */
if (ret > 0 && response_size)
memcpy(response_data, msg.data, response_size);
/* Return error if message result is non-zero */
if (ret >= 0 && msg.result)
ret = -1;
return ret;
}