diff options
author | Michael Ellerman <mpe@ellerman.id.au> | 2017-07-31 20:20:29 +1000 |
---|---|---|
committer | Michael Ellerman <mpe@ellerman.id.au> | 2017-07-31 20:20:29 +1000 |
commit | bb272221e9db79f13d454e1f3fb6b05013be985e (patch) | |
tree | 36f4acc50e3fabac71fadd34c720c0a6011db470 /drivers/platform/chrome | |
parent | 253fd51e2f533552ae35a0c661705da6c4842c1b (diff) | |
parent | 5771a8c08880cdca3bfb4a3fc6d309d6bba20877 (diff) | |
download | linux-bb272221e9db79f13d454e1f3fb6b05013be985e.tar.gz linux-bb272221e9db79f13d454e1f3fb6b05013be985e.tar.bz2 linux-bb272221e9db79f13d454e1f3fb6b05013be985e.zip |
Merge tag 'v4.13-rc1' into fixes
The fixes branch is based off a random pre-rc1 commit, because we had
some fixes that needed to go in before rc1 was released.
However we now need to fix some code that went in after that point, but
before rc1, so merge rc1 to get that code into fixes so we can fix it!
Diffstat (limited to 'drivers/platform/chrome')
-rw-r--r-- | drivers/platform/chrome/Kconfig | 14 | ||||
-rw-r--r-- | drivers/platform/chrome/Makefile | 7 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_debugfs.c | 401 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_debugfs.h | 27 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.c | 40 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.h | 6 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lightbar.c | 197 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lpc.c | 168 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lpc_mec.c | 140 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lpc_reg.c | 133 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_proto.c | 116 |
11 files changed, 1173 insertions, 76 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 76bdae1a93bb..0ad6e290bbda 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -49,7 +49,7 @@ config CROS_EC_CHARDEV config CROS_EC_LPC tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC && (X86 || COMPILE_TEST) + 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 @@ -59,6 +59,18 @@ config CROS_EC_LPC 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 + 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 have a ChromeOS Embedded Controller Microchip EC variant + choose Y here. + config CROS_EC_PROTO bool help diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 4f3462783a3c..66c345ca35fc 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -2,8 +2,11 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o \ - cros_ec_lightbar.o cros_ec_vbc.o + cros_ec_lightbar.o cros_ec_vbc.o \ + cros_ec_debugfs.o obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o -obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.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 +obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.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 new file mode 100644 index 000000000000..4cc66f405760 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -0,0 +1,401 @@ +/* + * cros_ec_debugfs - debug logs for Chrome OS EC + * + * Copyright 2015 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/circ_buf.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include "cros_ec_dev.h" +#include "cros_ec_debugfs.h" + +#define LOG_SHIFT 14 +#define LOG_SIZE (1 << LOG_SHIFT) +#define LOG_POLL_SEC 10 + +#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1)) + +/* struct cros_ec_debugfs - ChromeOS EC debugging information + * + * @ec: EC device this debugfs information belongs to + * @dir: dentry for debugfs files + * @log_buffer: circular buffer for console log information + * @read_msg: preallocated EC command and buffer to read console log + * @log_mutex: mutex to protect circular buffer + * @log_wq: waitqueue for log readers + * @log_poll_work: recurring task to poll EC for new console log data + * @panicinfo_blob: panicinfo debugfs blob + */ +struct cros_ec_debugfs { + struct cros_ec_dev *ec; + struct dentry *dir; + /* EC log */ + struct circ_buf log_buffer; + struct cros_ec_command *read_msg; + struct mutex log_mutex; + wait_queue_head_t log_wq; + struct delayed_work log_poll_work; + /* EC panicinfo */ + struct debugfs_blob_wrapper panicinfo_blob; +}; + +/* + * We need to make sure that the EC log buffer on the UART is large enough, + * so that it is unlikely enough to overlow within LOG_POLL_SEC. + */ +static void cros_ec_console_log_work(struct work_struct *__work) +{ + struct cros_ec_debugfs *debug_info = + container_of(to_delayed_work(__work), + struct cros_ec_debugfs, + log_poll_work); + struct cros_ec_dev *ec = debug_info->ec; + struct circ_buf *cb = &debug_info->log_buffer; + struct cros_ec_command snapshot_msg = { + .command = EC_CMD_CONSOLE_SNAPSHOT + ec->cmd_offset, + }; + + struct ec_params_console_read_v1 *read_params = + (struct ec_params_console_read_v1 *)debug_info->read_msg->data; + uint8_t *ec_buffer = (uint8_t *)debug_info->read_msg->data; + int idx; + int buf_space; + int ret; + + ret = cros_ec_cmd_xfer(ec->ec_dev, &snapshot_msg); + if (ret < 0) { + dev_err(ec->dev, "EC communication failed\n"); + goto resched; + } + if (snapshot_msg.result != EC_RES_SUCCESS) { + dev_err(ec->dev, "EC failed to snapshot the console log\n"); + goto resched; + } + + /* Loop until we have read everything, or there's an error. */ + mutex_lock(&debug_info->log_mutex); + buf_space = CIRC_SPACE(cb->head, cb->tail, LOG_SIZE); + + while (1) { + if (!buf_space) { + dev_info_once(ec->dev, + "Some logs may have been dropped...\n"); + break; + } + + memset(read_params, '\0', sizeof(*read_params)); + read_params->subcmd = CONSOLE_READ_RECENT; + ret = cros_ec_cmd_xfer(ec->ec_dev, debug_info->read_msg); + if (ret < 0) { + dev_err(ec->dev, "EC communication failed\n"); + break; + } + if (debug_info->read_msg->result != EC_RES_SUCCESS) { + dev_err(ec->dev, + "EC failed to read the console log\n"); + break; + } + + /* If the buffer is empty, we're done here. */ + if (ret == 0 || ec_buffer[0] == '\0') + break; + + idx = 0; + while (idx < ret && ec_buffer[idx] != '\0' && buf_space > 0) { + cb->buf[cb->head] = ec_buffer[idx]; + cb->head = CIRC_ADD(cb->head, LOG_SIZE, 1); + idx++; + buf_space--; + } + + wake_up(&debug_info->log_wq); + } + + mutex_unlock(&debug_info->log_mutex); + +resched: + schedule_delayed_work(&debug_info->log_poll_work, + msecs_to_jiffies(LOG_POLL_SEC * 1000)); +} + +static int cros_ec_console_log_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return nonseekable_open(inode, file); +} + +static ssize_t cros_ec_console_log_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct cros_ec_debugfs *debug_info = file->private_data; + struct circ_buf *cb = &debug_info->log_buffer; + ssize_t ret; + + mutex_lock(&debug_info->log_mutex); + + while (!CIRC_CNT(cb->head, cb->tail, LOG_SIZE)) { + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error; + } + + mutex_unlock(&debug_info->log_mutex); + + ret = wait_event_interruptible(debug_info->log_wq, + CIRC_CNT(cb->head, cb->tail, LOG_SIZE)); + if (ret < 0) + return ret; + + mutex_lock(&debug_info->log_mutex); + } + + /* Only copy until the end of the circular buffer, and let userspace + * retry to get the rest of the data. + */ + ret = min_t(size_t, CIRC_CNT_TO_END(cb->head, cb->tail, LOG_SIZE), + count); + + if (copy_to_user(buf, cb->buf + cb->tail, ret)) { + ret = -EFAULT; + goto error; + } + + cb->tail = CIRC_ADD(cb->tail, LOG_SIZE, ret); + +error: + mutex_unlock(&debug_info->log_mutex); + return ret; +} + +static unsigned int cros_ec_console_log_poll(struct file *file, + poll_table *wait) +{ + struct cros_ec_debugfs *debug_info = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &debug_info->log_wq, wait); + + mutex_lock(&debug_info->log_mutex); + if (CIRC_CNT(debug_info->log_buffer.head, + debug_info->log_buffer.tail, + LOG_SIZE)) + mask |= POLLIN | POLLRDNORM; + mutex_unlock(&debug_info->log_mutex); + + return mask; +} + +static int cros_ec_console_log_release(struct inode *inode, struct file *file) +{ + return 0; +} + +const struct file_operations cros_ec_console_log_fops = { + .owner = THIS_MODULE, + .open = cros_ec_console_log_open, + .read = cros_ec_console_log_read, + .llseek = no_llseek, + .poll = cros_ec_console_log_poll, + .release = cros_ec_console_log_release, +}; + +static int ec_read_version_supported(struct cros_ec_dev *ec) +{ + struct ec_params_get_cmd_versions_v1 *params; + struct ec_response_get_cmd_versions *response; + int ret; + + struct cros_ec_command *msg; + + msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*response)), + GFP_KERNEL); + if (!msg) + return 0; + + msg->command = EC_CMD_GET_CMD_VERSIONS + ec->cmd_offset; + msg->outsize = sizeof(*params); + msg->insize = sizeof(*response); + + params = (struct ec_params_get_cmd_versions_v1 *)msg->data; + params->cmd = EC_CMD_CONSOLE_READ; + response = (struct ec_response_get_cmd_versions *)msg->data; + + ret = cros_ec_cmd_xfer(ec->ec_dev, msg) >= 0 && + msg->result == EC_RES_SUCCESS && + (response->version_mask & EC_VER_MASK(1)); + + kfree(msg); + + return ret; +} + +static int cros_ec_create_console_log(struct cros_ec_debugfs *debug_info) +{ + struct cros_ec_dev *ec = debug_info->ec; + char *buf; + int read_params_size; + int read_response_size; + + if (!ec_read_version_supported(ec)) { + dev_warn(ec->dev, + "device does not support reading the console log\n"); + return 0; + } + + buf = devm_kzalloc(ec->dev, LOG_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + read_params_size = sizeof(struct ec_params_console_read_v1); + read_response_size = ec->ec_dev->max_response; + debug_info->read_msg = devm_kzalloc(ec->dev, + sizeof(*debug_info->read_msg) + + max(read_params_size, read_response_size), GFP_KERNEL); + if (!debug_info->read_msg) + return -ENOMEM; + + debug_info->read_msg->version = 1; + debug_info->read_msg->command = EC_CMD_CONSOLE_READ + ec->cmd_offset; + debug_info->read_msg->outsize = read_params_size; + debug_info->read_msg->insize = read_response_size; + + debug_info->log_buffer.buf = buf; + debug_info->log_buffer.head = 0; + debug_info->log_buffer.tail = 0; + + mutex_init(&debug_info->log_mutex); + init_waitqueue_head(&debug_info->log_wq); + + if (!debugfs_create_file("console_log", + S_IFREG | S_IRUGO, + debug_info->dir, + debug_info, + &cros_ec_console_log_fops)) + return -ENOMEM; + + INIT_DELAYED_WORK(&debug_info->log_poll_work, + cros_ec_console_log_work); + schedule_delayed_work(&debug_info->log_poll_work, 0); + + return 0; +} + +static void cros_ec_cleanup_console_log(struct cros_ec_debugfs *debug_info) +{ + if (debug_info->log_buffer.buf) { + cancel_delayed_work_sync(&debug_info->log_poll_work); + mutex_destroy(&debug_info->log_mutex); + } +} + +static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info) +{ + struct cros_ec_device *ec_dev = debug_info->ec->ec_dev; + int ret; + struct cros_ec_command *msg; + int insize; + + insize = ec_dev->max_response; + + msg = devm_kzalloc(debug_info->ec->dev, + sizeof(*msg) + insize, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->command = EC_CMD_GET_PANIC_INFO; + msg->insize = insize; + + ret = cros_ec_cmd_xfer(ec_dev, msg); + if (ret < 0) { + dev_warn(debug_info->ec->dev, "Cannot read panicinfo.\n"); + ret = 0; + goto free; + } + + /* No panic data */ + if (ret == 0) + goto free; + + debug_info->panicinfo_blob.data = msg->data; + debug_info->panicinfo_blob.size = ret; + + if (!debugfs_create_blob("panicinfo", + S_IFREG | S_IRUGO, + debug_info->dir, + &debug_info->panicinfo_blob)) { + ret = -ENOMEM; + goto free; + } + + return 0; + +free: + devm_kfree(debug_info->ec->dev, msg); + return ret; +} + +int cros_ec_debugfs_init(struct cros_ec_dev *ec) +{ + struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev); + const char *name = ec_platform->ec_name; + struct cros_ec_debugfs *debug_info; + int ret; + + debug_info = devm_kzalloc(ec->dev, sizeof(*debug_info), GFP_KERNEL); + if (!debug_info) + return -ENOMEM; + + debug_info->ec = ec; + debug_info->dir = debugfs_create_dir(name, NULL); + if (!debug_info->dir) + return -ENOMEM; + + ret = cros_ec_create_panicinfo(debug_info); + if (ret) + goto remove_debugfs; + + ret = cros_ec_create_console_log(debug_info); + if (ret) + goto remove_debugfs; + + ec->debug_info = debug_info; + + return 0; + +remove_debugfs: + debugfs_remove_recursive(debug_info->dir); + return ret; +} + +void cros_ec_debugfs_remove(struct cros_ec_dev *ec) +{ + if (!ec->debug_info) + return; + + debugfs_remove_recursive(ec->debug_info->dir); + cros_ec_cleanup_console_log(ec->debug_info); +} diff --git a/drivers/platform/chrome/cros_ec_debugfs.h b/drivers/platform/chrome/cros_ec_debugfs.h new file mode 100644 index 000000000000..1ff3a50aa1b8 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_debugfs.h @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _DRV_CROS_EC_DEBUGFS_H_ +#define _DRV_CROS_EC_DEBUGFS_H_ + +#include "cros_ec_dev.h" + +/* debugfs stuff */ +int cros_ec_debugfs_init(struct cros_ec_dev *ec); +void cros_ec_debugfs_remove(struct cros_ec_dev *ec); + +#endif /* _DRV_CROS_EC_DEBUGFS_H_ */ diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6aa120cd0574..cf6c4f0846b8 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -21,9 +21,11 @@ #include <linux/mfd/core.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/slab.h> #include <linux/uaccess.h> +#include "cros_ec_debugfs.h" #include "cros_ec_dev.h" /* Device variables */ @@ -427,10 +429,16 @@ static int ec_device_probe(struct platform_device *pdev) goto failed; } + if (cros_ec_debugfs_init(ec)) + dev_warn(dev, "failed to create debugfs directory\n"); + /* check whether this EC is a sensor hub. */ if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) cros_ec_sensors_register(ec); + /* Take control of the lightbar from the EC. */ + lb_manual_suspend_ctrl(ec, 1); + return 0; failed: @@ -441,6 +449,12 @@ failed: static int ec_device_remove(struct platform_device *pdev) { struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); + + /* Let the EC take over the lightbar again. */ + lb_manual_suspend_ctrl(ec, 0); + + cros_ec_debugfs_remove(ec); + cdev_del(&ec->cdev); device_unregister(&ec->class_dev); return 0; @@ -452,9 +466,35 @@ static const struct platform_device_id cros_ec_id[] = { }; MODULE_DEVICE_TABLE(platform, cros_ec_id); +static __maybe_unused int ec_device_suspend(struct device *dev) +{ + struct cros_ec_dev *ec = dev_get_drvdata(dev); + + lb_suspend(ec); + + return 0; +} + +static __maybe_unused int ec_device_resume(struct device *dev) +{ + struct cros_ec_dev *ec = dev_get_drvdata(dev); + + lb_resume(ec); + + return 0; +} + +static const struct dev_pm_ops cros_ec_dev_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = ec_device_suspend, + .resume = ec_device_resume, +#endif +}; + static struct platform_driver cros_ec_dev_driver = { .driver = { .name = "cros-ec-ctl", + .pm = &cros_ec_dev_pm_ops, }, .probe = ec_device_probe, .remove = ec_device_remove, diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index bfd2c84c3571..45e9453608c5 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -43,4 +43,10 @@ struct cros_ec_readmem { #define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) #define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) +/* Lightbar utilities */ +extern bool ec_has_lightbar(struct cros_ec_dev *ec); +extern int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable); +extern int lb_suspend(struct cros_ec_dev *ec); +extern int lb_resume(struct cros_ec_dev *ec); + #endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 8df3d447cacf..fd2b047a2748 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -38,6 +38,13 @@ /* Rate-limit the lightbar interface to prevent DoS. */ static unsigned long lb_interval_jiffies = 50 * HZ / 1000; +/* + * Whether or not we have given userspace control of the lightbar. + * If this is true, we won't do anything during suspend/resume. + */ +static bool userspace_control; +static struct cros_ec_dev *ec_with_lightbar; + static ssize_t interval_msec_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -295,7 +302,8 @@ exit: static char const *seqname[] = { "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", - "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", + "S0S3", "S3S5", "STOP", "RUN", "KONAMI", + "TAP", "PROGRAM", }; static ssize_t sequence_show(struct device *dev, @@ -340,6 +348,89 @@ exit: return ret; } +static int lb_send_empty_cmd(struct cros_ec_dev *ec, uint8_t cmd) +{ + struct ec_params_lightbar *param; + struct cros_ec_command *msg; + int ret; + + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_lightbar *)msg->data; + param->cmd = cmd; + + ret = lb_throttle(); + if (ret) + goto error; + + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + if (ret < 0) + goto error; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto error; + } + ret = 0; +error: + kfree(msg); + + return ret; +} + +int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) +{ + struct ec_params_lightbar *param; + struct cros_ec_command *msg; + int ret; + + if (ec != ec_with_lightbar) + return 0; + + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; + + param = (struct ec_params_lightbar *)msg->data; + + param->cmd = LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL; + param->manual_suspend_ctrl.enable = enable; + + ret = lb_throttle(); + if (ret) + goto error; + + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + if (ret < 0) + goto error; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto error; + } + ret = 0; +error: + kfree(msg); + + return ret; +} + +int lb_suspend(struct cros_ec_dev *ec) +{ + if (userspace_control || ec != ec_with_lightbar) + return 0; + + return lb_send_empty_cmd(ec, LIGHTBAR_CMD_SUSPEND); +} + +int lb_resume(struct cros_ec_dev *ec) +{ + if (userspace_control || ec != ec_with_lightbar) + return 0; + + return lb_send_empty_cmd(ec, LIGHTBAR_CMD_RESUME); +} + static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -390,6 +481,93 @@ exit: return ret; } +static ssize_t program_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int extra_bytes, max_size, ret; + struct ec_params_lightbar *param; + struct cros_ec_command *msg; + struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, + class_dev); + + /* + * We might need to reject the program for size reasons. The EC + * enforces a maximum program size, but we also don't want to try + * and send a program that is too big for the protocol. In order + * to ensure the latter, we also need to ensure we have extra bytes + * to represent the rest of the packet. + */ + extra_bytes = sizeof(*param) - sizeof(param->set_program.data); + max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes); + if (count > max_size) { + dev_err(dev, "Program is %u bytes, too long to send (max: %u)", + (unsigned int)count, max_size); + + return -EINVAL; + } + + msg = alloc_lightbar_cmd_msg(ec); + if (!msg) + return -ENOMEM; + + ret = lb_throttle(); + if (ret) + goto exit; + + dev_info(dev, "Copying %zu byte program to EC", count); + + param = (struct ec_params_lightbar *)msg->data; + param->cmd = LIGHTBAR_CMD_SET_PROGRAM; + + param->set_program.size = count; + memcpy(param->set_program.data, buf, count); + + /* + * We need to set the message size manually or else it will use + * EC_LB_PROG_LEN. This might be too long, and the program + * is unlikely to use all of the space. + */ + msg->outsize = count + extra_bytes; + + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + if (ret < 0) + goto exit; + if (msg->result != EC_RES_SUCCESS) { + ret = -EINVAL; + goto exit; + } + + ret = count; +exit: + kfree(msg); + + return ret; +} + +static ssize_t userspace_control_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", userspace_control); +} + +static ssize_t userspace_control_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + bool enable; + int ret; + + ret = strtobool(buf, &enable); + if (ret < 0) + return ret; + + userspace_control = enable; + + return count; +} + /* Module initialization */ static DEVICE_ATTR_RW(interval_msec); @@ -397,15 +575,25 @@ static DEVICE_ATTR_RO(version); static DEVICE_ATTR_WO(brightness); static DEVICE_ATTR_WO(led_rgb); static DEVICE_ATTR_RW(sequence); +static DEVICE_ATTR_WO(program); +static DEVICE_ATTR_RW(userspace_control); + static struct attribute *__lb_cmds_attrs[] = { &dev_attr_interval_msec.attr, &dev_attr_version.attr, &dev_attr_brightness.attr, &dev_attr_led_rgb.attr, &dev_attr_sequence.attr, + &dev_attr_program.attr, + &dev_attr_userspace_control.attr, NULL, }; +bool ec_has_lightbar(struct cros_ec_dev *ec) +{ + return !!get_lightbar_version(ec, NULL, NULL); +} + static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { @@ -422,10 +610,11 @@ static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, return 0; /* Only instantiate this stuff if the EC has a lightbar */ - if (get_lightbar_version(ec, NULL, NULL)) + if (ec_has_lightbar(ec)) { + ec_with_lightbar = ec; return a->mode; - else - return 0; + } + return 0; } struct attribute_group cros_ec_lightbar_attr_group = { diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index f9a245465fd0..2b6436d1b6a4 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -21,24 +21,29 @@ * expensive. */ +#include <linux/acpi.h> #include <linux/dmi.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/mfd/cros_ec.h> #include <linux/mfd/cros_ec_commands.h> +#include <linux/mfd/cros_ec_lpc_reg.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/printk.h> -#define DRV_NAME "cros_ec_lpc" +#define DRV_NAME "cros_ec_lpcs" +#define ACPI_DRV_NAME "GOOG0004" static int ec_response_timed_out(void) { unsigned long one_second = jiffies + HZ; + u8 data; usleep_range(200, 300); do { - if (!(inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK)) + if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) & + EC_LPC_STATUS_BUSY_MASK)) return 0; usleep_range(100, 200); } while (time_before(jiffies, one_second)); @@ -51,21 +56,20 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, { struct ec_host_request *request; struct ec_host_response response; - u8 sum = 0; - int i; + u8 sum; int ret = 0; u8 *dout; ret = cros_ec_prepare_tx(ec, msg); /* Write buffer */ - for (i = 0; i < ret; i++) - outb(ec->dout[i], EC_LPC_ADDR_HOST_PACKET + i); + cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); request = (struct ec_host_request *)ec->dout; /* Here we go */ - outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD); + sum = EC_COMMAND_PROTOCOL_3; + cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -74,17 +78,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = inb(EC_LPC_ADDR_HOST_DATA); + msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back response */ dout = (u8 *)&response; - for (i = 0; i < sizeof(response); i++) { - dout[i] = inb(EC_LPC_ADDR_HOST_PACKET + i); - sum += dout[i]; - } + sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response), + dout); msg->result = response.result; @@ -97,11 +99,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Read response and process checksum */ - for (i = 0; i < response.data_len; i++) { - msg->data[i] = - inb(EC_LPC_ADDR_HOST_PACKET + sizeof(response) + i); - sum += msg->data[i]; - } + sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET + + sizeof(response), response.data_len, + msg->data); if (sum) { dev_err(ec->dev, @@ -121,8 +121,7 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, struct cros_ec_command *msg) { struct ec_lpc_host_args args; - int csum; - int i; + u8 sum; int ret = 0; if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE || @@ -139,24 +138,20 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, args.data_size = msg->outsize; /* Initialize checksum */ - csum = msg->command + args.flags + - args.command_version + args.data_size; + sum = msg->command + args.flags + args.command_version + args.data_size; /* Copy data and update checksum */ - for (i = 0; i < msg->outsize; i++) { - outb(msg->data[i], EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->data[i]; - } + sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize, + msg->data); /* Finalize checksum and write args */ - args.checksum = csum & 0xFF; - outb(args.flags, EC_LPC_ADDR_HOST_ARGS); - outb(args.command_version, EC_LPC_ADDR_HOST_ARGS + 1); - outb(args.data_size, EC_LPC_ADDR_HOST_ARGS + 2); - outb(args.checksum, EC_LPC_ADDR_HOST_ARGS + 3); + args.checksum = sum; + cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), + (u8 *)&args); /* Here we go */ - outb(msg->command, EC_LPC_ADDR_HOST_CMD); + sum = msg->command; + cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -165,16 +160,14 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = inb(EC_LPC_ADDR_HOST_DATA); + msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back args */ - args.flags = inb(EC_LPC_ADDR_HOST_ARGS); - args.command_version = inb(EC_LPC_ADDR_HOST_ARGS + 1); - args.data_size = inb(EC_LPC_ADDR_HOST_ARGS + 2); - args.checksum = inb(EC_LPC_ADDR_HOST_ARGS + 3); + cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), + (u8 *)&args); if (args.data_size > msg->insize) { dev_err(ec->dev, @@ -185,20 +178,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, } /* Start calculating response checksum */ - csum = msg->command + args.flags + - args.command_version + args.data_size; + sum = msg->command + args.flags + args.command_version + args.data_size; /* Read response and update checksum */ - for (i = 0; i < args.data_size; i++) { - msg->data[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); - csum += msg->data[i]; - } + sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size, + msg->data); /* Verify checksum */ - if (args.checksum != (csum & 0xFF)) { + if (args.checksum != sum) { dev_err(ec->dev, "bad packet checksum, expected %02x, got %02x\n", - args.checksum, csum & 0xFF); + args.checksum, sum); ret = -EBADMSG; goto done; } @@ -222,14 +212,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, /* fixed length */ if (bytes) { - for (; cnt < bytes; i++, s++, cnt++) - *s = inb(EC_LPC_ADDR_MEMMAP + i); - return cnt; + cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s); + return bytes; } /* string */ for (; i < EC_MEMMAP_SIZE; i++, s++) { - *s = inb(EC_LPC_ADDR_MEMMAP + i); + cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s); cnt++; if (!*s) break; @@ -238,10 +227,23 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, return cnt; } +static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data) +{ + struct cros_ec_device *ec_dev = data; + + if (ec_dev->mkbp_event_supported && + cros_ec_get_next_event(ec_dev, NULL) > 0) + blocking_notifier_call_chain(&ec_dev->event_notifier, 0, + ec_dev); +} + static int cros_ec_lpc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct acpi_device *adev; + acpi_status status; struct cros_ec_device *ec_dev; + u8 buf[2]; int ret; if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE, @@ -250,8 +252,8 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) return -EBUSY; } - if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') || - (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) { + cros_ec_lpc_read_bytes(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; } @@ -287,12 +289,33 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) return ret; } + /* + * Connect a notify handler to process MKBP messages if we have a + * companion ACPI device. + */ + adev = ACPI_COMPANION(dev); + if (adev) { + status = acpi_install_notify_handler(adev->handle, + ACPI_ALL_NOTIFY, + cros_ec_lpc_acpi_notify, + ec_dev); + if (ACPI_FAILURE(status)) + dev_warn(dev, "Failed to register notifier %08x\n", + status); + } + return 0; } static int cros_ec_lpc_remove(struct platform_device *pdev) { struct cros_ec_device *ec_dev; + struct acpi_device *adev; + + adev = ACPI_COMPANION(&pdev->dev); + if (adev) + acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY, + cros_ec_lpc_acpi_notify); ec_dev = platform_get_drvdata(pdev); cros_ec_remove(ec_dev); @@ -300,6 +323,12 @@ static int cros_ec_lpc_remove(struct platform_device *pdev) return 0; } +static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = { + { ACPI_DRV_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_ec_lpc_acpi_device_ids); + static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { { /* @@ -337,18 +366,36 @@ static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { }; MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); +#ifdef CONFIG_PM_SLEEP +static int cros_ec_lpc_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_lpc_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +const struct dev_pm_ops cros_ec_lpc_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume) +}; + static struct platform_driver cros_ec_lpc_driver = { .driver = { .name = DRV_NAME, + .acpi_match_table = cros_ec_lpc_acpi_device_ids, + .pm = &cros_ec_lpc_pm_ops, }, .probe = cros_ec_lpc_probe, .remove = cros_ec_lpc_remove, }; -static struct platform_device cros_ec_lpc_device = { - .name = DRV_NAME -}; - static int __init cros_ec_lpc_init(void) { int ret; @@ -358,18 +405,13 @@ static int __init cros_ec_lpc_init(void) return -ENODEV; } + cros_ec_lpc_reg_init(); + /* Register the driver */ ret = platform_driver_register(&cros_ec_lpc_driver); if (ret) { pr_err(DRV_NAME ": can't register driver: %d\n", ret); - return ret; - } - - /* Register the device, and it'll get hooked up automatically */ - ret = platform_device_register(&cros_ec_lpc_device); - 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(); return ret; } @@ -378,8 +420,8 @@ static int __init cros_ec_lpc_init(void) static void __exit cros_ec_lpc_exit(void) { - platform_device_unregister(&cros_ec_lpc_device); platform_driver_unregister(&cros_ec_lpc_driver); + cros_ec_lpc_reg_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 new file mode 100644 index 000000000000..2eda2c2fc210 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lpc_mec.c @@ -0,0 +1,140 @@ +/* + * cros_ec_lpc_mec - LPC variant I/O for Microchip EC + * + * Copyright (C) 2016 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver uses the Chrome OS EC byte-level message-based protocol for + * communicating the keyboard state (which keys are pressed) from a keyboard EC + * to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing, + * but everything else (including deghosting) is done here. The main + * motivation for this is to keep the EC firmware as simple as possible, since + * it cannot be easily upgraded and EC flash/IRAM space is relatively + * expensive. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/mfd/cros_ec_lpc_mec.h> +#include <linux/mutex.h> +#include <linux/types.h> + +/* + * This mutex must be held while accessing the EMI unit. We can't rely on the + * EC mutex because memmap data may be accessed without it being held. + */ +static struct mutex io_mutex; + +/* + * cros_ec_lpc_mec_emi_write_address + * + * Initialize EMI read / write at a given 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, + enum cros_ec_lpc_mec_emi_access_mode access_type) +{ + /* Address relative to start of EMI range */ + addr -= MEC_EMI_RANGE_START; + outb((addr & 0xfc) | access_type, MEC_EMI_EC_ADDRESS_B0); + outb((addr >> 8) & 0x7f, MEC_EMI_EC_ADDRESS_B1); +} + +/* + * 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 + */ +u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type, + unsigned int offset, unsigned int length, + u8 *buf) +{ + int i = 0; + int io_addr; + u8 sum = 0; + enum cros_ec_lpc_mec_emi_access_mode access, new_access; + + /* + * Long access cannot be used on misaligned data since reading B0 loads + * the data register and writing B3 flushes. + */ + if (offset & 0x3 || length < 4) + access = ACCESS_TYPE_BYTE; + else + access = ACCESS_TYPE_LONG_AUTO_INCREMENT; + + mutex_lock(&io_mutex); + + /* Initialize I/O at desired address */ + cros_ec_lpc_mec_emi_write_address(offset, access); + + /* Skip bytes in case of misaligned offset */ + io_addr = MEC_EMI_EC_DATA_B0 + (offset & 0x3); + while (i < length) { + while (io_addr <= MEC_EMI_EC_DATA_B3) { + if (io_type == MEC_IO_READ) + buf[i] = inb(io_addr++); + else + outb(buf[i], io_addr++); + + sum += buf[i++]; + offset++; + + /* Extra bounds check in case of misaligned length */ + if (i == length) + goto done; + } + + /* + * Use long auto-increment access except for misaligned write, + * since writing B3 triggers the flush. + */ + if (length - i < 4 && io_type == MEC_IO_WRITE) + new_access = ACCESS_TYPE_BYTE; + else + new_access = ACCESS_TYPE_LONG_AUTO_INCREMENT; + + if (new_access != access || + access != ACCESS_TYPE_LONG_AUTO_INCREMENT) { + access = new_access; + cros_ec_lpc_mec_emi_write_address(offset, access); + } + + /* Access [B0, B3] on each loop pass */ + io_addr = MEC_EMI_EC_DATA_B0; + } + +done: + mutex_unlock(&io_mutex); + + return sum; +} +EXPORT_SYMBOL(cros_ec_lpc_io_bytes_mec); + +void cros_ec_lpc_mec_init(void) +{ + mutex_init(&io_mutex); +} +EXPORT_SYMBOL(cros_ec_lpc_mec_init); + +void cros_ec_lpc_mec_destroy(void) +{ + mutex_destroy(&io_mutex); +} +EXPORT_SYMBOL(cros_ec_lpc_mec_destroy); diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.c b/drivers/platform/chrome/cros_ec_lpc_reg.c new file mode 100644 index 000000000000..dcc7a3e30604 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lpc_reg.c @@ -0,0 +1,133 @@ +/* + * cros_ec_lpc_reg - LPC access to the Chrome OS Embedded Controller + * + * Copyright (C) 2016 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver uses the Chrome OS EC byte-level message-based protocol for + * communicating the keyboard state (which keys are pressed) from a keyboard EC + * to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing, + * but everything else (including deghosting) is done here. The main + * motivation for this is to keep the EC firmware as simple as possible, since + * it cannot be easily upgraded and EC flash/IRAM space is relatively + * expensive. + */ + +#include <linux/io.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/mfd/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) +{ + if (length == 0) + return 0; + + /* Access desired range through EMI interface */ + if (offset >= MEC_EMI_RANGE_START && offset <= MEC_EMI_RANGE_END) { + /* Ensure we don't straddle EMI region */ + if (WARN_ON(offset + length - 1 > MEC_EMI_RANGE_END)) + return 0; + + return cros_ec_lpc_io_bytes_mec(MEC_IO_READ, offset, length, + dest); + } + + if (WARN_ON(offset + length > MEC_EMI_RANGE_START && + offset < MEC_EMI_RANGE_START)) + return 0; + + return lpc_read_bytes(offset, length, dest); +} + +u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) +{ + if (length == 0) + return 0; + + /* Access desired range through EMI interface */ + if (offset >= MEC_EMI_RANGE_START && offset <= MEC_EMI_RANGE_END) { + /* Ensure we don't straddle EMI region */ + if (WARN_ON(offset + length - 1 > MEC_EMI_RANGE_END)) + return 0; + + return cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, offset, length, + msg); + } + + if (WARN_ON(offset + length > MEC_EMI_RANGE_START && + offset < MEC_EMI_RANGE_START)) + return 0; + + return lpc_write_bytes(offset, length, msg); +} + +void cros_ec_lpc_reg_init(void) +{ + cros_ec_lpc_mec_init(); +} + +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_proto.c b/drivers/platform/chrome/cros_ec_proto.c index ed5dee744c74..8dfa7fcb1248 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -150,6 +150,40 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev, } EXPORT_SYMBOL(cros_ec_check_result); +/* + * cros_ec_get_host_event_wake_mask + * + * Get the mask of host events that cause wake from suspend. + * + * @ec_dev: EC device to call + * @msg: message structure to use + * @mask: result when function returns >=0. + * + * LOCKING: + * the caller has ec_dev->lock mutex, or the caller knows there is + * no other command in progress. + */ +static int cros_ec_get_host_event_wake_mask(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg, + uint32_t *mask) +{ + struct ec_response_host_event_mask *r; + int ret; + + msg->command = EC_CMD_HOST_EVENT_GET_WAKE_MASK; + msg->version = 0; + msg->outsize = 0; + msg->insize = sizeof(*r); + + ret = send_command(ec_dev, msg); + if (ret > 0) { + r = (struct ec_response_host_event_mask *)msg->data; + *mask = r->mask; + } + + return ret; +} + static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev, int devidx, struct cros_ec_command *msg) @@ -235,6 +269,22 @@ static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev) return ret; } +/* + * cros_ec_get_host_command_version_mask + * + * Get the version mask of a given command. + * + * @ec_dev: EC device to call + * @msg: message structure to use + * @cmd: command to get the version of. + * @mask: result when function returns 0. + * + * @return 0 on success, error code otherwise + * + * LOCKING: + * the caller has ec_dev->lock mutex or the caller knows there is + * no other command in progress. + */ static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev, u16 cmd, u32 *mask) { @@ -256,7 +306,7 @@ static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev, pver = (struct ec_params_get_cmd_versions *)msg->data; pver->cmd = cmd; - ret = cros_ec_cmd_xfer(ec_dev, msg); + ret = send_command(ec_dev, msg); if (ret > 0) { rver = (struct ec_response_get_cmd_versions *)msg->data; *mask = rver->version_mask; @@ -371,6 +421,17 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) else ec_dev->mkbp_event_supported = 1; + /* + * Get host event wake mask, assume all events are wake events + * if unavailable. + */ + ret = cros_ec_get_host_event_wake_mask(ec_dev, proto_msg, + &ec_dev->host_event_wake_mask); + if (ret < 0) + ec_dev->host_event_wake_mask = U32_MAX; + + ret = 0; + exit: kfree(proto_msg); return ret; @@ -486,11 +547,54 @@ static int get_keyboard_state_event(struct cros_ec_device *ec_dev) return ec_dev->event_size; } -int cros_ec_get_next_event(struct cros_ec_device *ec_dev) +int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event) { - if (ec_dev->mkbp_event_supported) - return get_next_event(ec_dev); - else - return get_keyboard_state_event(ec_dev); + u32 host_event; + int ret; + + if (!ec_dev->mkbp_event_supported) { + ret = get_keyboard_state_event(ec_dev); + if (ret < 0) + return ret; + + if (wake_event) + *wake_event = true; + + return ret; + } + + ret = get_next_event(ec_dev); + if (ret < 0) + return ret; + + if (wake_event) { + host_event = cros_ec_get_host_event(ec_dev); + + /* Consider non-host_event as wake event */ + *wake_event = !host_event || + !!(host_event & ec_dev->host_event_wake_mask); + } + + return ret; } EXPORT_SYMBOL(cros_ec_get_next_event); + +u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) +{ + u32 host_event; + + BUG_ON(!ec_dev->mkbp_event_supported); + + if (ec_dev->event_data.event_type != EC_MKBP_EVENT_HOST_EVENT) + return 0; + + if (ec_dev->event_size != sizeof(host_event)) { + dev_warn(ec_dev->dev, "Invalid host event size\n"); + return 0; + } + + host_event = get_unaligned_le32(&ec_dev->event_data.data.host_event); + + return host_event; +} +EXPORT_SYMBOL(cros_ec_get_host_event); |