forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
5.9 KiB
215 lines
5.9 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Mailbox interface for Wilco Embedded Controller |
|
* |
|
* Copyright 2018 Google LLC |
|
* |
|
* The Wilco EC is similar to a typical ChromeOS embedded controller. |
|
* It uses the same MEC based low-level communication and a similar |
|
* protocol, but with some important differences. The EC firmware does |
|
* not support the same mailbox commands so it is not registered as a |
|
* cros_ec device type. |
|
* |
|
* Most messages follow a standard format, but there are some exceptions |
|
* and an interface is provided to do direct/raw transactions that do not |
|
* make assumptions about byte placement. |
|
*/ |
|
|
|
#include <linux/delay.h> |
|
#include <linux/device.h> |
|
#include <linux/io.h> |
|
#include <linux/platform_data/wilco-ec.h> |
|
#include <linux/platform_device.h> |
|
|
|
#include "../cros_ec_lpc_mec.h" |
|
|
|
/* 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 |
|
|
|
/* Number of header bytes to be counted as data bytes */ |
|
#define EC_MAILBOX_DATA_EXTRA 2 |
|
|
|
/* Maximum timeout */ |
|
#define EC_MAILBOX_TIMEOUT HZ |
|
|
|
/* 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 */ |
|
|
|
/** |
|
* wilco_ec_response_timed_out() - Wait for EC response. |
|
* @ec: EC device. |
|
* |
|
* Return: true if EC timed out, false if EC did not time out. |
|
*/ |
|
static bool wilco_ec_response_timed_out(struct wilco_ec_device *ec) |
|
{ |
|
unsigned long timeout = jiffies + EC_MAILBOX_TIMEOUT; |
|
|
|
do { |
|
if (!(inb(ec->io_command->start) & |
|
(EC_CMDR_PENDING | EC_CMDR_BUSY))) |
|
return false; |
|
usleep_range(100, 200); |
|
} while (time_before(jiffies, timeout)); |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* wilco_ec_checksum() - Compute 8-bit checksum over data range. |
|
* @data: Data to checksum. |
|
* @size: Number of bytes to checksum. |
|
* |
|
* Return: 8-bit checksum of provided data. |
|
*/ |
|
static u8 wilco_ec_checksum(const void *data, size_t size) |
|
{ |
|
u8 *data_bytes = (u8 *)data; |
|
u8 checksum = 0; |
|
size_t i; |
|
|
|
for (i = 0; i < size; i++) |
|
checksum += data_bytes[i]; |
|
|
|
return checksum; |
|
} |
|
|
|
/** |
|
* wilco_ec_prepare() - Prepare the request structure for the EC. |
|
* @msg: EC message with request information. |
|
* @rq: EC request structure to fill. |
|
*/ |
|
static void wilco_ec_prepare(struct wilco_ec_message *msg, |
|
struct wilco_ec_request *rq) |
|
{ |
|
memset(rq, 0, sizeof(*rq)); |
|
rq->struct_version = EC_MAILBOX_PROTO_VERSION; |
|
rq->mailbox_id = msg->type; |
|
rq->mailbox_version = EC_MAILBOX_VERSION; |
|
rq->data_size = msg->request_size; |
|
|
|
/* Checksum header and data */ |
|
rq->checksum = wilco_ec_checksum(rq, sizeof(*rq)); |
|
rq->checksum += wilco_ec_checksum(msg->request_data, msg->request_size); |
|
rq->checksum = -rq->checksum; |
|
} |
|
|
|
/** |
|
* wilco_ec_transfer() - Perform actual data transfer. |
|
* @ec: EC device. |
|
* @msg: EC message data for request and response. |
|
* @rq: Filled in request structure |
|
* |
|
* Context: ec->mailbox_lock should be held while using this function. |
|
* Return: number of bytes received or negative error code on failure. |
|
*/ |
|
static int wilco_ec_transfer(struct wilco_ec_device *ec, |
|
struct wilco_ec_message *msg, |
|
struct wilco_ec_request *rq) |
|
{ |
|
struct wilco_ec_response *rs; |
|
u8 checksum; |
|
u8 flag; |
|
|
|
/* Write request header, then data */ |
|
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq); |
|
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size, |
|
msg->request_data); |
|
|
|
/* Start the command */ |
|
outb(EC_MAILBOX_START_COMMAND, ec->io_command->start); |
|
|
|
/* For some commands (eg shutdown) the EC will not respond, that's OK */ |
|
if (msg->flags & WILCO_EC_FLAG_NO_RESPONSE) { |
|
dev_dbg(ec->dev, "EC does not respond to this command\n"); |
|
return 0; |
|
} |
|
|
|
/* Wait for it to complete */ |
|
if (wilco_ec_response_timed_out(ec)) { |
|
dev_dbg(ec->dev, "response timed out\n"); |
|
return -ETIMEDOUT; |
|
} |
|
|
|
/* Check result */ |
|
flag = inb(ec->io_data->start); |
|
if (flag) { |
|
dev_dbg(ec->dev, "bad response: 0x%02x\n", flag); |
|
return -EIO; |
|
} |
|
|
|
/* Read back response */ |
|
rs = ec->data_buffer; |
|
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0, |
|
sizeof(*rs) + EC_MAILBOX_DATA_SIZE, |
|
(u8 *)rs); |
|
if (checksum) { |
|
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum); |
|
return -EBADMSG; |
|
} |
|
|
|
if (rs->result) { |
|
dev_dbg(ec->dev, "EC reported failure: 0x%02x\n", rs->result); |
|
return -EBADMSG; |
|
} |
|
|
|
if (rs->data_size != EC_MAILBOX_DATA_SIZE) { |
|
dev_dbg(ec->dev, "unexpected packet size (%u != %u)\n", |
|
rs->data_size, EC_MAILBOX_DATA_SIZE); |
|
return -EMSGSIZE; |
|
} |
|
|
|
if (rs->data_size < msg->response_size) { |
|
dev_dbg(ec->dev, "EC didn't return enough data (%u < %zu)\n", |
|
rs->data_size, msg->response_size); |
|
return -EMSGSIZE; |
|
} |
|
|
|
memcpy(msg->response_data, rs->data, msg->response_size); |
|
|
|
return rs->data_size; |
|
} |
|
|
|
/** |
|
* wilco_ec_mailbox() - Send EC request and receive EC response. |
|
* @ec: EC device. |
|
* @msg: EC message data for request and response. |
|
* |
|
* On entry msg->type, msg->request_size, and msg->request_data should all be |
|
* filled in. If desired, msg->flags can be set. |
|
* |
|
* If a response is expected, msg->response_size should be set, and |
|
* msg->response_data should point to a buffer with enough space. On exit |
|
* msg->response_data will be filled. |
|
* |
|
* Return: number of bytes received or negative error code on failure. |
|
*/ |
|
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg) |
|
{ |
|
struct wilco_ec_request *rq; |
|
int ret; |
|
|
|
dev_dbg(ec->dev, "type=%04x flags=%02x rslen=%zu rqlen=%zu\n", |
|
msg->type, msg->flags, msg->response_size, msg->request_size); |
|
|
|
mutex_lock(&ec->mailbox_lock); |
|
/* Prepare request packet */ |
|
rq = ec->data_buffer; |
|
wilco_ec_prepare(msg, rq); |
|
|
|
ret = wilco_ec_transfer(ec, msg, rq); |
|
mutex_unlock(&ec->mailbox_lock); |
|
|
|
return ret; |
|
|
|
} |
|
EXPORT_SYMBOL_GPL(wilco_ec_mailbox);
|
|
|