-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
platform/chrome: Add new driver for Wilco EC
This EC is an incompatible variant of the typical Chrome OS embedded controller. It uses the same low-level communication and a similar protocol with some significant differences. The EC firmware does not support the same mailbox commands so it is not registered as a cros_ec device type. This commit exports the wilco_ec_mailbox() function so that other modules can use it to communicate with the EC. Signed-off-by: Duncan Laurie <dlaurie@google.com> Signed-off-by: Nick Crews <ncrews@chromium.org> [Fix the sparse warning: symbol 'wilco_ec_transfer' was not declared] Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com> [Fix Kconfig dependencies for wilco_ec] Reported-by: Randy Dunlap <rdunlap@infradead.org> Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
- Loading branch information
Nick Crews
authored and
Enric Balletbo i Serra
committed
Feb 21, 2019
1 parent
6b7cb22
commit 7b3d4f4
Showing
7 changed files
with
499 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
config WILCO_EC | ||
tristate "ChromeOS Wilco Embedded Controller" | ||
depends on ACPI && X86 && CROS_EC_LPC_MEC | ||
help | ||
If you say Y here, you get support for talking to the ChromeOS | ||
Wilco EC over an eSPI bus. This uses a simple byte-level protocol | ||
with a checksum. | ||
|
||
To compile this driver as a module, choose M here: the | ||
module will be called wilco_ec. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# SPDX-License-Identifier: GPL-2.0 | ||
|
||
wilco_ec-objs := core.o mailbox.o | ||
obj-$(CONFIG_WILCO_EC) += wilco_ec.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Core driver for Wilco Embedded Controller | ||
* | ||
* Copyright 2018 Google LLC | ||
* | ||
* This is the entry point for the drivers that control the Wilco EC. | ||
* This driver is responsible for several tasks: | ||
* - Initialize the register interface that is used by wilco_ec_mailbox() | ||
* - Create a platform device which is picked up by the debugfs driver | ||
* - Create a platform device which is picked up by the RTC driver | ||
*/ | ||
|
||
#include <linux/acpi.h> | ||
#include <linux/device.h> | ||
#include <linux/ioport.h> | ||
#include <linux/module.h> | ||
#include <linux/platform_data/wilco-ec.h> | ||
#include <linux/platform_device.h> | ||
|
||
#include "../cros_ec_lpc_mec.h" | ||
|
||
#define DRV_NAME "wilco-ec" | ||
|
||
static struct resource *wilco_get_resource(struct platform_device *pdev, | ||
int index) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct resource *res; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_IO, index); | ||
if (!res) { | ||
dev_dbg(dev, "Couldn't find IO resource %d\n", index); | ||
return res; | ||
} | ||
|
||
return devm_request_region(dev, res->start, resource_size(res), | ||
dev_name(dev)); | ||
} | ||
|
||
static int wilco_ec_probe(struct platform_device *pdev) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct wilco_ec_device *ec; | ||
|
||
ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); | ||
if (!ec) | ||
return -ENOMEM; | ||
|
||
platform_set_drvdata(pdev, ec); | ||
ec->dev = dev; | ||
mutex_init(&ec->mailbox_lock); | ||
|
||
/* Largest data buffer size requirement is extended data response */ | ||
ec->data_size = sizeof(struct wilco_ec_response) + | ||
EC_MAILBOX_DATA_SIZE_EXTENDED; | ||
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL); | ||
if (!ec->data_buffer) | ||
return -ENOMEM; | ||
|
||
/* Prepare access to IO regions provided by ACPI */ | ||
ec->io_data = wilco_get_resource(pdev, 0); /* Host Data */ | ||
ec->io_command = wilco_get_resource(pdev, 1); /* Host Command */ | ||
ec->io_packet = wilco_get_resource(pdev, 2); /* MEC EMI */ | ||
if (!ec->io_data || !ec->io_command || !ec->io_packet) | ||
return -ENODEV; | ||
|
||
/* Initialize cros_ec register interface for communication */ | ||
cros_ec_lpc_mec_init(ec->io_packet->start, | ||
ec->io_packet->start + EC_MAILBOX_DATA_SIZE); | ||
|
||
return 0; | ||
} | ||
|
||
static int wilco_ec_remove(struct platform_device *pdev) | ||
{ | ||
/* Teardown cros_ec interface */ | ||
cros_ec_lpc_mec_destroy(); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct acpi_device_id wilco_ec_acpi_device_ids[] = { | ||
{ "GOOG000C", 0 }, | ||
{ } | ||
}; | ||
MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids); | ||
|
||
static struct platform_driver wilco_ec_driver = { | ||
.driver = { | ||
.name = DRV_NAME, | ||
.acpi_match_table = wilco_ec_acpi_device_ids, | ||
}, | ||
.probe = wilco_ec_probe, | ||
.remove = wilco_ec_remove, | ||
}; | ||
|
||
module_platform_driver(wilco_ec_driver); | ||
|
||
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); | ||
MODULE_AUTHOR("Duncan Laurie <dlaurie@chromium.org>"); | ||
MODULE_LICENSE("GPL v2"); | ||
MODULE_DESCRIPTION("ChromeOS Wilco Embedded Controller driver"); | ||
MODULE_ALIAS("platform:" DRV_NAME); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
// 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)); | ||
|
||
/* Handle messages without trimming bytes from the request */ | ||
if (msg->request_size && msg->flags & WILCO_EC_FLAG_RAW_REQUEST) { | ||
rq->reserved_raw = *(u8 *)msg->request_data; | ||
msg->request_size--; | ||
memmove(msg->request_data, msg->request_data + 1, | ||
msg->request_size); | ||
} | ||
|
||
/* Fill in request packet */ | ||
rq->struct_version = EC_MAILBOX_PROTO_VERSION; | ||
rq->mailbox_id = msg->type; | ||
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->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; | ||
size_t size; | ||
|
||
/* 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; | ||
} | ||
|
||
if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA) | ||
size = EC_MAILBOX_DATA_SIZE_EXTENDED; | ||
else | ||
size = EC_MAILBOX_DATA_SIZE; | ||
|
||
/* Read back response */ | ||
rs = ec->data_buffer; | ||
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0, | ||
sizeof(*rs) + size, (u8 *)rs); | ||
if (checksum) { | ||
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum); | ||
return -EBADMSG; | ||
} | ||
|
||
/* Check that the EC reported success */ | ||
msg->result = rs->result; | ||
if (msg->result) { | ||
dev_dbg(ec->dev, "bad response: 0x%02x\n", msg->result); | ||
return -EBADMSG; | ||
} | ||
|
||
/* Check the returned data size, skipping the header */ | ||
if (rs->data_size != size) { | ||
dev_dbg(ec->dev, "unexpected packet size (%u != %zu)", | ||
rs->data_size, size); | ||
return -EMSGSIZE; | ||
} | ||
|
||
/* Skip 1 response data byte unless specified */ | ||
size = (msg->flags & WILCO_EC_FLAG_RAW_RESPONSE) ? 0 : 1; | ||
if ((ssize_t) rs->data_size - size < msg->response_size) { | ||
dev_dbg(ec->dev, "response data too short (%zd < %zu)", | ||
(ssize_t) rs->data_size - size, msg->response_size); | ||
return -EMSGSIZE; | ||
} | ||
|
||
/* Ignore response data bytes as requested */ | ||
memcpy(msg->response_data, rs->data + size, msg->response_size); | ||
|
||
/* Return actual amount of data received */ | ||
return msg->response_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->flags, msg->command, msg->request_size, | ||
* msg->response_size, and msg->request_data should all be filled in. | ||
* | ||
* On exit msg->result and 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, "cmd=%02x type=%04x flags=%02x rslen=%zu rqlen=%zu\n", | ||
msg->command, msg->type, msg->flags, msg->response_size, | ||
msg->request_size); | ||
|
||
/* Prepare request packet */ | ||
rq = ec->data_buffer; | ||
wilco_ec_prepare(msg, rq); | ||
|
||
mutex_lock(&ec->mailbox_lock); | ||
ret = wilco_ec_transfer(ec, msg, rq); | ||
mutex_unlock(&ec->mailbox_lock); | ||
|
||
return ret; | ||
|
||
} | ||
EXPORT_SYMBOL_GPL(wilco_ec_mailbox); |
Oops, something went wrong.