aboutsummaryrefslogtreecommitdiff
path: root/src/ec/google/wilco/mailbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ec/google/wilco/mailbox.c')
-rw-r--r--src/ec/google/wilco/mailbox.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/ec/google/wilco/mailbox.c b/src/ec/google/wilco/mailbox.c
new file mode 100644
index 0000000000..1c38a5b880
--- /dev/null
+++ b/src/ec/google/wilco/mailbox.c
@@ -0,0 +1,257 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 Google LLC
+ *
+ * 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; version 2 of the License
+ *
+ * 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.
+ */
+
+#include <arch/io.h>
+#include <console/console.h>
+#include <delay.h>
+#include <ec/google/common/mec.h>
+#include <stdint.h>
+#include <stdlib.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);
+
+ /* Wait for it to complete */
+ if (wilco_ec_response_timed_out()) {
+ printk(BIOS_ERR, "%s: response timed out\n", __func__);
+ return -1;
+ }
+
+ /* 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;
+ }
+
+ /* 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 (%lu 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;
+}