summaryrefslogtreecommitdiffstats
path: root/drivers/platform/chrome
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-07-12 03:45:29 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2019-07-12 03:45:29 +0200
commitd7d170a8e357bd9926cc6bfea5c2385c2eac65b2 (patch)
treecb66ba886b0c0b92d3e41ae551b4bc90e219a913 /drivers/platform/chrome
parentMerge tag 'devicetree-for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/g... (diff)
parentmfd / platform: cros_ec_debugfs: Expose resume result via debugfs (diff)
downloadlinux-d7d170a8e357bd9926cc6bfea5c2385c2eac65b2.tar.xz
linux-d7d170a8e357bd9926cc6bfea5c2385c2eac65b2.zip
Merge tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux
Pull chrome platform updates from Benson Leung "CrOS EC: - Add new CrOS ISHTP transport protocol - Add proper documentation for debugfs entries and expose resume and uptime files - Select LPC transport protocol variant at runtime. - Add lid angle sensor driver - Fix oops on suspend/resume for lightbar driver - Set CrOS SPI transport protol in realtime Wilco EC: - Add telemetry char device interface - Add support for event handling - Add new sysfs attributes Misc: - Contains ib-mfd-cros-v5.3 immutable branch from mfd, with cros_ec_commands.h header freshly synced with Chrome OS's EC project" * tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (54 commits) mfd / platform: cros_ec_debugfs: Expose resume result via debugfs platform/chrome: lightbar: Get drvdata from parent in suspend/resume iio: cros_ec: Add lid angle driver platform/chrome: wilco_ec: Add circular buffer as event queue platform/chrome: cros_ec_lpc_mec: Fix kernel-doc comment first line platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime platform/chrome: cros_ec_lpc: Merge cros_ec_lpc and cros_ec_lpc_reg Input: cros_ec_keyb: mask out extra flags in event_type platform/chrome: wilco_ec: Fix unreleased lock in event_read() platform/chrome: cros_ec_debugfs: cros_ec_uptime_fops can be static platform/chrome: cros_ec_debugfs: Add debugfs ABI documentation platform/chrome: cros_ec_debugfs: Fix kernel-doc comment first line platform/chrome: cros_ec_debugfs: Add debugfs entry to retrieve EC uptime mfd: cros_ec: Update I2S API mfd: cros_ec: Add Management API entry points mfd: cros_ec: Add SKU ID and Secure storage API mfd: cros_ec: Add API for rwsig mfd: cros_ec: Add API for Fingerprint support mfd: cros_ec: Add API for Touchpad support mfd: cros_ec: Add API for EC-EC communication ...
Diffstat (limited to 'drivers/platform/chrome')
-rw-r--r--drivers/platform/chrome/Kconfig42
-rw-r--r--drivers/platform/chrome/Makefile4
-rw-r--r--drivers/platform/chrome/cros_ec_debugfs.c48
-rw-r--r--drivers/platform/chrome/cros_ec_ishtp.c763
-rw-r--r--drivers/platform/chrome/cros_ec_lightbar.c6
-rw-r--r--drivers/platform/chrome/cros_ec_lpc.c165
-rw-r--r--drivers/platform/chrome/cros_ec_lpc_mec.c14
-rw-r--r--drivers/platform/chrome/cros_ec_lpc_reg.c101
-rw-r--r--drivers/platform/chrome/cros_ec_lpc_reg.h45
-rw-r--r--drivers/platform/chrome/cros_ec_spi.c68
-rw-r--r--drivers/platform/chrome/cros_ec_sysfs.c2
-rw-r--r--drivers/platform/chrome/cros_ec_vbc.c2
-rw-r--r--drivers/platform/chrome/wilco_ec/Kconfig18
-rw-r--r--drivers/platform/chrome/wilco_ec/Makefile6
-rw-r--r--drivers/platform/chrome/wilco_ec/core.c26
-rw-r--r--drivers/platform/chrome/wilco_ec/debugfs.c12
-rw-r--r--drivers/platform/chrome/wilco_ec/event.c581
-rw-r--r--drivers/platform/chrome/wilco_ec/mailbox.c21
-rw-r--r--drivers/platform/chrome/wilco_ec/properties.c132
-rw-r--r--drivers/platform/chrome/wilco_ec/sysfs.c156
-rw-r--r--drivers/platform/chrome/wilco_ec/telemetry.c450
21 files changed, 2405 insertions, 257 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
index 2826f7136f65..970679d0b6f6 100644
--- a/drivers/platform/chrome/Kconfig
+++ b/drivers/platform/chrome/Kconfig
@@ -72,6 +72,19 @@ config CROS_EC_RPMSG
To compile this driver as a module, choose M here: the
module will be called cros_ec_rpmsg.
+config CROS_EC_ISHTP
+ tristate "ChromeOS Embedded Controller (ISHTP)"
+ depends on MFD_CROS_EC
+ depends on INTEL_ISH_HID
+ help
+ If you say Y here, you get support for talking to the ChromeOS EC
+ firmware running on Intel Integrated Sensor Hub (ISH), using the
+ ISH Transport protocol (ISH-TP). 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 cros_ec_ishtp.
+
config CROS_EC_SPI
tristate "ChromeOS Embedded Controller (SPI)"
depends on MFD_CROS_EC && SPI
@@ -83,28 +96,17 @@ config CROS_EC_SPI
'pre-amble' bytes before the response actually starts.
config CROS_EC_LPC
- tristate "ChromeOS Embedded Controller (LPC)"
- depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
- help
- If you say Y here, you get support for talking to the ChromeOS EC
- over an LPC bus. This uses a simple byte-level protocol with a
- checksum. This is used for userspace access only. The kernel
- typically has its own communication methods.
-
- To compile this driver as a module, choose M here: the
- module will be called cros_ec_lpc.
-
-config CROS_EC_LPC_MEC
- bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant"
- depends on CROS_EC_LPC
- default n
+ tristate "ChromeOS Embedded Controller (LPC)"
+ depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
help
- If you say Y here, a variant LPC protocol for the Microchip EC
- will be used. Note that this variant is not backward compatible
- with non-Microchip ECs.
+ If you say Y here, you get support for talking to the ChromeOS EC
+ over an LPC bus, including the LPC Microchip EC (MEC) variant.
+ This uses a simple byte-level protocol with a checksum. This is
+ used for userspace access only. The kernel typically has its own
+ communication methods.
- If you have a ChromeOS Embedded Controller Microchip EC variant
- choose Y here.
+ To compile this driver as a module, choose M here: the
+ module will be called cros_ec_lpcs.
config CROS_EC_PROTO
bool
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index 1b2f1dcfcd5c..fd0af05cc14c 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -7,10 +7,10 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o
+obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o
-cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o
-cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
+cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c
index 4c2a27f6a6d0..8ec1cc2889f2 100644
--- a/drivers/platform/chrome/cros_ec_debugfs.c
+++ b/drivers/platform/chrome/cros_ec_debugfs.c
@@ -25,7 +25,8 @@
#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
-/* struct cros_ec_debugfs - ChromeOS EC debugging information
+/**
+ * struct cros_ec_debugfs - EC debugging information.
*
* @ec: EC device this debugfs information belongs to
* @dir: dentry for debugfs files
@@ -241,7 +242,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file,
read_buf, p - read_buf);
}
-const struct file_operations cros_ec_console_log_fops = {
+static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct cros_ec_debugfs *debug_info = file->private_data;
+ struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
+ struct {
+ struct cros_ec_command cmd;
+ struct ec_response_uptime_info resp;
+ } __packed msg = {};
+ struct ec_response_uptime_info *resp;
+ char read_buf[32];
+ int ret;
+
+ resp = (struct ec_response_uptime_info *)&msg.resp;
+
+ msg.cmd.command = EC_CMD_GET_UPTIME_INFO;
+ msg.cmd.insize = sizeof(*resp);
+
+ ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd);
+ if (ret < 0)
+ return ret;
+
+ ret = scnprintf(read_buf, sizeof(read_buf), "%u\n",
+ resp->time_since_ec_boot_ms);
+
+ return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret);
+}
+
+static const struct file_operations cros_ec_console_log_fops = {
.owner = THIS_MODULE,
.open = cros_ec_console_log_open,
.read = cros_ec_console_log_read,
@@ -250,13 +279,20 @@ const struct file_operations cros_ec_console_log_fops = {
.release = cros_ec_console_log_release,
};
-const struct file_operations cros_ec_pdinfo_fops = {
+static const struct file_operations cros_ec_pdinfo_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = cros_ec_pdinfo_read,
.llseek = default_llseek,
};
+static const struct file_operations cros_ec_uptime_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = cros_ec_uptime_read,
+ .llseek = default_llseek,
+};
+
static int ec_read_version_supported(struct cros_ec_dev *ec)
{
struct ec_params_get_cmd_versions_v1 *params;
@@ -408,6 +444,12 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
&cros_ec_pdinfo_fops);
+ debugfs_create_file("uptime", 0444, debug_info->dir, debug_info,
+ &cros_ec_uptime_fops);
+
+ debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
+ &ec->ec_dev->last_resume_result);
+
ec->debug_info = debug_info;
dev_set_drvdata(&pd->dev, ec);
diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c
new file mode 100644
index 000000000000..e504d255d5ce
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_ishtp.c
@@ -0,0 +1,763 @@
+// SPDX-License-Identifier: GPL-2.0
+// ISHTP interface for ChromeOS Embedded Controller
+//
+// Copyright (c) 2019, Intel Corporation.
+//
+// ISHTP client driver for talking to the Chrome OS EC firmware running
+// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
+// (ISH-TP).
+
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/intel-ish-client-if.h>
+
+/*
+ * ISH TX/RX ring buffer pool size
+ *
+ * The AP->ISH messages and corresponding ISH->AP responses are
+ * serialized. We need 1 TX and 1 RX buffer for these.
+ *
+ * The MKBP ISH->AP events are serialized. We need one additional RX
+ * buffer for them.
+ */
+#define CROS_ISH_CL_TX_RING_SIZE 8
+#define CROS_ISH_CL_RX_RING_SIZE 8
+
+/* ISH CrOS EC Host Commands */
+enum cros_ec_ish_channel {
+ CROS_EC_COMMAND = 1, /* AP->ISH message */
+ CROS_MKBP_EVENT = 2, /* ISH->AP events */
+};
+
+/*
+ * ISH firmware timeout for 1 message send failure is 1Hz, and the
+ * firmware will retry 2 times, so 3Hz is used for timeout.
+ */
+#define ISHTP_SEND_TIMEOUT (3 * HZ)
+
+/* ISH Transport CrOS EC ISH client unique GUID */
+static const guid_t cros_ish_guid =
+ GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
+ 0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
+
+struct header {
+ u8 channel;
+ u8 status;
+ u8 reserved[2];
+} __packed;
+
+struct cros_ish_out_msg {
+ struct header hdr;
+ struct ec_host_request ec_request;
+} __packed;
+
+struct cros_ish_in_msg {
+ struct header hdr;
+ struct ec_host_response ec_response;
+} __packed;
+
+#define IN_MSG_EC_RESPONSE_PREAMBLE \
+ offsetof(struct cros_ish_in_msg, ec_response)
+
+#define OUT_MSG_EC_REQUEST_PREAMBLE \
+ offsetof(struct cros_ish_out_msg, ec_request)
+
+#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
+
+/*
+ * The Read-Write Semaphore is used to prevent message TX or RX while
+ * the ishtp client is being initialized or undergoing reset.
+ *
+ * The readers are the kernel function calls responsible for IA->ISH
+ * and ISH->AP messaging.
+ *
+ * The writers are .reset() and .probe() function.
+ */
+DECLARE_RWSEM(init_lock);
+
+/**
+ * struct response_info - Encapsulate firmware response related
+ * information for passing between function ish_send() and
+ * process_recv() callback.
+ *
+ * @data: Copy the data received from firmware here.
+ * @max_size: Max size allocated for the @data buffer. If the received
+ * data exceeds this value, we log an error.
+ * @size: Actual size of data received from firmware.
+ * @error: 0 for success, negative error code for a failure in process_recv().
+ * @received: Set to true on receiving a valid firmware response to host command
+ * @wait_queue: Wait queue for host to wait for firmware response.
+ */
+struct response_info {
+ void *data;
+ size_t max_size;
+ size_t size;
+ int error;
+ bool received;
+ wait_queue_head_t wait_queue;
+};
+
+/**
+ * struct ishtp_cl_data - Encapsulate per ISH TP Client.
+ *
+ * @cros_ish_cl: ISHTP firmware client instance.
+ * @cl_device: ISHTP client device instance.
+ * @response: Response info passing between ish_send() and process_recv().
+ * @work_ishtp_reset: Work queue reset handling.
+ * @work_ec_evt: Work queue for EC events.
+ * @ec_dev: CrOS EC MFD device.
+ *
+ * This structure is used to store per client data.
+ */
+struct ishtp_cl_data {
+ struct ishtp_cl *cros_ish_cl;
+ struct ishtp_cl_device *cl_device;
+
+ /*
+ * Used for passing firmware response information between
+ * ish_send() and process_recv() callback.
+ */
+ struct response_info response;
+
+ struct work_struct work_ishtp_reset;
+ struct work_struct work_ec_evt;
+ struct cros_ec_device *ec_dev;
+};
+
+/**
+ * ish_evt_handler - ISH to AP event handler
+ * @work: Work struct
+ */
+static void ish_evt_handler(struct work_struct *work)
+{
+ struct ishtp_cl_data *client_data =
+ container_of(work, struct ishtp_cl_data, work_ec_evt);
+ struct cros_ec_device *ec_dev = client_data->ec_dev;
+
+ if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
+ blocking_notifier_call_chain(&ec_dev->event_notifier,
+ 0, ec_dev);
+ }
+}
+
+/**
+ * ish_send() - Send message from host to firmware
+ *
+ * @client_data: Client data instance
+ * @out_msg: Message buffer to be sent to firmware
+ * @out_size: Size of out going message
+ * @in_msg: Message buffer where the incoming data is copied. This buffer
+ * is allocated by calling
+ * @in_size: Max size of incoming message
+ *
+ * Return: Number of bytes copied in the in_msg on success, negative
+ * error code on failure.
+ */
+static int ish_send(struct ishtp_cl_data *client_data,
+ u8 *out_msg, size_t out_size,
+ u8 *in_msg, size_t in_size)
+{
+ int rv;
+ struct header *out_hdr = (struct header *)out_msg;
+ struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
+
+ dev_dbg(cl_data_to_dev(client_data),
+ "%s: channel=%02u status=%02u\n",
+ __func__, out_hdr->channel, out_hdr->status);
+
+ /* Setup for incoming response */
+ client_data->response.data = in_msg;
+ client_data->response.max_size = in_size;
+ client_data->response.error = 0;
+ client_data->response.received = false;
+
+ rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
+ if (rv) {
+ dev_err(cl_data_to_dev(client_data),
+ "ishtp_cl_send error %d\n", rv);
+ return rv;
+ }
+
+ wait_event_interruptible_timeout(client_data->response.wait_queue,
+ client_data->response.received,
+ ISHTP_SEND_TIMEOUT);
+ if (!client_data->response.received) {
+ dev_err(cl_data_to_dev(client_data),
+ "Timed out for response to host message\n");
+ return -ETIMEDOUT;
+ }
+
+ if (client_data->response.error < 0)
+ return client_data->response.error;
+
+ return client_data->response.size;
+}
+
+/**
+ * process_recv() - Received and parse incoming packet
+ * @cros_ish_cl: Client instance to get stats
+ * @rb_in_proc: Host interface message buffer
+ *
+ * Parse the incoming packet. If it is a response packet then it will
+ * update per instance flags and wake up the caller waiting to for the
+ * response. If it is an event packet then it will schedule event work.
+ */
+static void process_recv(struct ishtp_cl *cros_ish_cl,
+ struct ishtp_cl_rb *rb_in_proc)
+{
+ size_t data_len = rb_in_proc->buf_idx;
+ struct ishtp_cl_data *client_data =
+ ishtp_get_client_data(cros_ish_cl);
+ struct device *dev = cl_data_to_dev(client_data);
+ struct cros_ish_in_msg *in_msg =
+ (struct cros_ish_in_msg *)rb_in_proc->buffer.data;
+
+ /* Proceed only if reset or init is not in progress */
+ if (!down_read_trylock(&init_lock)) {
+ /* Free the buffer */
+ ishtp_cl_io_rb_recycle(rb_in_proc);
+ dev_warn(dev,
+ "Host is not ready to receive incoming messages\n");
+ return;
+ }
+
+ /*
+ * All firmware messages contain a header. Check the buffer size
+ * before accessing elements inside.
+ */
+ if (!rb_in_proc->buffer.data) {
+ dev_warn(dev, "rb_in_proc->buffer.data returned null");
+ client_data->response.error = -EBADMSG;
+ goto end_error;
+ }
+
+ if (data_len < sizeof(struct header)) {
+ dev_err(dev, "data size %zu is less than header %zu\n",
+ data_len, sizeof(struct header));
+ client_data->response.error = -EMSGSIZE;
+ goto end_error;
+ }
+
+ dev_dbg(dev, "channel=%02u status=%02u\n",
+ in_msg->hdr.channel, in_msg->hdr.status);
+
+ switch (in_msg->hdr.channel) {
+ case CROS_EC_COMMAND:
+ /* Sanity check */
+ if (!client_data->response.data) {
+ dev_err(dev,
+ "Receiving buffer is null. Should be allocated by calling function\n");
+ client_data->response.error = -EINVAL;
+ goto error_wake_up;
+ }
+
+ if (client_data->response.received) {
+ dev_err(dev,
+ "Previous firmware message not yet processed\n");
+ client_data->response.error = -EINVAL;
+ goto error_wake_up;
+ }
+
+ if (data_len > client_data->response.max_size) {
+ dev_err(dev,
+ "Received buffer size %zu is larger than allocated buffer %zu\n",
+ data_len, client_data->response.max_size);
+ client_data->response.error = -EMSGSIZE;
+ goto error_wake_up;
+ }
+
+ if (in_msg->hdr.status) {
+ dev_err(dev, "firmware returned status %d\n",
+ in_msg->hdr.status);
+ client_data->response.error = -EIO;
+ goto error_wake_up;
+ }
+
+ /* Update the actual received buffer size */
+ client_data->response.size = data_len;
+
+ /*
+ * Copy the buffer received in firmware response for the
+ * calling thread.
+ */
+ memcpy(client_data->response.data,
+ rb_in_proc->buffer.data, data_len);
+
+ /* Set flag before waking up the caller */
+ client_data->response.received = true;
+error_wake_up:
+ /* Wake the calling thread */
+ wake_up_interruptible(&client_data->response.wait_queue);
+
+ break;
+
+ case CROS_MKBP_EVENT:
+ /* The event system doesn't send any data in buffer */
+ schedule_work(&client_data->work_ec_evt);
+
+ break;
+
+ default:
+ dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
+ }
+
+end_error:
+ /* Free the buffer */
+ ishtp_cl_io_rb_recycle(rb_in_proc);
+
+ up_read(&init_lock);
+}
+
+/**
+ * ish_event_cb() - bus driver callback for incoming message
+ * @cl_device: ISHTP client device for which this message is targeted.
+ *
+ * Remove the packet from the list and process the message by calling
+ * process_recv.
+ */
+static void ish_event_cb(struct ishtp_cl_device *cl_device)
+{
+ struct ishtp_cl_rb *rb_in_proc;
+ struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+
+ while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
+ /* Decide what to do with received data */
+ process_recv(cros_ish_cl, rb_in_proc);
+ }
+}
+
+/**
+ * cros_ish_init() - Init function for ISHTP client
+ * @cros_ish_cl: ISHTP client instance
+ *
+ * This function complete the initializtion of the client.
+ *
+ * Return: 0 for success, negative error code for failure.
+ */
+static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
+{
+ int rv;
+ struct ishtp_device *dev;
+ struct ishtp_fw_client *fw_client;
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+
+ rv = ishtp_cl_link(cros_ish_cl);
+ if (rv) {
+ dev_err(cl_data_to_dev(client_data),
+ "ishtp_cl_link failed\n");
+ return rv;
+ }
+
+ dev = ishtp_get_ishtp_device(cros_ish_cl);
+
+ /* Connect to firmware client */
+ ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
+ ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
+
+ fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
+ if (!fw_client) {
+ dev_err(cl_data_to_dev(client_data),
+ "ish client uuid not found\n");
+ rv = -ENOENT;
+ goto err_cl_unlink;
+ }
+
+ ishtp_cl_set_fw_client_id(cros_ish_cl,
+ ishtp_get_fw_client_id(fw_client));
+ ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
+
+ rv = ishtp_cl_connect(cros_ish_cl);
+ if (rv) {
+ dev_err(cl_data_to_dev(client_data),
+ "client connect fail\n");
+ goto err_cl_unlink;
+ }
+
+ ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
+ return 0;
+
+err_cl_unlink:
+ ishtp_cl_unlink(cros_ish_cl);
+ return rv;
+}
+
+/**
+ * cros_ish_deinit() - Deinit function for ISHTP client
+ * @cros_ish_cl: ISHTP client instance
+ *
+ * Unlink and free cros_ec client
+ */
+static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
+{
+ ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
+ ishtp_cl_disconnect(cros_ish_cl);
+ ishtp_cl_unlink(cros_ish_cl);
+ ishtp_cl_flush_queues(cros_ish_cl);
+
+ /* Disband and free all Tx and Rx client-level rings */
+ ishtp_cl_free(cros_ish_cl);
+}
+
+/**
+ * prepare_cros_ec_rx() - Check & prepare receive buffer
+ * @ec_dev: CrOS EC MFD device.
+ * @in_msg: Incoming message buffer
+ * @msg: cros_ec command used to send & receive data
+ *
+ * Return: 0 for success, negative error code for failure.
+ *
+ * Check the received buffer. Convert to cros_ec_command format.
+ */
+static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
+ const struct cros_ish_in_msg *in_msg,
+ struct cros_ec_command *msg)
+{
+ u8 sum = 0;
+ int i, rv, offset;
+
+ /* Check response error code */
+ msg->result = in_msg->ec_response.result;
+ rv = cros_ec_check_result(ec_dev, msg);
+ if (rv < 0)
+ return rv;
+
+ if (in_msg->ec_response.data_len > msg->insize) {
+ dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
+ in_msg->ec_response.data_len, msg->insize);
+ return -ENOSPC;
+ }
+
+ /* Copy response packet payload and compute checksum */
+ for (i = 0; i < sizeof(struct ec_host_response); i++)
+ sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
+
+ offset = sizeof(struct cros_ish_in_msg);
+ for (i = 0; i < in_msg->ec_response.data_len; i++)
+ sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
+
+ if (sum) {
+ dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+
+static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
+ struct cros_ec_command *msg)
+{
+ int rv;
+ struct ishtp_cl *cros_ish_cl = ec_dev->priv;
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+ struct device *dev = cl_data_to_dev(client_data);
+ struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
+ struct cros_ish_out_msg *out_msg =
+ (struct cros_ish_out_msg *)ec_dev->dout;
+ size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
+ size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
+
+ /* Sanity checks */
+ if (in_size > ec_dev->din_size) {
+ dev_err(dev,
+ "Incoming payload size %zu is too large for ec_dev->din_size %d\n",
+ in_size, ec_dev->din_size);
+ return -EMSGSIZE;
+ }
+
+ if (out_size > ec_dev->dout_size) {
+ dev_err(dev,
+ "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
+ out_size, ec_dev->dout_size);
+ return -EMSGSIZE;
+ }
+
+ /* Proceed only if reset-init is not in progress */
+ if (!down_read_trylock(&init_lock)) {
+ dev_warn(dev,
+ "Host is not ready to send messages to ISH. Try again\n");
+ return -EAGAIN;
+ }
+
+ /* Prepare the package to be sent over ISH TP */
+ out_msg->hdr.channel = CROS_EC_COMMAND;
+ out_msg->hdr.status = 0;
+
+ ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
+ cros_ec_prepare_tx(ec_dev, msg);
+ ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
+
+ dev_dbg(dev,
+ "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
+ out_msg->ec_request.struct_version,
+ out_msg->ec_request.checksum,
+ out_msg->ec_request.command,
+ out_msg->ec_request.command_version,
+ out_msg->ec_request.data_len);
+
+ /* Send command to ISH EC firmware and read response */
+ rv = ish_send(client_data,
+ (u8 *)out_msg, out_size,
+ (u8 *)in_msg, in_size);
+ if (rv < 0)
+ goto end_error;
+
+ rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
+ if (rv)
+ goto end_error;
+
+ rv = in_msg->ec_response.data_len;
+
+ dev_dbg(dev,
+ "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
+ in_msg->ec_response.struct_version,
+ in_msg->ec_response.checksum,
+ in_msg->ec_response.result,
+ in_msg->ec_response.data_len);
+
+end_error:
+ if (msg->command == EC_CMD_REBOOT_EC)
+ msleep(EC_REBOOT_DELAY_MS);
+
+ up_read(&init_lock);
+
+ return rv;
+}
+
+static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
+{
+ struct cros_ec_device *ec_dev;
+ struct device *dev = cl_data_to_dev(client_data);
+
+ ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
+ if (!ec_dev)
+ return -ENOMEM;
+
+ client_data->ec_dev = ec_dev;
+ dev->driver_data = ec_dev;
+
+ ec_dev->dev = dev;
+ ec_dev->priv = client_data->cros_ish_cl;
+ ec_dev->cmd_xfer = NULL;
+ ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
+ ec_dev->phys_name = dev_name(dev);
+ ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
+ sizeof(struct ec_response_get_protocol_info);
+ ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
+
+ return cros_ec_register(ec_dev);
+}
+
+static void reset_handler(struct work_struct *work)
+{
+ int rv;
+ struct device *dev;
+ struct ishtp_cl *cros_ish_cl;
+ struct ishtp_cl_device *cl_device;
+ struct ishtp_cl_data *client_data =
+ container_of(work, struct ishtp_cl_data, work_ishtp_reset);
+
+ /* Lock for reset to complete */
+ down_write(&init_lock);
+
+ cros_ish_cl = client_data->cros_ish_cl;
+ cl_device = client_data->cl_device;
+
+ /* Unlink, flush queues & start again */
+ ishtp_cl_unlink(cros_ish_cl);
+ ishtp_cl_flush_queues(cros_ish_cl);
+ ishtp_cl_free(cros_ish_cl);
+
+ cros_ish_cl = ishtp_cl_allocate(cl_device);
+ if (!cros_ish_cl) {
+ up_write(&init_lock);
+ return;
+ }
+
+ ishtp_set_drvdata(cl_device, cros_ish_cl);
+ ishtp_set_client_data(cros_ish_cl, client_data);
+ client_data->cros_ish_cl = cros_ish_cl;
+
+ rv = cros_ish_init(cros_ish_cl);
+ if (rv) {
+ ishtp_cl_free(cros_ish_cl);
+ dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
+ up_write(&init_lock);
+ return;
+ }
+
+ /* Refresh ec_dev device pointers */
+ client_data->ec_dev->priv = client_data->cros_ish_cl;
+ dev = cl_data_to_dev(client_data);
+ dev->driver_data = client_data->ec_dev;
+
+ dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
+
+ up_write(&init_lock);
+}
+
+/**
+ * cros_ec_ishtp_probe() - ISHTP client driver probe callback
+ * @cl_device: ISHTP client device instance
+ *
+ * Return: 0 for success, negative error code for failure.
+ */
+static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
+{
+ int rv;
+ struct ishtp_cl *cros_ish_cl;
+ struct ishtp_cl_data *client_data =
+ devm_kzalloc(ishtp_device(cl_device),
+ sizeof(*client_data), GFP_KERNEL);
+ if (!client_data)
+ return -ENOMEM;
+
+ /* Lock for initialization to complete */
+ down_write(&init_lock);
+
+ cros_ish_cl = ishtp_cl_allocate(cl_device);
+ if (!cros_ish_cl) {
+ rv = -ENOMEM;
+ goto end_ishtp_cl_alloc_error;
+ }
+
+ ishtp_set_drvdata(cl_device, cros_ish_cl);
+ ishtp_set_client_data(cros_ish_cl, client_data);
+ client_data->cros_ish_cl = cros_ish_cl;
+ client_data->cl_device = cl_device;
+
+ init_waitqueue_head(&client_data->response.wait_queue);
+
+ INIT_WORK(&client_data->work_ishtp_reset,
+ reset_handler);
+ INIT_WORK(&client_data->work_ec_evt,
+ ish_evt_handler);
+
+ rv = cros_ish_init(cros_ish_cl);
+ if (rv)
+ goto end_ishtp_cl_init_error;
+
+ ishtp_get_device(cl_device);
+
+ up_write(&init_lock);
+
+ /* Register croc_ec_dev mfd */
+ rv = cros_ec_dev_init(client_data);
+ if (rv)
+ goto end_cros_ec_dev_init_error;
+
+ return 0;
+
+end_cros_ec_dev_init_error:
+ ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
+ ishtp_cl_disconnect(cros_ish_cl);
+ ishtp_cl_unlink(cros_ish_cl);
+ ishtp_cl_flush_queues(cros_ish_cl);
+ ishtp_put_device(cl_device);
+end_ishtp_cl_init_error:
+ ishtp_cl_free(cros_ish_cl);
+end_ishtp_cl_alloc_error:
+ up_write(&init_lock);
+ return rv;
+}
+
+/**
+ * cros_ec_ishtp_remove() - ISHTP client driver remove callback
+ * @cl_device: ISHTP client device instance
+ *
+ * Return: 0
+ */
+static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device)
+{
+ struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+
+ cancel_work_sync(&client_data->work_ishtp_reset);
+ cancel_work_sync(&client_data->work_ec_evt);
+ cros_ish_deinit(cros_ish_cl);
+ ishtp_put_device(cl_device);
+
+ return 0;
+}
+
+/**
+ * cros_ec_ishtp_reset() - ISHTP client driver reset callback
+ * @cl_device: ISHTP client device instance
+ *
+ * Return: 0
+ */
+static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
+{
+ struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+
+ schedule_work(&client_data->work_ishtp_reset);
+
+ return 0;
+}
+
+/**
+ * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
+ * @device: device instance
+ *
+ * Return: 0 for success, negative error code for failure.
+ */
+static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
+{
+ struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
+ struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+
+ return cros_ec_suspend(client_data->ec_dev);
+}
+
+/**
+ * cros_ec_ishtp_resume() - ISHTP client driver resume callback
+ * @device: device instance
+ *
+ * Return: 0 for success, negative error code for failure.
+ */
+static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
+{
+ struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
+ struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+ struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
+
+ return cros_ec_resume(client_data->ec_dev);
+}
+
+static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
+ cros_ec_ishtp_resume);
+
+static struct ishtp_cl_driver cros_ec_ishtp_driver = {
+ .name = "cros_ec_ishtp",
+ .guid = &cros_ish_guid,
+ .probe = cros_ec_ishtp_probe,
+ .remove = cros_ec_ishtp_remove,
+ .reset = cros_ec_ishtp_reset,
+ .driver = {
+ .pm = &cros_ec_ishtp_pm_ops,
+ },
+};
+
+static int __init cros_ec_ishtp_mod_init(void)
+{
+ return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
+}
+
+static void __exit cros_ec_ishtp_mod_exit(void)
+{
+ ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
+}
+
+module_init(cros_ec_ishtp_mod_init);
+module_exit(cros_ec_ishtp_mod_exit);
+
+MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
+MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ishtp:*");
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c
index d30a6650b0b5..609598bbb6c3 100644
--- a/drivers/platform/chrome/cros_ec_lightbar.c
+++ b/drivers/platform/chrome/cros_ec_lightbar.c
@@ -547,7 +547,7 @@ static struct attribute *__lb_cmds_attrs[] = {
NULL,
};
-struct attribute_group cros_ec_lightbar_attr_group = {
+static struct attribute_group cros_ec_lightbar_attr_group = {
.name = "lightbar",
.attrs = __lb_cmds_attrs,
};
@@ -600,7 +600,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd)
static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
{
- struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
if (userspace_control)
return 0;
@@ -610,7 +610,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
{
- struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
if (userspace_control)
return 0;
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
index c9c240fbe7c6..2c44c7f3322a 100644
--- a/drivers/platform/chrome/cros_ec_lpc.c
+++ b/drivers/platform/chrome/cros_ec_lpc.c
@@ -23,7 +23,7 @@
#include <linux/printk.h>
#include <linux/suspend.h>
-#include "cros_ec_lpc_reg.h"
+#include "cros_ec_lpc_mec.h"
#define DRV_NAME "cros_ec_lpcs"
#define ACPI_DRV_NAME "GOOG0004"
@@ -31,6 +31,96 @@
/* True if ACPI device is present */
static bool cros_ec_lpc_acpi_device_found;
+/**
+ * struct lpc_driver_ops - LPC driver operations
+ * @read: Copy length bytes from EC address offset into buffer dest. Returns
+ * the 8-bit checksum of all bytes read.
+ * @write: Copy length bytes from buffer msg into EC address offset. Returns
+ * the 8-bit checksum of all bytes written.
+ */
+struct lpc_driver_ops {
+ u8 (*read)(unsigned int offset, unsigned int length, u8 *dest);
+ u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg);
+};
+
+static struct lpc_driver_ops cros_ec_lpc_ops = { };
+
+/*
+ * A generic instance of the read function of struct lpc_driver_ops, used for
+ * the LPC EC.
+ */
+static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
+ u8 *dest)
+{
+ int sum = 0;
+ int i;
+
+ for (i = 0; i < length; ++i) {
+ dest[i] = inb(offset + i);
+ sum += dest[i];
+ }
+
+ /* Return checksum of all bytes read */
+ return sum;
+}
+
+/*
+ * A generic instance of the write function of struct lpc_driver_ops, used for
+ * the LPC EC.
+ */
+static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
+ const u8 *msg)
+{
+ int sum = 0;
+ int i;
+
+ for (i = 0; i < length; ++i) {
+ outb(msg[i], offset + i);
+ sum += msg[i];
+ }
+
+ /* Return checksum of all bytes written */
+ return sum;
+}
+
+/*
+ * An instance of the read function of struct lpc_driver_ops, used for the
+ * MEC variant of LPC EC.
+ */
+static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
+ u8 *dest)
+{
+ int in_range = cros_ec_lpc_mec_in_range(offset, length);
+
+ if (in_range < 0)
+ return 0;
+
+ return in_range ?
+ cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
+ offset - EC_HOST_CMD_REGION0,
+ length, dest) :
+ cros_ec_lpc_read_bytes(offset, length, dest);
+}
+
+/*
+ * An instance of the write function of struct lpc_driver_ops, used for the
+ * MEC variant of LPC EC.
+ */
+static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
+ const u8 *msg)
+{
+ int in_range = cros_ec_lpc_mec_in_range(offset, length);
+
+ if (in_range < 0)
+ return 0;
+
+ return in_range ?
+ cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
+ offset - EC_HOST_CMD_REGION0,
+ length, (u8 *)msg) :
+ cros_ec_lpc_write_bytes(offset, length, msg);
+}
+
static int ec_response_timed_out(void)
{
unsigned long one_second = jiffies + HZ;
@@ -38,7 +128,7 @@ static int ec_response_timed_out(void)
usleep_range(200, 300);
do {
- if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) &
+ if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) &
EC_LPC_STATUS_BUSY_MASK))
return 0;
usleep_range(100, 200);
@@ -58,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
ret = cros_ec_prepare_tx(ec, msg);
/* Write buffer */
- cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
+ cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
/* Here we go */
sum = EC_COMMAND_PROTOCOL_3;
- cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
+ cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
if (ec_response_timed_out()) {
dev_warn(ec->dev, "EC responsed timed out\n");
@@ -71,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
}
/* Check result */
- msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
+ msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
ret = cros_ec_check_result(ec, msg);
if (ret)
goto done;
/* Read back response */
dout = (u8 *)&response;
- sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
- dout);
+ sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
+ dout);
msg->result = response.result;
@@ -92,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
}
/* Read response and process checksum */
- sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
- sizeof(response), response.data_len,
- msg->data);
+ sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
+ sizeof(response), response.data_len,
+ msg->data);
if (sum) {
dev_err(ec->dev,
@@ -134,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
sum = msg->command + args.flags + args.command_version + args.data_size;
/* Copy data and update checksum */
- sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
- msg->data);
+ sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
+ msg->data);
/* Finalize checksum and write args */
args.checksum = sum;
- cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
- (u8 *)&args);
+ cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
+ (u8 *)&args);
/* Here we go */
sum = msg->command;
- cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
+ cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
if (ec_response_timed_out()) {
dev_warn(ec->dev, "EC responsed timed out\n");
@@ -153,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
}
/* Check result */
- msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
+ msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
ret = cros_ec_check_result(ec, msg);
if (ret)
goto done;
/* Read back args */
- cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
- (u8 *)&args);
+ cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
if (args.data_size > msg->insize) {
dev_err(ec->dev,
@@ -174,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
sum = msg->command + args.flags + args.command_version + args.data_size;
/* Read response and update checksum */
- sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
- msg->data);
+ sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
+ msg->data);
/* Verify checksum */
if (args.checksum != sum) {
@@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
/* fixed length */
if (bytes) {
- cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
+ cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
return bytes;
}
/* string */
for (; i < EC_MEMMAP_SIZE; i++, s++) {
- cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s);
+ cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s);
cnt++;
if (!*s)
break;
@@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
return -EBUSY;
}
- cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
+ /*
+ * Read the mapped ID twice, the first one is assuming the
+ * EC is a Microchip Embedded Controller (MEC) variant, if the
+ * protocol fails, fallback to the non MEC variant and try to
+ * read again the ID.
+ */
+ cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes;
+ cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes;
+ cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
if (buf[0] != 'E' || buf[1] != 'C') {
- dev_err(dev, "EC ID not detected\n");
- return -ENODEV;
+ /* Re-assign read/write operations for the non MEC variant */
+ cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes;
+ cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes;
+ cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2,
+ buf);
+ if (buf[0] != 'E' || buf[1] != 'C') {
+ dev_err(dev, "EC ID not detected\n");
+ return -ENODEV;
+ }
}
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
@@ -405,7 +509,7 @@ static int cros_ec_lpc_resume(struct device *dev)
}
#endif
-const struct dev_pm_ops cros_ec_lpc_pm_ops = {
+static const struct dev_pm_ops cros_ec_lpc_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume)
};
@@ -446,13 +550,14 @@ static int __init cros_ec_lpc_init(void)
return -ENODEV;
}
- cros_ec_lpc_reg_init();
+ cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
+ EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
/* Register the driver */
ret = platform_driver_register(&cros_ec_lpc_driver);
if (ret) {
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
- cros_ec_lpc_reg_destroy();
+ cros_ec_lpc_mec_destroy();
return ret;
}
@@ -462,7 +567,7 @@ static int __init cros_ec_lpc_init(void)
if (ret) {
pr_err(DRV_NAME ": can't register device: %d\n", ret);
platform_driver_unregister(&cros_ec_lpc_driver);
- cros_ec_lpc_reg_destroy();
+ cros_ec_lpc_mec_destroy();
}
}
@@ -474,7 +579,7 @@ static void __exit cros_ec_lpc_exit(void)
if (!cros_ec_lpc_acpi_device_found)
platform_device_unregister(&cros_ec_lpc_device);
platform_driver_unregister(&cros_ec_lpc_driver);
- cros_ec_lpc_reg_destroy();
+ cros_ec_lpc_mec_destroy();
}
module_init(cros_ec_lpc_init);
diff --git a/drivers/platform/chrome/cros_ec_lpc_mec.c b/drivers/platform/chrome/cros_ec_lpc_mec.c
index d8890bafb55d..9035b17e8c86 100644
--- a/drivers/platform/chrome/cros_ec_lpc_mec.c
+++ b/drivers/platform/chrome/cros_ec_lpc_mec.c
@@ -17,12 +17,10 @@
static struct mutex io_mutex;
static u16 mec_emi_base, mec_emi_end;
-/*
- * cros_ec_lpc_mec_emi_write_address
- *
- * Initialize EMI read / write at a given address.
+/**
+ * cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
*
- * @addr: Starting read / write address
+ * @addr: Starting read / write address
* @access_type: Type of access, typically 32-bit auto-increment
*/
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
@@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
return 0;
}
-/*
- * cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port
+/**
+ * cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port.
*
* @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request
* @offset: Base read / write address
* @length: Number of bytes to read / write
* @buf: Destination / source buffer
*
- * @return 8-bit checksum of all bytes read / written
+ * Return: 8-bit checksum of all bytes read / written
*/
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
unsigned int offset, unsigned int length,
diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.c b/drivers/platform/chrome/cros_ec_lpc_reg.c
deleted file mode 100644
index 0f5cd0ac8b49..000000000000
--- a/drivers/platform/chrome/cros_ec_lpc_reg.c
+++ /dev/null
@@ -1,101 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-// LPC interface for ChromeOS Embedded Controller
-//
-// Copyright (C) 2016 Google, Inc
-
-#include <linux/io.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
-
-#include "cros_ec_lpc_mec.h"
-
-static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
-{
- int i;
- int sum = 0;
-
- for (i = 0; i < length; ++i) {
- dest[i] = inb(offset + i);
- sum += dest[i];
- }
-
- /* Return checksum of all bytes read */
- return sum;
-}
-
-static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
-{
- int i;
- int sum = 0;
-
- for (i = 0; i < length; ++i) {
- outb(msg[i], offset + i);
- sum += msg[i];
- }
-
- /* Return checksum of all bytes written */
- return sum;
-}
-
-#ifdef CONFIG_CROS_EC_LPC_MEC
-
-u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
-{
- int in_range = cros_ec_lpc_mec_in_range(offset, length);
-
- if (in_range < 0)
- return 0;
-
- return in_range ?
- cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
- offset - EC_HOST_CMD_REGION0,
- length, dest) :
- lpc_read_bytes(offset, length, dest);
-}
-
-u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
-{
- int in_range = cros_ec_lpc_mec_in_range(offset, length);
-
- if (in_range < 0)
- return 0;
-
- return in_range ?
- cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
- offset - EC_HOST_CMD_REGION0,
- length, msg) :
- lpc_write_bytes(offset, length, msg);
-}
-
-void cros_ec_lpc_reg_init(void)
-{
- cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
- EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
-}
-
-void cros_ec_lpc_reg_destroy(void)
-{
- cros_ec_lpc_mec_destroy();
-}
-
-#else /* CONFIG_CROS_EC_LPC_MEC */
-
-u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
-{
- return lpc_read_bytes(offset, length, dest);
-}
-
-u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
-{
- return lpc_write_bytes(offset, length, msg);
-}
-
-void cros_ec_lpc_reg_init(void)
-{
-}
-
-void cros_ec_lpc_reg_destroy(void)
-{
-}
-
-#endif /* CONFIG_CROS_EC_LPC_MEC */
diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.h b/drivers/platform/chrome/cros_ec_lpc_reg.h
deleted file mode 100644
index 416fd2572182..000000000000
--- a/drivers/platform/chrome/cros_ec_lpc_reg.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * LPC interface for ChromeOS Embedded Controller
- *
- * Copyright (C) 2016 Google, Inc
- */
-
-#ifndef __CROS_EC_LPC_REG_H
-#define __CROS_EC_LPC_REG_H
-
-/**
- * cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address.
- * Returns 8-bit checksum of all bytes read.
- *
- * @offset: Base read address
- * @length: Number of bytes to read
- * @dest: Destination buffer
- */
-u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest);
-
-/**
- * cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address.
- * Returns 8-bit checksum of all bytes written.
- *
- * @offset: Base write address
- * @length: Number of bytes to write
- * @msg: Write data buffer
- */
-u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg);
-
-/**
- * cros_ec_lpc_reg_init
- *
- * Initialize register I/O.
- */
-void cros_ec_lpc_reg_init(void);
-
-/**
- * cros_ec_lpc_reg_destroy
- *
- * Cleanup reg I/O.
- */
-void cros_ec_lpc_reg_destroy(void);
-
-#endif /* __CROS_EC_LPC_REG_H */
diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c
index 8e9451720e73..006a8ff64057 100644
--- a/drivers/platform/chrome/cros_ec_spi.c
+++ b/drivers/platform/chrome/cros_ec_spi.c
@@ -12,7 +12,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
-
+#include <uapi/linux/sched/types.h>
/* The header byte, which follows the preamble */
#define EC_MSG_HEADER 0xec
@@ -67,12 +67,14 @@
* is sent when we want to turn on CS at the start of a transaction.
* @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that
* is sent when we want to turn off CS at the end of a transaction.
+ * @high_pri_worker: Used to schedule high priority work.
*/
struct cros_ec_spi {
struct spi_device *spi;
s64 last_transfer_ns;
unsigned int start_of_msg_delay;
unsigned int end_of_msg_delay;
+ struct kthread_worker *high_pri_worker;
};
typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
@@ -89,7 +91,7 @@ typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
*/
struct cros_ec_xfer_work_params {
- struct work_struct work;
+ struct kthread_work work;
cros_ec_xfer_fn_t fn;
struct cros_ec_device *ec_dev;
struct cros_ec_command *ec_msg;
@@ -632,7 +634,7 @@ exit:
return ret;
}
-static void cros_ec_xfer_high_pri_work(struct work_struct *work)
+static void cros_ec_xfer_high_pri_work(struct kthread_work *work)
{
struct cros_ec_xfer_work_params *params;
@@ -644,12 +646,14 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
struct cros_ec_command *ec_msg,
cros_ec_xfer_fn_t fn)
{
- struct cros_ec_xfer_work_params params;
-
- INIT_WORK_ONSTACK(&params.work, cros_ec_xfer_high_pri_work);
- params.ec_dev = ec_dev;
- params.ec_msg = ec_msg;
- params.fn = fn;
+ struct cros_ec_spi *ec_spi = ec_dev->priv;
+ struct cros_ec_xfer_work_params params = {
+ .work = KTHREAD_WORK_INIT(params.work,
+ cros_ec_xfer_high_pri_work),
+ .ec_dev = ec_dev,
+ .ec_msg = ec_msg,
+ .fn = fn,
+ };
/*
* This looks a bit ridiculous. Why do the work on a
@@ -660,9 +664,8 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
* context switched out for too long and the EC giving up on
* the transfer.
*/
- queue_work(system_highpri_wq, &params.work);
- flush_work(&params.work);
- destroy_work_on_stack(&params.work);
+ kthread_queue_work(ec_spi->high_pri_worker, &params.work);
+ kthread_flush_work(&params.work);
return params.ret;
}
@@ -694,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
ec_spi->end_of_msg_delay = val;
}
+static void cros_ec_spi_high_pri_release(void *worker)
+{
+ kthread_destroy_worker(worker);
+}
+
+static int cros_ec_spi_devm_high_pri_alloc(struct device *dev,
+ struct cros_ec_spi *ec_spi)
+{
+ struct sched_param sched_priority = {
+ .sched_priority = MAX_RT_PRIO - 1,
+ };
+ int err;
+
+ ec_spi->high_pri_worker =
+ kthread_create_worker(0, "cros_ec_spi_high_pri");
+
+ if (IS_ERR(ec_spi->high_pri_worker)) {
+ err = PTR_ERR(ec_spi->high_pri_worker);
+ dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err);
+ return err;
+ }
+
+ err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release,
+ ec_spi->high_pri_worker);
+ if (err)
+ return err;
+
+ err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task,
+ SCHED_FIFO, &sched_priority);
+ if (err)
+ dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err);
+ return err;
+}
+
static int cros_ec_spi_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -703,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi)
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
+ spi->rt = true;
err = spi_setup(spi);
if (err < 0)
return err;
@@ -732,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
ec_spi->last_transfer_ns = ktime_get_ns();
+ err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi);
+ if (err)
+ return err;
+
err = cros_ec_register(ec_dev);
if (err) {
dev_err(dev, "cannot register EC\n");
@@ -777,7 +819,7 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id);
static struct spi_driver cros_ec_driver_spi = {
.driver = {
.name = "cros-ec-spi",
- .of_match_table = of_match_ptr(cros_ec_spi_of_match),
+ .of_match_table = cros_ec_spi_of_match,
.pm = &cros_ec_spi_pm_ops,
},
.probe = cros_ec_spi_probe,
diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c
index fe0b7614ae1b..3edb237bf8ed 100644
--- a/drivers/platform/chrome/cros_ec_sysfs.c
+++ b/drivers/platform/chrome/cros_ec_sysfs.c
@@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
return a->mode;
}
-struct attribute_group cros_ec_attr_group = {
+static struct attribute_group cros_ec_attr_group = {
.attrs = __ec_attrs,
.is_visible = cros_ec_ctrl_visible,
};
diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c
index 8392a1ec33a7..2aaefed87eb4 100644
--- a/drivers/platform/chrome/cros_ec_vbc.c
+++ b/drivers/platform/chrome/cros_ec_vbc.c
@@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = {
NULL
};
-struct attribute_group cros_ec_vbc_attr_group = {
+static struct attribute_group cros_ec_vbc_attr_group = {
.name = "vbc",
.bin_attrs = cros_ec_vbc_bin_attrs,
};
diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig
index fd29cbfd3d5d..89007b0bc743 100644
--- a/drivers/platform/chrome/wilco_ec/Kconfig
+++ b/drivers/platform/chrome/wilco_ec/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config WILCO_EC
tristate "ChromeOS Wilco Embedded Controller"
- depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC
+ depends on ACPI && X86 && CROS_EC_LPC
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
@@ -19,3 +19,19 @@ config WILCO_EC_DEBUGFS
manipulation and allow for testing arbitrary commands. This
interface is intended for debug only and will not be present
on production devices.
+
+config WILCO_EC_EVENTS
+ tristate "Enable event forwarding from EC to userspace"
+ depends on WILCO_EC
+ help
+ If you say Y here, you get support for the EC to send events
+ (such as power state changes) to userspace. The EC sends the events
+ over ACPI, and a driver queues up the events to be read by a
+ userspace daemon from /dev/wilco_event using read() and poll().
+
+config WILCO_EC_TELEMETRY
+ tristate "Enable querying telemetry data from EC"
+ depends on WILCO_EC
+ help
+ If you say Y here, you get support to query EC telemetry data from
+ /dev/wilco_telem0 using write() and then read().
diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
index 063e7fb4ea17..bc817164596e 100644
--- a/drivers/platform/chrome/wilco_ec/Makefile
+++ b/drivers/platform/chrome/wilco_ec/Makefile
@@ -1,6 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
-wilco_ec-objs := core.o mailbox.o
+wilco_ec-objs := core.o mailbox.o properties.o sysfs.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
wilco_ec_debugfs-objs := debugfs.o
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
+wilco_ec_events-objs := event.o
+obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o
+wilco_ec_telem-objs := telemetry.o
+obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o
diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
index 05e1e2be1c91..3724bf4b77c6 100644
--- a/drivers/platform/chrome/wilco_ec/core.c
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
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_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
if (!ec->data_buffer)
return -ENOMEM;
@@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev)
goto unregister_debugfs;
}
+ ret = wilco_ec_add_sysfs(ec);
+ if (ret < 0) {
+ dev_err(dev, "Failed to create sysfs entries: %d", ret);
+ goto unregister_rtc;
+ }
+
+ /* Register child device that will be found by the telemetry driver. */
+ ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
+ PLATFORM_DEVID_AUTO,
+ ec, sizeof(*ec));
+ if (IS_ERR(ec->telem_pdev)) {
+ dev_err(dev, "Failed to create telemetry platform device\n");
+ ret = PTR_ERR(ec->telem_pdev);
+ goto remove_sysfs;
+ }
+
return 0;
+remove_sysfs:
+ wilco_ec_remove_sysfs(ec);
+unregister_rtc:
+ platform_device_unregister(ec->rtc_pdev);
unregister_debugfs:
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
@@ -102,6 +120,8 @@ static int wilco_ec_remove(struct platform_device *pdev)
{
struct wilco_ec_device *ec = platform_get_drvdata(pdev);
+ wilco_ec_remove_sysfs(ec);
+ platform_device_unregister(ec->telem_pdev);
platform_device_unregister(ec->rtc_pdev);
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c
index f163476d080d..8d65a1e2f1a3 100644
--- a/drivers/platform/chrome/wilco_ec/debugfs.c
+++ b/drivers/platform/chrome/wilco_ec/debugfs.c
@@ -16,14 +16,14 @@
#define DRV_NAME "wilco-ec-debugfs"
-/* The 256 raw bytes will take up more space when represented as a hex string */
-#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4)
+/* The raw bytes will take up more space when represented as a hex string */
+#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
struct wilco_ec_debugfs {
struct wilco_ec_device *ec;
struct dentry *dir;
size_t response_size;
- u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED];
+ u8 raw_data[EC_MAILBOX_DATA_SIZE];
u8 formatted_data[FORMATTED_BUFFER_SIZE];
};
static struct wilco_ec_debugfs *debug_info;
@@ -124,12 +124,6 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf,
msg.response_data = debug_info->raw_data;
msg.response_size = EC_MAILBOX_DATA_SIZE;
- /* Telemetry commands use extended response data */
- if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) {
- msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA;
- msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED;
- }
-
ret = wilco_ec_mailbox(debug_info->ec, &msg);
if (ret < 0)
return ret;
diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c
new file mode 100644
index 000000000000..dba3d445623f
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/event.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI event handling for Wilco Embedded Controller
+ *
+ * Copyright 2019 Google LLC
+ *
+ * The Wilco Embedded Controller can create custom events that
+ * are not handled as standard ACPI objects. These events can
+ * contain information about changes in EC controlled features,
+ * such as errors and events in the dock or display. For example,
+ * an event is triggered if the dock is plugged into a display
+ * incorrectly. These events are needed for telemetry and
+ * diagnostics reasons, and for possibly alerting the user.
+
+ * These events are triggered by the EC with an ACPI Notify(0x90),
+ * and then the BIOS reads the event buffer from EC RAM via an
+ * ACPI method. When the OS receives these events via ACPI,
+ * it passes them along to this driver. The events are put into
+ * a queue which can be read by a userspace daemon via a char device
+ * that implements read() and poll(). The event queue acts as a
+ * circular buffer of size 64, so if there are no userspace consumers
+ * the kernel will not run out of memory. The char device will appear at
+ * /dev/wilco_event{n}, where n is some small non-negative integer,
+ * starting from 0. Standard ACPI events such as the battery getting
+ * plugged/unplugged can also come through this path, but they are
+ * dealt with via other paths, and are ignored here.
+
+ * To test, you can tail the binary data with
+ * $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"'
+ * and then create an event by plugging/unplugging the battery.
+ */
+
+#include <linux/acpi.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+/* ACPI Notify event code indicating event data is available. */
+#define EC_ACPI_NOTIFY_EVENT 0x90
+/* ACPI Method to execute to retrieve event data buffer from the EC. */
+#define EC_ACPI_GET_EVENT "QSET"
+/* Maximum number of words in event data returned by the EC. */
+#define EC_ACPI_MAX_EVENT_WORDS 6
+#define EC_ACPI_MAX_EVENT_SIZE \
+ (sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16))
+
+/* Node will appear in /dev/EVENT_DEV_NAME */
+#define EVENT_DEV_NAME "wilco_event"
+#define EVENT_CLASS_NAME EVENT_DEV_NAME
+#define DRV_NAME EVENT_DEV_NAME
+#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d")
+static struct class event_class = {
+ .owner = THIS_MODULE,
+ .name = EVENT_CLASS_NAME,
+};
+
+/* Keep track of all the device numbers used. */
+#define EVENT_MAX_DEV 128
+static int event_major;
+static DEFINE_IDA(event_ida);
+
+/* Size of circular queue of events. */
+#define MAX_NUM_EVENTS 64
+
+/**
+ * struct ec_event - Extended event returned by the EC.
+ * @size: Number of 16bit words in structure after the size word.
+ * @type: Extended event type, meaningless for us.
+ * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS.
+ */
+struct ec_event {
+ u16 size;
+ u16 type;
+ u16 event[0];
+} __packed;
+
+#define ec_event_num_words(ev) (ev->size - 1)
+#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16)))
+
+/**
+ * struct ec_event_queue - Circular queue for events.
+ * @capacity: Number of elements the queue can hold.
+ * @head: Next index to write to.
+ * @tail: Next index to read from.
+ * @entries: Array of events.
+ */
+struct ec_event_queue {
+ int capacity;
+ int head;
+ int tail;
+ struct ec_event *entries[0];
+};
+
+/* Maximum number of events to store in ec_event_queue */
+static int queue_size = 64;
+module_param(queue_size, int, 0644);
+
+static struct ec_event_queue *event_queue_new(int capacity)
+{
+ struct ec_event_queue *q;
+
+ q = kzalloc(struct_size(q, entries, capacity), GFP_KERNEL);
+ if (!q)
+ return NULL;
+
+ q->capacity = capacity;
+
+ return q;
+}
+
+static inline bool event_queue_empty(struct ec_event_queue *q)
+{
+ /* head==tail when both full and empty, but head==NULL when empty */
+ return q->head == q->tail && !q->entries[q->head];
+}
+
+static inline bool event_queue_full(struct ec_event_queue *q)
+{
+ /* head==tail when both full and empty, but head!=NULL when full */
+ return q->head == q->tail && q->entries[q->head];
+}
+
+static struct ec_event *event_queue_pop(struct ec_event_queue *q)
+{
+ struct ec_event *ev;
+
+ if (event_queue_empty(q))
+ return NULL;
+
+ ev = q->entries[q->tail];
+ q->entries[q->tail] = NULL;
+ q->tail = (q->tail + 1) % q->capacity;
+
+ return ev;
+}
+
+/*
+ * If full, overwrite the oldest event and return it so the caller
+ * can kfree it. If not full, return NULL.
+ */
+static struct ec_event *event_queue_push(struct ec_event_queue *q,
+ struct ec_event *ev)
+{
+ struct ec_event *popped = NULL;
+
+ if (event_queue_full(q))
+ popped = event_queue_pop(q);
+ q->entries[q->head] = ev;
+ q->head = (q->head + 1) % q->capacity;
+
+ return popped;
+}
+
+static void event_queue_free(struct ec_event_queue *q)
+{
+ struct ec_event *event;
+
+ while ((event = event_queue_pop(q)) != NULL)
+ kfree(event);
+
+ kfree(q);
+}
+
+/**
+ * struct event_device_data - Data for a Wilco EC device that responds to ACPI.
+ * @events: Circular queue of EC events to be provided to userspace.
+ * @queue_lock: Protect the queue from simultaneous read/writes.
+ * @wq: Wait queue to notify processes when events are available or the
+ * device has been removed.
+ * @cdev: Char dev that userspace reads() and polls() from.
+ * @dev: Device associated with the %cdev.
+ * @exist: Has the device been not been removed? Once a device has been removed,
+ * writes, reads, and new opens will fail.
+ * @available: Guarantee only one client can open() file and read from queue.
+ *
+ * There will be one of these structs for each ACPI device registered. This data
+ * is the queue of events received from ACPI that still need to be read from
+ * userspace, the device and char device that userspace is using, a wait queue
+ * used to notify different threads when something has changed, plus a flag
+ * on whether the ACPI device has been removed.
+ */
+struct event_device_data {
+ struct ec_event_queue *events;
+ spinlock_t queue_lock;
+ wait_queue_head_t wq;
+ struct device dev;
+ struct cdev cdev;
+ bool exist;
+ atomic_t available;
+};
+
+/**
+ * enqueue_events() - Place EC events in queue to be read by userspace.
+ * @adev: Device the events came from.
+ * @buf: Buffer of event data.
+ * @length: Length of event data buffer.
+ *
+ * %buf contains a number of ec_event's, packed one after the other.
+ * Each ec_event is of variable length. Start with the first event, copy it
+ * into a persistent ec_event, store that entry in the queue, move on
+ * to the next ec_event in buf, and repeat.
+ *
+ * Return: 0 on success or negative error code on failure.
+ */
+static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length)
+{
+ struct event_device_data *dev_data = adev->driver_data;
+ struct ec_event *event, *queue_event, *old_event;
+ size_t num_words, event_size;
+ u32 offset = 0;
+
+ while (offset < length) {
+ event = (struct ec_event *)(buf + offset);
+
+ num_words = ec_event_num_words(event);
+ event_size = ec_event_size(event);
+ if (num_words > EC_ACPI_MAX_EVENT_WORDS) {
+ dev_err(&adev->dev, "Too many event words: %zu > %d\n",
+ num_words, EC_ACPI_MAX_EVENT_WORDS);
+ return -EOVERFLOW;
+ }
+
+ /* Ensure event does not overflow the available buffer */
+ if ((offset + event_size) > length) {
+ dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n",
+ offset + event_size, length);
+ return -EOVERFLOW;
+ }
+
+ /* Point to the next event in the buffer */
+ offset += event_size;
+
+ /* Copy event into the queue */
+ queue_event = kmemdup(event, event_size, GFP_KERNEL);
+ if (!queue_event)
+ return -ENOMEM;
+ spin_lock(&dev_data->queue_lock);
+ old_event = event_queue_push(dev_data->events, queue_event);
+ spin_unlock(&dev_data->queue_lock);
+ kfree(old_event);
+ wake_up_interruptible(&dev_data->wq);
+ }
+
+ return 0;
+}
+
+/**
+ * event_device_notify() - Callback when EC generates an event over ACPI.
+ * @adev: The device that the event is coming from.
+ * @value: Value passed to Notify() in ACPI.
+ *
+ * This function will read the events from the device and enqueue them.
+ */
+static void event_device_notify(struct acpi_device *adev, u32 value)
+{
+ struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ if (value != EC_ACPI_NOTIFY_EVENT) {
+ dev_err(&adev->dev, "Invalid event: 0x%08x\n", value);
+ return;
+ }
+
+ /* Execute ACPI method to get event data buffer. */
+ status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT,
+ NULL, &event_buffer);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "Error executing ACPI method %s()\n",
+ EC_ACPI_GET_EVENT);
+ return;
+ }
+
+ obj = (union acpi_object *)event_buffer.pointer;
+ if (!obj) {
+ dev_err(&adev->dev, "Nothing returned from %s()\n",
+ EC_ACPI_GET_EVENT);
+ return;
+ }
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ dev_err(&adev->dev, "Invalid object returned from %s()\n",
+ EC_ACPI_GET_EVENT);
+ kfree(obj);
+ return;
+ }
+ if (obj->buffer.length < sizeof(struct ec_event)) {
+ dev_err(&adev->dev, "Invalid buffer length %d from %s()\n",
+ obj->buffer.length, EC_ACPI_GET_EVENT);
+ kfree(obj);
+ return;
+ }
+
+ enqueue_events(adev, obj->buffer.pointer, obj->buffer.length);
+ kfree(obj);
+}
+
+static int event_open(struct inode *inode, struct file *filp)
+{
+ struct event_device_data *dev_data;
+
+ dev_data = container_of(inode->i_cdev, struct event_device_data, cdev);
+ if (!dev_data->exist)
+ return -ENODEV;
+
+ if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
+ return -EBUSY;
+
+ /* Increase refcount on device so dev_data is not freed */
+ get_device(&dev_data->dev);
+ stream_open(inode, filp);
+ filp->private_data = dev_data;
+
+ return 0;
+}
+
+static __poll_t event_poll(struct file *filp, poll_table *wait)
+{
+ struct event_device_data *dev_data = filp->private_data;
+ __poll_t mask = 0;
+
+ poll_wait(filp, &dev_data->wq, wait);
+ if (!dev_data->exist)
+ return EPOLLHUP;
+ if (!event_queue_empty(dev_data->events))
+ mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI;
+ return mask;
+}
+
+/**
+ * event_read() - Callback for passing event data to userspace via read().
+ * @filp: The file we are reading from.
+ * @buf: Pointer to userspace buffer to fill with one event.
+ * @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE.
+ * @pos: File position pointer, irrelevant since we don't support seeking.
+ *
+ * Removes the first event from the queue, places it in the passed buffer.
+ *
+ * If there are no events in the the queue, then one of two things happens,
+ * depending on if the file was opened in nonblocking mode: If in nonblocking
+ * mode, then return -EAGAIN to say there's no data. If in blocking mode, then
+ * block until an event is available.
+ *
+ * Return: Number of bytes placed in buffer, negative error code on failure.
+ */
+static ssize_t event_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *pos)
+{
+ struct event_device_data *dev_data = filp->private_data;
+ struct ec_event *event;
+ ssize_t n_bytes_written = 0;
+ int err;
+
+ /* We only will give them the entire event at once */
+ if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE)
+ return -EINVAL;
+
+ spin_lock(&dev_data->queue_lock);
+ while (event_queue_empty(dev_data->events)) {
+ spin_unlock(&dev_data->queue_lock);
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ err = wait_event_interruptible(dev_data->wq,
+ !event_queue_empty(dev_data->events) ||
+ !dev_data->exist);
+ if (err)
+ return err;
+
+ /* Device was removed as we waited? */
+ if (!dev_data->exist)
+ return -ENODEV;
+ spin_lock(&dev_data->queue_lock);
+ }
+ event = event_queue_pop(dev_data->events);
+ spin_unlock(&dev_data->queue_lock);
+ n_bytes_written = ec_event_size(event);
+ if (copy_to_user(buf, event, n_bytes_written))
+ n_bytes_written = -EFAULT;
+ kfree(event);
+
+ return n_bytes_written;
+}
+
+static int event_release(struct inode *inode, struct file *filp)
+{
+ struct event_device_data *dev_data = filp->private_data;
+
+ atomic_set(&dev_data->available, 1);
+ put_device(&dev_data->dev);
+
+ return 0;
+}
+
+static const struct file_operations event_fops = {
+ .open = event_open,
+ .poll = event_poll,
+ .read = event_read,
+ .release = event_release,
+ .llseek = no_llseek,
+ .owner = THIS_MODULE,
+};
+
+/**
+ * free_device_data() - Callback to free the event_device_data structure.
+ * @d: The device embedded in our device data, which we have been ref counting.
+ *
+ * This is called only after event_device_remove() has been called and all
+ * userspace programs have called event_release() on all the open file
+ * descriptors.
+ */
+static void free_device_data(struct device *d)
+{
+ struct event_device_data *dev_data;
+
+ dev_data = container_of(d, struct event_device_data, dev);
+ event_queue_free(dev_data->events);
+ kfree(dev_data);
+}
+
+static void hangup_device(struct event_device_data *dev_data)
+{
+ dev_data->exist = false;
+ /* Wake up the waiting processes so they can close. */
+ wake_up_interruptible(&dev_data->wq);
+ put_device(&dev_data->dev);
+}
+
+/**
+ * event_device_add() - Callback when creating a new device.
+ * @adev: ACPI device that we will be receiving events from.
+ *
+ * This finds a free minor number for the device, allocates and initializes
+ * some device data, and creates a new device and char dev node.
+ *
+ * The device data is freed in free_device_data(), which is called when
+ * %dev_data->dev is release()ed. This happens after all references to
+ * %dev_data->dev are dropped, which happens once both event_device_remove()
+ * has been called and every open()ed file descriptor has been release()ed.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int event_device_add(struct acpi_device *adev)
+{
+ struct event_device_data *dev_data;
+ int error, minor;
+
+ minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL);
+ if (minor < 0) {
+ error = minor;
+ dev_err(&adev->dev, "Failed to find minor number: %d\n", error);
+ return error;
+ }
+
+ dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
+ if (!dev_data) {
+ error = -ENOMEM;
+ goto free_minor;
+ }
+
+ /* Initialize the device data. */
+ adev->driver_data = dev_data;
+ dev_data->events = event_queue_new(queue_size);
+ if (!dev_data->events) {
+ kfree(dev_data);
+ error = -ENOMEM;
+ goto free_minor;
+ }
+ spin_lock_init(&dev_data->queue_lock);
+ init_waitqueue_head(&dev_data->wq);
+ dev_data->exist = true;
+ atomic_set(&dev_data->available, 1);
+
+ /* Initialize the device. */
+ dev_data->dev.devt = MKDEV(event_major, minor);
+ dev_data->dev.class = &event_class;
+ dev_data->dev.release = free_device_data;
+ dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor);
+ device_initialize(&dev_data->dev);
+
+ /* Initialize the character device, and add it to userspace. */
+ cdev_init(&dev_data->cdev, &event_fops);
+ error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
+ if (error)
+ goto free_dev_data;
+
+ return 0;
+
+free_dev_data:
+ hangup_device(dev_data);
+free_minor:
+ ida_simple_remove(&event_ida, minor);
+ return error;
+}
+
+static int event_device_remove(struct acpi_device *adev)
+{
+ struct event_device_data *dev_data = adev->driver_data;
+
+ cdev_device_del(&dev_data->cdev, &dev_data->dev);
+ ida_simple_remove(&event_ida, MINOR(dev_data->dev.devt));
+ hangup_device(dev_data);
+
+ return 0;
+}
+
+static const struct acpi_device_id event_acpi_ids[] = {
+ { "GOOG000D", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, event_acpi_ids);
+
+static struct acpi_driver event_driver = {
+ .name = DRV_NAME,
+ .class = DRV_NAME,
+ .ids = event_acpi_ids,
+ .ops = {
+ .add = event_device_add,
+ .notify = event_device_notify,
+ .remove = event_device_remove,
+ },
+ .owner = THIS_MODULE,
+};
+
+static int __init event_module_init(void)
+{
+ dev_t dev_num = 0;
+ int ret;
+
+ ret = class_register(&event_class);
+ if (ret) {
+ pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
+ return ret;
+ }
+
+ /* Request device numbers, starting with minor=0. Save the major num. */
+ ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME);
+ if (ret) {
+ pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
+ goto destroy_class;
+ }
+ event_major = MAJOR(dev_num);
+
+ ret = acpi_bus_register_driver(&event_driver);
+ if (ret < 0) {
+ pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
+ goto unregister_region;
+ }
+
+ return 0;
+
+unregister_region:
+ unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
+destroy_class:
+ class_unregister(&event_class);
+ ida_destroy(&event_ida);
+ return ret;
+}
+
+static void __exit event_module_exit(void)
+{
+ acpi_bus_unregister_driver(&event_driver);
+ unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
+ class_unregister(&event_class);
+ ida_destroy(&event_ida);
+}
+
+module_init(event_module_init);
+module_exit(event_module_exit);
+
+MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
+MODULE_DESCRIPTION("Wilco EC ACPI event driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c
index 7fb58b487963..ced1f9f3dcee 100644
--- a/drivers/platform/chrome/wilco_ec/mailbox.c
+++ b/drivers/platform/chrome/wilco_ec/mailbox.c
@@ -119,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
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);
@@ -148,21 +147,11 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
return -EIO;
}
- /*
- * The EC always returns either EC_MAILBOX_DATA_SIZE or
- * EC_MAILBOX_DATA_SIZE_EXTENDED bytes of data, so we need to
- * calculate the checksum on **all** of this data, even if we
- * won't use all of it.
- */
- 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);
+ sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
+ (u8 *)rs);
if (checksum) {
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
return -EBADMSG;
@@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
return -EBADMSG;
}
- if (rs->data_size != size) {
- dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
- rs->data_size, size);
+ if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
+ dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
+ rs->data_size, EC_MAILBOX_DATA_SIZE);
return -EMSGSIZE;
}
diff --git a/drivers/platform/chrome/wilco_ec/properties.c b/drivers/platform/chrome/wilco_ec/properties.c
new file mode 100644
index 000000000000..e69682c95ea2
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/properties.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/string.h>
+#include <linux/unaligned/le_memmove.h>
+
+/* Operation code; what the EC should do with the property */
+enum ec_property_op {
+ EC_OP_GET = 0,
+ EC_OP_SET = 1,
+};
+
+struct ec_property_request {
+ u8 op; /* One of enum ec_property_op */
+ u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
+ u8 length;
+ u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
+} __packed;
+
+struct ec_property_response {
+ u8 reserved[2];
+ u8 op; /* One of enum ec_property_op */
+ u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
+ u8 length;
+ u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
+} __packed;
+
+static int send_property_msg(struct wilco_ec_device *ec,
+ struct ec_property_request *rq,
+ struct ec_property_response *rs)
+{
+ struct wilco_ec_message ec_msg;
+ int ret;
+
+ memset(&ec_msg, 0, sizeof(ec_msg));
+ ec_msg.type = WILCO_EC_MSG_PROPERTY;
+ ec_msg.request_data = rq;
+ ec_msg.request_size = sizeof(*rq);
+ ec_msg.response_data = rs;
+ ec_msg.response_size = sizeof(*rs);
+
+ ret = wilco_ec_mailbox(ec, &ec_msg);
+ if (ret < 0)
+ return ret;
+ if (rs->op != rq->op)
+ return -EBADMSG;
+ if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id)))
+ return -EBADMSG;
+
+ return 0;
+}
+
+int wilco_ec_get_property(struct wilco_ec_device *ec,
+ struct wilco_ec_property_msg *prop_msg)
+{
+ struct ec_property_request rq;
+ struct ec_property_response rs;
+ int ret;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.op = EC_OP_GET;
+ put_unaligned_le32(prop_msg->property_id, rq.property_id);
+
+ ret = send_property_msg(ec, &rq, &rs);
+ if (ret < 0)
+ return ret;
+
+ prop_msg->length = rs.length;
+ memcpy(prop_msg->data, rs.data, rs.length);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wilco_ec_get_property);
+
+int wilco_ec_set_property(struct wilco_ec_device *ec,
+ struct wilco_ec_property_msg *prop_msg)
+{
+ struct ec_property_request rq;
+ struct ec_property_response rs;
+ int ret;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.op = EC_OP_SET;
+ put_unaligned_le32(prop_msg->property_id, rq.property_id);
+ rq.length = prop_msg->length;
+ memcpy(rq.data, prop_msg->data, prop_msg->length);
+
+ ret = send_property_msg(ec, &rq, &rs);
+ if (ret < 0)
+ return ret;
+ if (rs.length != prop_msg->length)
+ return -EBADMSG;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wilco_ec_set_property);
+
+int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
+ u8 *val)
+{
+ struct wilco_ec_property_msg msg;
+ int ret;
+
+ msg.property_id = property_id;
+
+ ret = wilco_ec_get_property(ec, &msg);
+ if (ret < 0)
+ return ret;
+ if (msg.length != 1)
+ return -EBADMSG;
+
+ *val = msg.data[0];
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property);
+
+int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
+ u8 val)
+{
+ struct wilco_ec_property_msg msg;
+
+ msg.property_id = property_id;
+ msg.data[0] = val;
+ msg.length = 1;
+
+ return wilco_ec_set_property(ec, &msg);
+}
+EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property);
diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c
new file mode 100644
index 000000000000..3b86a21005d3
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/sysfs.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Sysfs properties to view and modify EC-controlled features on Wilco devices.
+ * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
+ *
+ * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
+ */
+
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/sysfs.h>
+
+#define CMD_KB_CMOS 0x7C
+#define SUB_CMD_KB_CMOS_AUTO_ON 0x03
+
+struct boot_on_ac_request {
+ u8 cmd; /* Always CMD_KB_CMOS */
+ u8 reserved1;
+ u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */
+ u8 reserved3to5[3];
+ u8 val; /* Either 0 or 1 */
+ u8 reserved7;
+} __packed;
+
+#define CMD_EC_INFO 0x38
+enum get_ec_info_op {
+ CMD_GET_EC_LABEL = 0,
+ CMD_GET_EC_REV = 1,
+ CMD_GET_EC_MODEL = 2,
+ CMD_GET_EC_BUILD_DATE = 3,
+};
+
+struct get_ec_info_req {
+ u8 cmd; /* Always CMD_EC_INFO */
+ u8 reserved;
+ u8 op; /* One of enum get_ec_info_op */
+} __packed;
+
+struct get_ec_info_resp {
+ u8 reserved[2];
+ char value[9]; /* __nonstring: might not be null terminated */
+} __packed;
+
+static ssize_t boot_on_ac_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev);
+ struct boot_on_ac_request rq;
+ struct wilco_ec_message msg;
+ int ret;
+ u8 val;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+ if (val > 1)
+ return -EINVAL;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.cmd = CMD_KB_CMOS;
+ rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
+ rq.val = val;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.type = WILCO_EC_MSG_LEGACY;
+ msg.request_data = &rq;
+ msg.request_size = sizeof(rq);
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(boot_on_ac);
+
+static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev);
+ struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
+ struct get_ec_info_resp resp;
+ int ret;
+
+ struct wilco_ec_message msg = {
+ .type = WILCO_EC_MSG_LEGACY,
+ .request_data = &req,
+ .request_size = sizeof(req),
+ .response_data = &resp,
+ .response_size = sizeof(resp),
+ };
+
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value),
+ (char *)&resp.value);
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return get_info(dev, buf, CMD_GET_EC_LABEL);
+}
+
+static DEVICE_ATTR_RO(version);
+
+static ssize_t build_revision_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return get_info(dev, buf, CMD_GET_EC_REV);
+}
+
+static DEVICE_ATTR_RO(build_revision);
+
+static ssize_t build_date_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
+}
+
+static DEVICE_ATTR_RO(build_date);
+
+static ssize_t model_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return get_info(dev, buf, CMD_GET_EC_MODEL);
+}
+
+static DEVICE_ATTR_RO(model_number);
+
+
+static struct attribute *wilco_dev_attrs[] = {
+ &dev_attr_boot_on_ac.attr,
+ &dev_attr_build_date.attr,
+ &dev_attr_build_revision.attr,
+ &dev_attr_model_number.attr,
+ &dev_attr_version.attr,
+ NULL,
+};
+
+static struct attribute_group wilco_dev_attr_group = {
+ .attrs = wilco_dev_attrs,
+};
+
+int wilco_ec_add_sysfs(struct wilco_ec_device *ec)
+{
+ return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
+}
+
+void wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
+{
+ sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
+}
diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c
new file mode 100644
index 000000000000..94cdc166c840
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/telemetry.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Telemetry communication for Wilco EC
+ *
+ * Copyright 2019 Google LLC
+ *
+ * The Wilco Embedded Controller is able to send telemetry data
+ * which is useful for enterprise applications. A daemon running on
+ * the OS sends a command to the EC via a write() to a char device,
+ * and can read the response with a read(). The write() request is
+ * verified by the driver to ensure that it is performing only one
+ * of the whitelisted commands, and that no extraneous data is
+ * being transmitted to the EC. The response is passed directly
+ * back to the reader with no modification.
+ *
+ * The character device will appear as /dev/wilco_telemN, where N
+ * is some small non-negative integer, starting with 0. Only one
+ * process may have the file descriptor open at a time. The calling
+ * userspace program needs to keep the device file descriptor open
+ * between the calls to write() and read() in order to preserve the
+ * response. Up to 32 bytes will be available for reading.
+ *
+ * For testing purposes, try requesting the EC's firmware build
+ * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
+ * argument index=3. i.e. write [0x38, 0x00, 0x03]
+ * to the device node. An ASCII string of the build date is
+ * returned.
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+#define TELEM_DEV_NAME "wilco_telem"
+#define TELEM_CLASS_NAME TELEM_DEV_NAME
+#define DRV_NAME TELEM_DEV_NAME
+#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")
+static struct class telem_class = {
+ .owner = THIS_MODULE,
+ .name = TELEM_CLASS_NAME,
+};
+
+/* Keep track of all the device numbers used. */
+#define TELEM_MAX_DEV 128
+static int telem_major;
+static DEFINE_IDA(telem_ida);
+
+/* EC telemetry command codes */
+#define WILCO_EC_TELEM_GET_LOG 0x99
+#define WILCO_EC_TELEM_GET_VERSION 0x38
+#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E
+#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA
+#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95
+#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C
+#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07
+
+#define TELEM_ARGS_SIZE_MAX 30
+
+/**
+ * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
+ * @command: One of WILCO_EC_TELEM_GET_* command codes.
+ * @reserved: Must be 0.
+ * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
+ */
+struct wilco_ec_telem_request {
+ u8 command;
+ u8 reserved;
+ u8 args[TELEM_ARGS_SIZE_MAX];
+} __packed;
+
+/*
+ * The following telem_args_get_* structs are embedded within the |args| field
+ * of wilco_ec_telem_request.
+ */
+
+struct telem_args_get_log {
+ u8 log_type;
+ u8 log_index;
+} __packed;
+
+/*
+ * Get a piece of info about the EC firmware version:
+ * 0 = label
+ * 1 = svn_rev
+ * 2 = model_no
+ * 3 = build_date
+ * 4 = frio_version
+ */
+struct telem_args_get_version {
+ u8 index;
+} __packed;
+
+struct telem_args_get_fan_info {
+ u8 command;
+ u8 fan_number;
+ u8 arg;
+} __packed;
+
+struct telem_args_get_diag_info {
+ u8 type;
+ u8 sub_type;
+} __packed;
+
+struct telem_args_get_temp_info {
+ u8 command;
+ u8 index;
+ u8 field;
+ u8 zone;
+} __packed;
+
+struct telem_args_get_temp_read {
+ u8 sensor_index;
+} __packed;
+
+struct telem_args_get_batt_ext_info {
+ u8 var_args[5];
+} __packed;
+
+/**
+ * check_telem_request() - Ensure that a request from userspace is valid.
+ * @rq: Request buffer copied from userspace.
+ * @size: Number of bytes copied from userspace.
+ *
+ * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
+ * -EMSGSIZE if the request is too long.
+ *
+ * We do not want to allow userspace to send arbitrary telemetry commands to
+ * the EC. Therefore we check to ensure that
+ * 1. The request follows the format of struct wilco_ec_telem_request.
+ * 2. The supplied command code is one of the whitelisted commands.
+ * 3. The request only contains the necessary data for the header and arguments.
+ */
+static int check_telem_request(struct wilco_ec_telem_request *rq,
+ size_t size)
+{
+ size_t max_size = offsetof(struct wilco_ec_telem_request, args);
+
+ if (rq->reserved)
+ return -EINVAL;
+
+ switch (rq->command) {
+ case WILCO_EC_TELEM_GET_LOG:
+ max_size += sizeof(struct telem_args_get_log);
+ break;
+ case WILCO_EC_TELEM_GET_VERSION:
+ max_size += sizeof(struct telem_args_get_version);
+ break;
+ case WILCO_EC_TELEM_GET_FAN_INFO:
+ max_size += sizeof(struct telem_args_get_fan_info);
+ break;
+ case WILCO_EC_TELEM_GET_DIAG_INFO:
+ max_size += sizeof(struct telem_args_get_diag_info);
+ break;
+ case WILCO_EC_TELEM_GET_TEMP_INFO:
+ max_size += sizeof(struct telem_args_get_temp_info);
+ break;
+ case WILCO_EC_TELEM_GET_TEMP_READ:
+ max_size += sizeof(struct telem_args_get_temp_read);
+ break;
+ case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
+ max_size += sizeof(struct telem_args_get_batt_ext_info);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return (size <= max_size) ? 0 : -EMSGSIZE;
+}
+
+/**
+ * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
+ * @cdev: Char dev that userspace reads and polls from.
+ * @dev: Device associated with the %cdev.
+ * @ec: Wilco EC that we will be communicating with using the mailbox interface.
+ * @available: Boolean of if the device can be opened.
+ */
+struct telem_device_data {
+ struct device dev;
+ struct cdev cdev;
+ struct wilco_ec_device *ec;
+ atomic_t available;
+};
+
+#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
+
+/**
+ * struct telem_session_data - Data that exists between open() and release().
+ * @dev_data: Pointer to get back to the device data and EC.
+ * @request: Command and arguments sent to EC.
+ * @response: Response buffer of data from EC.
+ * @has_msg: Is there data available to read from a previous write?
+ */
+struct telem_session_data {
+ struct telem_device_data *dev_data;
+ struct wilco_ec_telem_request request;
+ u8 response[TELEM_RESPONSE_SIZE];
+ bool has_msg;
+};
+
+/**
+ * telem_open() - Callback for when the device node is opened.
+ * @inode: inode for this char device node.
+ * @filp: file for this char device node.
+ *
+ * We need to ensure that after writing a command to the device,
+ * the same userspace process reads the corresponding result.
+ * Therefore, we increment a refcount on opening the device, so that
+ * only one process can communicate with the EC at a time.
+ *
+ * Return: 0 on success, or negative error code on failure.
+ */
+static int telem_open(struct inode *inode, struct file *filp)
+{
+ struct telem_device_data *dev_data;
+ struct telem_session_data *sess_data;
+
+ /* Ensure device isn't already open */
+ dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
+ if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
+ return -EBUSY;
+
+ get_device(&dev_data->dev);
+
+ sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
+ if (!sess_data) {
+ atomic_set(&dev_data->available, 1);
+ return -ENOMEM;
+ }
+ sess_data->dev_data = dev_data;
+ sess_data->has_msg = false;
+
+ nonseekable_open(inode, filp);
+ filp->private_data = sess_data;
+
+ return 0;
+}
+
+static ssize_t telem_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct telem_session_data *sess_data = filp->private_data;
+ struct wilco_ec_message msg = {};
+ int ret;
+
+ if (count > sizeof(sess_data->request))
+ return -EMSGSIZE;
+ if (copy_from_user(&sess_data->request, buf, count))
+ return -EFAULT;
+ ret = check_telem_request(&sess_data->request, count);
+ if (ret < 0)
+ return ret;
+
+ memset(sess_data->response, 0, sizeof(sess_data->response));
+ msg.type = WILCO_EC_MSG_TELEMETRY;
+ msg.request_data = &sess_data->request;
+ msg.request_size = sizeof(sess_data->request);
+ msg.response_data = sess_data->response;
+ msg.response_size = sizeof(sess_data->response);
+
+ ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
+ if (ret < 0)
+ return ret;
+ if (ret != sizeof(sess_data->response))
+ return -EMSGSIZE;
+
+ sess_data->has_msg = true;
+
+ return count;
+}
+
+static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *pos)
+{
+ struct telem_session_data *sess_data = filp->private_data;
+
+ if (!sess_data->has_msg)
+ return -ENODATA;
+ if (count > sizeof(sess_data->response))
+ return -EINVAL;
+
+ if (copy_to_user(buf, sess_data->response, count))
+ return -EFAULT;
+
+ sess_data->has_msg = false;
+
+ return count;
+}
+
+static int telem_release(struct inode *inode, struct file *filp)
+{
+ struct telem_session_data *sess_data = filp->private_data;
+
+ atomic_set(&sess_data->dev_data->available, 1);
+ put_device(&sess_data->dev_data->dev);
+ kfree(sess_data);
+
+ return 0;
+}
+
+static const struct file_operations telem_fops = {
+ .open = telem_open,
+ .write = telem_write,
+ .read = telem_read,
+ .release = telem_release,
+ .llseek = no_llseek,
+ .owner = THIS_MODULE,
+};
+
+/**
+ * telem_device_free() - Callback to free the telem_device_data structure.
+ * @d: The device embedded in our device data, which we have been ref counting.
+ *
+ * Once all open file descriptors are closed and the device has been removed,
+ * the refcount of the device will fall to 0 and this will be called.
+ */
+static void telem_device_free(struct device *d)
+{
+ struct telem_device_data *dev_data;
+
+ dev_data = container_of(d, struct telem_device_data, dev);
+ kfree(dev_data);
+}
+
+/**
+ * telem_device_probe() - Callback when creating a new device.
+ * @pdev: platform device that we will be receiving telems from.
+ *
+ * This finds a free minor number for the device, allocates and initializes
+ * some device data, and creates a new device and char dev node.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int telem_device_probe(struct platform_device *pdev)
+{
+ struct telem_device_data *dev_data;
+ int error, minor;
+
+ /* Get the next available device number */
+ minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
+ if (minor < 0) {
+ error = minor;
+ dev_err(&pdev->dev, "Failed to find minor number: %d", error);
+ return error;
+ }
+
+ dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
+ if (!dev_data) {
+ ida_simple_remove(&telem_ida, minor);
+ return -ENOMEM;
+ }
+
+ /* Initialize the device data */
+ dev_data->ec = dev_get_platdata(&pdev->dev);
+ atomic_set(&dev_data->available, 1);
+ platform_set_drvdata(pdev, dev_data);
+
+ /* Initialize the device */
+ dev_data->dev.devt = MKDEV(telem_major, minor);
+ dev_data->dev.class = &telem_class;
+ dev_data->dev.release = telem_device_free;
+ dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
+ device_initialize(&dev_data->dev);
+
+ /* Initialize the character device and add it to userspace */;
+ cdev_init(&dev_data->cdev, &telem_fops);
+ error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
+ if (error) {
+ put_device(&dev_data->dev);
+ ida_simple_remove(&telem_ida, minor);
+ return error;
+ }
+
+ return 0;
+}
+
+static int telem_device_remove(struct platform_device *pdev)
+{
+ struct telem_device_data *dev_data = platform_get_drvdata(pdev);
+
+ cdev_device_del(&dev_data->cdev, &dev_data->dev);
+ put_device(&dev_data->dev);
+ ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
+
+ return 0;
+}
+
+static struct platform_driver telem_driver = {
+ .probe = telem_device_probe,
+ .remove = telem_device_remove,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init telem_module_init(void)
+{
+ dev_t dev_num = 0;
+ int ret;
+
+ ret = class_register(&telem_class);
+ if (ret) {
+ pr_err(DRV_NAME ": Failed registering class: %d", ret);
+ return ret;
+ }
+
+ /* Request the kernel for device numbers, starting with minor=0 */
+ ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
+ if (ret) {
+ pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret);
+ goto destroy_class;
+ }
+ telem_major = MAJOR(dev_num);
+
+ ret = platform_driver_register(&telem_driver);
+ if (ret < 0) {
+ pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
+ goto unregister_region;
+ }
+
+ return 0;
+
+unregister_region:
+ unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
+destroy_class:
+ class_unregister(&telem_class);
+ ida_destroy(&telem_ida);
+ return ret;
+}
+
+static void __exit telem_module_exit(void)
+{
+ platform_driver_unregister(&telem_driver);
+ unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
+ class_unregister(&telem_class);
+ ida_destroy(&telem_ida);
+}
+
+module_init(telem_module_init);
+module_exit(telem_module_exit);
+
+MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
+MODULE_DESCRIPTION("Wilco EC telemetry driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);