chrome-platform-for-linus-4.13

Changes in this pull request are around catching up
 cros_ec with the internal chromeos-kernel versions of
 cros_ec, cros_ec_lpc, and cros_ec_lightbar.
 
 Also, switching maintainership from olof to bleung.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABCgAGBQJZZBB8AAoJEB8J9XsKL+ZYcf4P/iRXb23r6pJgaqE3jO1mLJjQ
 aJH8sMVk3q0tIA/Wo3blVZmUD87RkDPqQNRhUx4AKuTtkq+zi+YIdltBk9nyK2tZ
 oRKtAFe1RL1a7Bxvh2im51mFE91q05nItPee+zylAKHL2PudKsAtvsjqEP/qmIBm
 h3XkkOMzSB3cqAjzaLm6bE531pFoRx6yKWUMGr0aTbOjXewC2uhP/U9rJYqtiaYl
 1oRfg1759cUxH1QXmsKIA5Ua2gKDZ+32aszxxgxSWmZ5671SB0psuyLW4Aar7XS0
 MNKGIYgKWBAUHX8iBTLwz/Z4VBB8X9DS2BfDvCZwDJtjCjYcJPzLKjqyGeJ3wr0G
 jW/kfjJL0G1FPxmS7WnsiUcDJemn+p/ia2/9HipLMM61fy7clezmBaxV8I4aWMh0
 zxW8Bk7+qOOv9D72ErKKHJ1oaZ3EWXgWWfiUEmr+99n6GOfFu0vF5+gcdV4HVLKB
 g2Gmt89OE+oMBAlWtDhX/RdhY2Xxf4POsCriBrqrealYXe9NIxjrleKRr6ysEj37
 71/X6TFaqGTYoyyDAVjFmIu6upGVoCLLdx9b/BodV1hyq97AIKHOdzOXpCKk2nvx
 IuA+JOWeoSGBD28CBhuvitJFDwTJv973Z+N9VrvZj91MKI89zI3Y0+sPAm69fbQ4
 mqkTtiLPIfCsvZE/7lWN
 =QtSr
 -----END PGP SIGNATURE-----

Merge tag 'chrome-platform-for-linus-4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/bleung/chrome-platform

Pull chrome platform updates from Benson Leung:
 "Changes in this pull request are around catching up cros_ec with the
  internal chromeos-kernel versions of cros_ec, cros_ec_lpc, and
  cros_ec_lightbar.

  Also, switching maintainership from olof to bleung"

* tag 'chrome-platform-for-linus-4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/bleung/chrome-platform:
  platform/chrome : Add myself as Maintainer
  platform/chrome: cros_ec_lightbar - hide unused PM functions
  cros_ec: Don't signal wake event for non-wake host events
  cros_ec: Fix deadlock when EC is not responsive at probe
  cros_ec: Don't return error when checking command version
  platform/chrome: cros_ec_lightbar - Avoid I2C xfer to EC during suspend
  platform/chrome: cros_ec_lightbar - Add userspace lightbar control bit to EC
  platform/chrome: cros_ec_lightbar - Control of suspend/resume lightbar sequence
  platform/chrome: cros_ec_lightbar - Add lightbar program feature to sysfs
  platform/chrome: cros_ec_lpc: Add MKBP events support over ACPI
  platform/chrome: cros_ec_lpc: Add power management ops
  platform/chrome: cros_ec_lpc: Add support for GOOG004 ACPI device
  platform/chrome: cros_ec_lpc: Add support for mec1322 EC
  platform/chrome: cros_ec_lpc: Add R/W helpers to LPC protocol variants
  mfd: cros_ec: Add support for dumping panic information
  cros_ec_debugfs: Pass proper struct sizes to cros_ec_cmd_xfer()
  mfd: cros_ec: add debugfs, console log file
  mfd: cros_ec: Add EC console read structures definitions
  mfd: cros_ec: Add helper for event notifier.
This commit is contained in:
Linus Torvalds 2017-07-11 09:55:47 -07:00
commit a3ddacbae5
17 changed files with 1393 additions and 84 deletions

View File

@ -3319,9 +3319,10 @@ F: Documentation/devicetree/bindings/input/touchscreen/chipone_icn8318.txt
F: drivers/input/touchscreen/chipone_icn8318.c
CHROME HARDWARE PLATFORM SUPPORT
M: Benson Leung <bleung@chromium.org>
M: Olof Johansson <olof@lixom.net>
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/bleung/chrome-platform.git
F: drivers/platform/chrome/
CISCO VIC ETHERNET NIC DRIVER

View File

@ -54,12 +54,19 @@ static const struct mfd_cell ec_pd_cell = {
static irqreturn_t ec_irq_thread(int irq, void *data)
{
struct cros_ec_device *ec_dev = data;
bool wake_event = true;
int ret;
if (device_may_wakeup(ec_dev->dev))
ret = cros_ec_get_next_event(ec_dev, &wake_event);
/*
* Signal only if wake host events or any interrupt if
* cros_ec_get_next_event() returned an error (default value for
* wake_event is true)
*/
if (wake_event && device_may_wakeup(ec_dev->dev))
pm_wakeup_event(ec_dev->dev, 0);
ret = cros_ec_get_next_event(ec_dev);
if (ret > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
0, ec_dev);
@ -224,7 +231,7 @@ EXPORT_SYMBOL(cros_ec_suspend);
static void cros_ec_drain_events(struct cros_ec_device *ec_dev)
{
while (cros_ec_get_next_event(ec_dev) > 0)
while (cros_ec_get_next_event(ec_dev, NULL) > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
1, ec_dev);
}

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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_ */

View File

@ -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,

View File

@ -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_ */

View File

@ -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 = {

View File

@ -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);

View File

@ -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);

View File

@ -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 */

View File

@ -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);

View File

@ -149,6 +149,7 @@ struct cros_ec_device {
struct ec_response_get_next_event event_data;
int event_size;
u32 host_event_wake_mask;
};
/**
@ -172,6 +173,8 @@ struct cros_ec_platform {
u16 cmd_offset;
};
struct cros_ec_debugfs;
/*
* struct cros_ec_dev - ChromeOS EC device entry point
*
@ -179,6 +182,7 @@ struct cros_ec_platform {
* @cdev: Character device structure in /dev
* @ec_dev: cros_ec_device structure to talk to the physical device
* @dev: pointer to the platform device
* @debug_info: cros_ec_debugfs structure for debugging information
* @cmd_offset: offset to apply for each command.
*/
struct cros_ec_dev {
@ -186,6 +190,7 @@ struct cros_ec_dev {
struct cdev cdev;
struct cros_ec_device *ec_dev;
struct device *dev;
struct cros_ec_debugfs *debug_info;
u16 cmd_offset;
u32 features[2];
};
@ -295,10 +300,22 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev);
* cros_ec_get_next_event - Fetch next event from the ChromeOS EC
*
* @ec_dev: Device to fetch event from
* @wake_event: Pointer to a bool set to true upon return if the event might be
* treated as a wake event. Ignored if null.
*
* Returns: 0 on success, Linux error number on failure
*/
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);
/**
* cros_ec_get_host_event - Return a mask of event set by the EC.
*
* When MKBP is supported, when the EC raises an interrupt,
* We collect the events raised and call the functions in the ec notifier.
*
* This function is a helper to know which events are raised.
*/
u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev);
/* sysfs stuff */
extern struct attribute_group cros_ec_attr_group;

View File

@ -625,6 +625,10 @@ struct ec_params_get_cmd_versions {
uint8_t cmd; /* Command to check */
} __packed;
struct ec_params_get_cmd_versions_v1 {
uint16_t cmd; /* Command to check */
} __packed;
struct ec_response_get_cmd_versions {
/*
* Mask of supported versions; use EC_VER_MASK() to compare with a
@ -1158,13 +1162,20 @@ struct lightbar_params_v1 {
struct rgb_s color[8]; /* 0-3 are Google colors */
} __packed;
/* Lightbar program */
#define EC_LB_PROG_LEN 192
struct lightbar_program {
uint8_t size;
uint8_t data[EC_LB_PROG_LEN];
};
struct ec_params_lightbar {
uint8_t cmd; /* Command (see enum lightbar_command) */
union {
struct {
/* no args */
} dump, off, on, init, get_seq, get_params_v0, get_params_v1,
version, get_brightness, get_demo;
version, get_brightness, get_demo, suspend, resume;
struct {
uint8_t num;
@ -1182,8 +1193,13 @@ struct ec_params_lightbar {
uint8_t led;
} get_rgb;
struct {
uint8_t enable;
} manual_suspend_ctrl;
struct lightbar_params_v0 set_params_v0;
struct lightbar_params_v1 set_params_v1;
struct lightbar_program set_program;
};
} __packed;
@ -1216,7 +1232,8 @@ struct ec_response_lightbar {
struct {
/* no return params */
} off, on, init, set_brightness, seq, reg, set_rgb,
demo, set_params_v0, set_params_v1;
demo, set_params_v0, set_params_v1,
set_program, manual_suspend_ctrl, suspend, resume;
};
} __packed;
@ -1240,6 +1257,10 @@ enum lightbar_command {
LIGHTBAR_CMD_GET_DEMO = 15,
LIGHTBAR_CMD_GET_PARAMS_V1 = 16,
LIGHTBAR_CMD_SET_PARAMS_V1 = 17,
LIGHTBAR_CMD_SET_PROGRAM = 18,
LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL = 19,
LIGHTBAR_CMD_SUSPEND = 20,
LIGHTBAR_CMD_RESUME = 21,
LIGHTBAR_NUM_CMDS
};
@ -2285,13 +2306,28 @@ struct ec_params_charge_control {
#define EC_CMD_CONSOLE_SNAPSHOT 0x97
/*
* Read next chunk of data from saved snapshot.
* Read data from the saved snapshot. If the subcmd parameter is
* CONSOLE_READ_NEXT, this will return data starting from the beginning of
* the latest snapshot. If it is CONSOLE_READ_RECENT, it will start from the
* end of the previous snapshot.
*
* The params are only looked at in version >= 1 of this command. Prior
* versions will just default to CONSOLE_READ_NEXT behavior.
*
* Response is null-terminated string. Empty string, if there is no more
* remaining output.
*/
#define EC_CMD_CONSOLE_READ 0x98
enum ec_console_read_subcmd {
CONSOLE_READ_NEXT = 0,
CONSOLE_READ_RECENT
};
struct ec_params_console_read_v1 {
uint8_t subcmd; /* enum ec_console_read_subcmd */
} __packed;
/*****************************************************************************/
/*

View File

@ -0,0 +1,90 @@
/*
* 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.
*/
#ifndef __LINUX_MFD_CROS_EC_MEC_H
#define __LINUX_MFD_CROS_EC_MEC_H
#include <linux/mfd/cros_ec_commands.h>
enum cros_ec_lpc_mec_emi_access_mode {
/* 8-bit access */
ACCESS_TYPE_BYTE = 0x0,
/* 16-bit access */
ACCESS_TYPE_WORD = 0x1,
/* 32-bit access */
ACCESS_TYPE_LONG = 0x2,
/*
* 32-bit access, read or write of MEC_EMI_EC_DATA_B3 causes the
* EC data register to be incremented.
*/
ACCESS_TYPE_LONG_AUTO_INCREMENT = 0x3,
};
enum cros_ec_lpc_mec_io_type {
MEC_IO_READ,
MEC_IO_WRITE,
};
/* Access IO ranges 0x800 thru 0x9ff using EMI interface instead of LPC */
#define MEC_EMI_RANGE_START EC_HOST_CMD_REGION0
#define MEC_EMI_RANGE_END (EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE)
/* EMI registers are relative to base */
#define MEC_EMI_BASE 0x800
#define MEC_EMI_HOST_TO_EC (MEC_EMI_BASE + 0)
#define MEC_EMI_EC_TO_HOST (MEC_EMI_BASE + 1)
#define MEC_EMI_EC_ADDRESS_B0 (MEC_EMI_BASE + 2)
#define MEC_EMI_EC_ADDRESS_B1 (MEC_EMI_BASE + 3)
#define MEC_EMI_EC_DATA_B0 (MEC_EMI_BASE + 4)
#define MEC_EMI_EC_DATA_B1 (MEC_EMI_BASE + 5)
#define MEC_EMI_EC_DATA_B2 (MEC_EMI_BASE + 6)
#define MEC_EMI_EC_DATA_B3 (MEC_EMI_BASE + 7)
/*
* cros_ec_lpc_mec_init
*
* Initialize MEC I/O.
*/
void cros_ec_lpc_mec_init(void);
/*
* cros_ec_lpc_mec_destroy
*
* Cleanup MEC I/O.
*/
void cros_ec_lpc_mec_destroy(void);
/**
* 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);
#endif /* __LINUX_MFD_CROS_EC_MEC_H */

View File

@ -0,0 +1,61 @@
/*
* 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.
*/
#ifndef __LINUX_MFD_CROS_EC_REG_H
#define __LINUX_MFD_CROS_EC_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 /* __LINUX_MFD_CROS_EC_REG_H */