diff options
Diffstat (limited to 'src/ec')
-rw-r--r-- | src/ec/google/wilco/Kconfig | 47 | ||||
-rw-r--r-- | src/ec/google/wilco/Makefile.inc | 5 | ||||
-rw-r--r-- | src/ec/google/wilco/ec.h | 47 | ||||
-rw-r--r-- | src/ec/google/wilco/mailbox.c | 257 |
4 files changed, 356 insertions, 0 deletions
diff --git a/src/ec/google/wilco/Kconfig b/src/ec/google/wilco/Kconfig new file mode 100644 index 0000000000..e905d5ee21 --- /dev/null +++ b/src/ec/google/wilco/Kconfig @@ -0,0 +1,47 @@ +config EC_GOOGLE_WILCO + bool + default n + select EC_GOOGLE_COMMON_MEC + help + Google Wilco Embedded Controller interface. + +config EC_BASE_ACPI_DATA + hex + default 0x930 + help + This option provides the 16-bit IO base address for the ACPI + data interface. This is the standard ACPI EC interface that + is used by the ACPI EC drivers in the OS. + +config EC_BASE_ACPI_COMMAND + hex + default 0x934 + help + This option provides the 16-bit IO base address for the ACPI + EC command interface. This is the standard ACPI EC interface + that is used by the ACPI EC drivers in the OS. + +config EC_BASE_HOST_DATA + hex + default 0x940 + help + This option provides the 16-bit IO base address for the host + data interface. This is the interface that is used to drive + the mailbox protocol. + +config EC_BASE_HOST_COMMAND + hex + default 0x944 + help + This option provides the 16-bit IO base address for the host + command interface. This is the interface that is used to drive + the mailbox protocol. + +config EC_BASE_PACKET + hex + default 0x950 + help + This option provides the 16-bit IO base address for the EC + mailbox interface data region. This data buffer is used along + with the host command and data registers to drive the EC + mailbox interface. This is also the MEC EMI base address. diff --git a/src/ec/google/wilco/Makefile.inc b/src/ec/google/wilco/Makefile.inc new file mode 100644 index 0000000000..6130f6f36a --- /dev/null +++ b/src/ec/google/wilco/Makefile.inc @@ -0,0 +1,5 @@ +ifeq ($(CONFIG_EC_GOOGLE_WILCO),y) + +ramstage-y += mailbox.c + +endif diff --git a/src/ec/google/wilco/ec.h b/src/ec/google/wilco/ec.h new file mode 100644 index 0000000000..0ce6166322 --- /dev/null +++ b/src/ec/google/wilco/ec.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef EC_GOOGLE_WILCO_EC_H +#define EC_GOOGLE_WILCO_EC_H + +#include <stddef.h> +#include <stdint.h> + +/* Different supported message types */ +enum wilco_ec_msg_type { + WILCO_EC_MSG_RAW, /* Raw message, do not skip any data */ + WILCO_EC_MSG_DEFAULT, /* Skip 1 byte of response data */ + WILCO_EC_MSG_NO_RESPONSE, /* EC does not respond to command */ +}; + +/** + * wilco_ec_mailbox + * + * Send a command request to the EC mailbox and receive the response. + * + * @type: Mailbox message type, see enum above + * @command: Command to execute + * @request_data: Request data buffer + * @request_size: Number of bytes in request data buffer (max 32) + * @response_data: Response data buffer + * @response_size: Number of bytes in response data buffer (max 32) + * + * @return number of bytes received, negative error code on failure + */ +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); + +#endif /* EC_GOOGLE_WILCO_EC_H */ 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; +} |