mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-30 02:21:15 +00:00
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:
commit
a3ddacbae5
17 changed files with 1393 additions and 84 deletions
|
@ -3319,9 +3319,10 @@ F: Documentation/devicetree/bindings/input/touchscreen/chipone_icn8318.txt
|
||||||
F: drivers/input/touchscreen/chipone_icn8318.c
|
F: drivers/input/touchscreen/chipone_icn8318.c
|
||||||
|
|
||||||
CHROME HARDWARE PLATFORM SUPPORT
|
CHROME HARDWARE PLATFORM SUPPORT
|
||||||
|
M: Benson Leung <bleung@chromium.org>
|
||||||
M: Olof Johansson <olof@lixom.net>
|
M: Olof Johansson <olof@lixom.net>
|
||||||
S: Maintained
|
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/
|
F: drivers/platform/chrome/
|
||||||
|
|
||||||
CISCO VIC ETHERNET NIC DRIVER
|
CISCO VIC ETHERNET NIC DRIVER
|
||||||
|
|
|
@ -54,12 +54,19 @@ static const struct mfd_cell ec_pd_cell = {
|
||||||
static irqreturn_t ec_irq_thread(int irq, void *data)
|
static irqreturn_t ec_irq_thread(int irq, void *data)
|
||||||
{
|
{
|
||||||
struct cros_ec_device *ec_dev = data;
|
struct cros_ec_device *ec_dev = data;
|
||||||
|
bool wake_event = true;
|
||||||
int ret;
|
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);
|
pm_wakeup_event(ec_dev->dev, 0);
|
||||||
|
|
||||||
ret = cros_ec_get_next_event(ec_dev);
|
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||||
0, ec_dev);
|
0, ec_dev);
|
||||||
|
@ -224,7 +231,7 @@ EXPORT_SYMBOL(cros_ec_suspend);
|
||||||
|
|
||||||
static void cros_ec_drain_events(struct cros_ec_device *ec_dev)
|
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,
|
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||||
1, ec_dev);
|
1, ec_dev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ config CROS_EC_CHARDEV
|
||||||
|
|
||||||
config CROS_EC_LPC
|
config CROS_EC_LPC
|
||||||
tristate "ChromeOS Embedded Controller (LPC)"
|
tristate "ChromeOS Embedded Controller (LPC)"
|
||||||
depends on MFD_CROS_EC && (X86 || COMPILE_TEST)
|
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
||||||
help
|
help
|
||||||
If you say Y here, you get support for talking to the ChromeOS EC
|
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
|
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
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called cros_ec_lpc.
|
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
|
config CROS_EC_PROTO
|
||||||
bool
|
bool
|
||||||
help
|
help
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
|
obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
|
||||||
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
|
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
|
||||||
cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.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_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_EC_PROTO) += cros_ec_proto.o
|
||||||
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
|
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
|
||||||
|
|
401
drivers/platform/chrome/cros_ec_debugfs.c
Normal file
401
drivers/platform/chrome/cros_ec_debugfs.c
Normal 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);
|
||||||
|
}
|
27
drivers/platform/chrome/cros_ec_debugfs.h
Normal file
27
drivers/platform/chrome/cros_ec_debugfs.h
Normal 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_ */
|
|
@ -21,9 +21,11 @@
|
||||||
#include <linux/mfd/core.h>
|
#include <linux/mfd/core.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/pm.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
|
#include "cros_ec_debugfs.h"
|
||||||
#include "cros_ec_dev.h"
|
#include "cros_ec_dev.h"
|
||||||
|
|
||||||
/* Device variables */
|
/* Device variables */
|
||||||
|
@ -427,10 +429,16 @@ static int ec_device_probe(struct platform_device *pdev)
|
||||||
goto failed;
|
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. */
|
/* check whether this EC is a sensor hub. */
|
||||||
if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE))
|
if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE))
|
||||||
cros_ec_sensors_register(ec);
|
cros_ec_sensors_register(ec);
|
||||||
|
|
||||||
|
/* Take control of the lightbar from the EC. */
|
||||||
|
lb_manual_suspend_ctrl(ec, 1);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
failed:
|
failed:
|
||||||
|
@ -441,6 +449,12 @@ failed:
|
||||||
static int ec_device_remove(struct platform_device *pdev)
|
static int ec_device_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
|
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);
|
cdev_del(&ec->cdev);
|
||||||
device_unregister(&ec->class_dev);
|
device_unregister(&ec->class_dev);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -452,9 +466,35 @@ static const struct platform_device_id cros_ec_id[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(platform, 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 = {
|
static struct platform_driver cros_ec_dev_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "cros-ec-ctl",
|
.name = "cros-ec-ctl",
|
||||||
|
.pm = &cros_ec_dev_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = ec_device_probe,
|
.probe = ec_device_probe,
|
||||||
.remove = ec_device_remove,
|
.remove = ec_device_remove,
|
||||||
|
|
|
@ -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_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)
|
#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_ */
|
#endif /* _CROS_EC_DEV_H_ */
|
||||||
|
|
|
@ -38,6 +38,13 @@
|
||||||
/* Rate-limit the lightbar interface to prevent DoS. */
|
/* Rate-limit the lightbar interface to prevent DoS. */
|
||||||
static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
|
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,
|
static ssize_t interval_msec_show(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
@ -295,7 +302,8 @@ exit:
|
||||||
|
|
||||||
static char const *seqname[] = {
|
static char const *seqname[] = {
|
||||||
"ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
|
"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,
|
static ssize_t sequence_show(struct device *dev,
|
||||||
|
@ -340,6 +348,89 @@ exit:
|
||||||
return ret;
|
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,
|
static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
|
||||||
const char *buf, size_t count)
|
const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
|
@ -390,6 +481,93 @@ exit:
|
||||||
return ret;
|
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 */
|
/* Module initialization */
|
||||||
|
|
||||||
static DEVICE_ATTR_RW(interval_msec);
|
static DEVICE_ATTR_RW(interval_msec);
|
||||||
|
@ -397,15 +575,25 @@ static DEVICE_ATTR_RO(version);
|
||||||
static DEVICE_ATTR_WO(brightness);
|
static DEVICE_ATTR_WO(brightness);
|
||||||
static DEVICE_ATTR_WO(led_rgb);
|
static DEVICE_ATTR_WO(led_rgb);
|
||||||
static DEVICE_ATTR_RW(sequence);
|
static DEVICE_ATTR_RW(sequence);
|
||||||
|
static DEVICE_ATTR_WO(program);
|
||||||
|
static DEVICE_ATTR_RW(userspace_control);
|
||||||
|
|
||||||
static struct attribute *__lb_cmds_attrs[] = {
|
static struct attribute *__lb_cmds_attrs[] = {
|
||||||
&dev_attr_interval_msec.attr,
|
&dev_attr_interval_msec.attr,
|
||||||
&dev_attr_version.attr,
|
&dev_attr_version.attr,
|
||||||
&dev_attr_brightness.attr,
|
&dev_attr_brightness.attr,
|
||||||
&dev_attr_led_rgb.attr,
|
&dev_attr_led_rgb.attr,
|
||||||
&dev_attr_sequence.attr,
|
&dev_attr_sequence.attr,
|
||||||
|
&dev_attr_program.attr,
|
||||||
|
&dev_attr_userspace_control.attr,
|
||||||
NULL,
|
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,
|
static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj,
|
||||||
struct attribute *a, int n)
|
struct attribute *a, int n)
|
||||||
{
|
{
|
||||||
|
@ -422,10 +610,11 @@ static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj,
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Only instantiate this stuff if the EC has a lightbar */
|
/* 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;
|
return a->mode;
|
||||||
else
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct attribute_group cros_ec_lightbar_attr_group = {
|
struct attribute_group cros_ec_lightbar_attr_group = {
|
||||||
|
|
|
@ -21,24 +21,29 @@
|
||||||
* expensive.
|
* expensive.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/acpi.h>
|
||||||
#include <linux/dmi.h>
|
#include <linux/dmi.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/mfd/cros_ec.h>
|
#include <linux/mfd/cros_ec.h>
|
||||||
#include <linux/mfd/cros_ec_commands.h>
|
#include <linux/mfd/cros_ec_commands.h>
|
||||||
|
#include <linux/mfd/cros_ec_lpc_reg.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/printk.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)
|
static int ec_response_timed_out(void)
|
||||||
{
|
{
|
||||||
unsigned long one_second = jiffies + HZ;
|
unsigned long one_second = jiffies + HZ;
|
||||||
|
u8 data;
|
||||||
|
|
||||||
usleep_range(200, 300);
|
usleep_range(200, 300);
|
||||||
do {
|
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;
|
return 0;
|
||||||
usleep_range(100, 200);
|
usleep_range(100, 200);
|
||||||
} while (time_before(jiffies, one_second));
|
} 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_request *request;
|
||||||
struct ec_host_response response;
|
struct ec_host_response response;
|
||||||
u8 sum = 0;
|
u8 sum;
|
||||||
int i;
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
u8 *dout;
|
u8 *dout;
|
||||||
|
|
||||||
ret = cros_ec_prepare_tx(ec, msg);
|
ret = cros_ec_prepare_tx(ec, msg);
|
||||||
|
|
||||||
/* Write buffer */
|
/* Write buffer */
|
||||||
for (i = 0; i < ret; i++)
|
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||||
outb(ec->dout[i], EC_LPC_ADDR_HOST_PACKET + i);
|
|
||||||
|
|
||||||
request = (struct ec_host_request *)ec->dout;
|
request = (struct ec_host_request *)ec->dout;
|
||||||
|
|
||||||
/* Here we go */
|
/* 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()) {
|
if (ec_response_timed_out()) {
|
||||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
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 */
|
/* 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);
|
ret = cros_ec_check_result(ec, msg);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Read back response */
|
/* Read back response */
|
||||||
dout = (u8 *)&response;
|
dout = (u8 *)&response;
|
||||||
for (i = 0; i < sizeof(response); i++) {
|
sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||||
dout[i] = inb(EC_LPC_ADDR_HOST_PACKET + i);
|
dout);
|
||||||
sum += dout[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
msg->result = response.result;
|
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 */
|
/* Read response and process checksum */
|
||||||
for (i = 0; i < response.data_len; i++) {
|
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
|
||||||
msg->data[i] =
|
sizeof(response), response.data_len,
|
||||||
inb(EC_LPC_ADDR_HOST_PACKET + sizeof(response) + i);
|
msg->data);
|
||||||
sum += msg->data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sum) {
|
if (sum) {
|
||||||
dev_err(ec->dev,
|
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 cros_ec_command *msg)
|
||||||
{
|
{
|
||||||
struct ec_lpc_host_args args;
|
struct ec_lpc_host_args args;
|
||||||
int csum;
|
u8 sum;
|
||||||
int i;
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE ||
|
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;
|
args.data_size = msg->outsize;
|
||||||
|
|
||||||
/* Initialize checksum */
|
/* Initialize checksum */
|
||||||
csum = msg->command + args.flags +
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||||
args.command_version + args.data_size;
|
|
||||||
|
|
||||||
/* Copy data and update checksum */
|
/* Copy data and update checksum */
|
||||||
for (i = 0; i < msg->outsize; i++) {
|
sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||||
outb(msg->data[i], EC_LPC_ADDR_HOST_PARAM + i);
|
msg->data);
|
||||||
csum += msg->data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finalize checksum and write args */
|
/* Finalize checksum and write args */
|
||||||
args.checksum = csum & 0xFF;
|
args.checksum = sum;
|
||||||
outb(args.flags, EC_LPC_ADDR_HOST_ARGS);
|
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||||
outb(args.command_version, EC_LPC_ADDR_HOST_ARGS + 1);
|
(u8 *)&args);
|
||||||
outb(args.data_size, EC_LPC_ADDR_HOST_ARGS + 2);
|
|
||||||
outb(args.checksum, EC_LPC_ADDR_HOST_ARGS + 3);
|
|
||||||
|
|
||||||
/* Here we go */
|
/* 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()) {
|
if (ec_response_timed_out()) {
|
||||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
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 */
|
/* 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);
|
ret = cros_ec_check_result(ec, msg);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Read back args */
|
/* Read back args */
|
||||||
args.flags = inb(EC_LPC_ADDR_HOST_ARGS);
|
cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||||
args.command_version = inb(EC_LPC_ADDR_HOST_ARGS + 1);
|
(u8 *)&args);
|
||||||
args.data_size = inb(EC_LPC_ADDR_HOST_ARGS + 2);
|
|
||||||
args.checksum = inb(EC_LPC_ADDR_HOST_ARGS + 3);
|
|
||||||
|
|
||||||
if (args.data_size > msg->insize) {
|
if (args.data_size > msg->insize) {
|
||||||
dev_err(ec->dev,
|
dev_err(ec->dev,
|
||||||
|
@ -185,20 +178,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start calculating response checksum */
|
/* Start calculating response checksum */
|
||||||
csum = msg->command + args.flags +
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||||
args.command_version + args.data_size;
|
|
||||||
|
|
||||||
/* Read response and update checksum */
|
/* Read response and update checksum */
|
||||||
for (i = 0; i < args.data_size; i++) {
|
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||||
msg->data[i] = inb(EC_LPC_ADDR_HOST_PARAM + i);
|
msg->data);
|
||||||
csum += msg->data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify checksum */
|
/* Verify checksum */
|
||||||
if (args.checksum != (csum & 0xFF)) {
|
if (args.checksum != sum) {
|
||||||
dev_err(ec->dev,
|
dev_err(ec->dev,
|
||||||
"bad packet checksum, expected %02x, got %02x\n",
|
"bad packet checksum, expected %02x, got %02x\n",
|
||||||
args.checksum, csum & 0xFF);
|
args.checksum, sum);
|
||||||
ret = -EBADMSG;
|
ret = -EBADMSG;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
@ -222,14 +212,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
||||||
|
|
||||||
/* fixed length */
|
/* fixed length */
|
||||||
if (bytes) {
|
if (bytes) {
|
||||||
for (; cnt < bytes; i++, s++, cnt++)
|
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
|
||||||
*s = inb(EC_LPC_ADDR_MEMMAP + i);
|
return bytes;
|
||||||
return cnt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* string */
|
/* string */
|
||||||
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
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++;
|
cnt++;
|
||||||
if (!*s)
|
if (!*s)
|
||||||
break;
|
break;
|
||||||
|
@ -238,10 +227,23 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
||||||
return cnt;
|
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)
|
static int cros_ec_lpc_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
|
struct acpi_device *adev;
|
||||||
|
acpi_status status;
|
||||||
struct cros_ec_device *ec_dev;
|
struct cros_ec_device *ec_dev;
|
||||||
|
u8 buf[2];
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE,
|
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;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') ||
|
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||||
(inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) {
|
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||||
dev_err(dev, "EC ID not detected\n");
|
dev_err(dev, "EC ID not detected\n");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
@ -287,12 +289,33 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
||||||
return ret;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cros_ec_lpc_remove(struct platform_device *pdev)
|
static int cros_ec_lpc_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct cros_ec_device *ec_dev;
|
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);
|
ec_dev = platform_get_drvdata(pdev);
|
||||||
cros_ec_remove(ec_dev);
|
cros_ec_remove(ec_dev);
|
||||||
|
@ -300,6 +323,12 @@ static int cros_ec_lpc_remove(struct platform_device *pdev)
|
||||||
return 0;
|
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 = {
|
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);
|
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 = {
|
static struct platform_driver cros_ec_lpc_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = DRV_NAME,
|
.name = DRV_NAME,
|
||||||
|
.acpi_match_table = cros_ec_lpc_acpi_device_ids,
|
||||||
|
.pm = &cros_ec_lpc_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = cros_ec_lpc_probe,
|
.probe = cros_ec_lpc_probe,
|
||||||
.remove = cros_ec_lpc_remove,
|
.remove = cros_ec_lpc_remove,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct platform_device cros_ec_lpc_device = {
|
|
||||||
.name = DRV_NAME
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init cros_ec_lpc_init(void)
|
static int __init cros_ec_lpc_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -358,18 +405,13 @@ static int __init cros_ec_lpc_init(void)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cros_ec_lpc_reg_init();
|
||||||
|
|
||||||
/* Register the driver */
|
/* Register the driver */
|
||||||
ret = platform_driver_register(&cros_ec_lpc_driver);
|
ret = platform_driver_register(&cros_ec_lpc_driver);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
||||||
return ret;
|
cros_ec_lpc_reg_destroy();
|
||||||
}
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,8 +420,8 @@ static int __init cros_ec_lpc_init(void)
|
||||||
|
|
||||||
static void __exit cros_ec_lpc_exit(void)
|
static void __exit cros_ec_lpc_exit(void)
|
||||||
{
|
{
|
||||||
platform_device_unregister(&cros_ec_lpc_device);
|
|
||||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||||
|
cros_ec_lpc_reg_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(cros_ec_lpc_init);
|
module_init(cros_ec_lpc_init);
|
||||||
|
|
140
drivers/platform/chrome/cros_ec_lpc_mec.c
Normal file
140
drivers/platform/chrome/cros_ec_lpc_mec.c
Normal 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);
|
133
drivers/platform/chrome/cros_ec_lpc_reg.c
Normal file
133
drivers/platform/chrome/cros_ec_lpc_reg.c
Normal 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 */
|
|
@ -150,6 +150,40 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(cros_ec_check_result);
|
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,
|
static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev,
|
||||||
int devidx,
|
int devidx,
|
||||||
struct cros_ec_command *msg)
|
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;
|
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,
|
static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev,
|
||||||
u16 cmd, u32 *mask)
|
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 = (struct ec_params_get_cmd_versions *)msg->data;
|
||||||
pver->cmd = cmd;
|
pver->cmd = cmd;
|
||||||
|
|
||||||
ret = cros_ec_cmd_xfer(ec_dev, msg);
|
ret = send_command(ec_dev, msg);
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
rver = (struct ec_response_get_cmd_versions *)msg->data;
|
rver = (struct ec_response_get_cmd_versions *)msg->data;
|
||||||
*mask = rver->version_mask;
|
*mask = rver->version_mask;
|
||||||
|
@ -371,6 +421,17 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev)
|
||||||
else
|
else
|
||||||
ec_dev->mkbp_event_supported = 1;
|
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:
|
exit:
|
||||||
kfree(proto_msg);
|
kfree(proto_msg);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -486,11 +547,54 @@ static int get_keyboard_state_event(struct cros_ec_device *ec_dev)
|
||||||
return ec_dev->event_size;
|
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)
|
u32 host_event;
|
||||||
return get_next_event(ec_dev);
|
int ret;
|
||||||
else
|
|
||||||
return get_keyboard_state_event(ec_dev);
|
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);
|
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);
|
||||||
|
|
|
@ -149,6 +149,7 @@ struct cros_ec_device {
|
||||||
|
|
||||||
struct ec_response_get_next_event event_data;
|
struct ec_response_get_next_event event_data;
|
||||||
int event_size;
|
int event_size;
|
||||||
|
u32 host_event_wake_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,6 +173,8 @@ struct cros_ec_platform {
|
||||||
u16 cmd_offset;
|
u16 cmd_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct cros_ec_debugfs;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* struct cros_ec_dev - ChromeOS EC device entry point
|
* struct cros_ec_dev - ChromeOS EC device entry point
|
||||||
*
|
*
|
||||||
|
@ -179,6 +182,7 @@ struct cros_ec_platform {
|
||||||
* @cdev: Character device structure in /dev
|
* @cdev: Character device structure in /dev
|
||||||
* @ec_dev: cros_ec_device structure to talk to the physical device
|
* @ec_dev: cros_ec_device structure to talk to the physical device
|
||||||
* @dev: pointer to the platform device
|
* @dev: pointer to the platform device
|
||||||
|
* @debug_info: cros_ec_debugfs structure for debugging information
|
||||||
* @cmd_offset: offset to apply for each command.
|
* @cmd_offset: offset to apply for each command.
|
||||||
*/
|
*/
|
||||||
struct cros_ec_dev {
|
struct cros_ec_dev {
|
||||||
|
@ -186,6 +190,7 @@ struct cros_ec_dev {
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
struct cros_ec_device *ec_dev;
|
struct cros_ec_device *ec_dev;
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
struct cros_ec_debugfs *debug_info;
|
||||||
u16 cmd_offset;
|
u16 cmd_offset;
|
||||||
u32 features[2];
|
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
|
* cros_ec_get_next_event - Fetch next event from the ChromeOS EC
|
||||||
*
|
*
|
||||||
* @ec_dev: Device to fetch event from
|
* @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
|
* 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 */
|
/* sysfs stuff */
|
||||||
extern struct attribute_group cros_ec_attr_group;
|
extern struct attribute_group cros_ec_attr_group;
|
||||||
|
|
|
@ -625,6 +625,10 @@ struct ec_params_get_cmd_versions {
|
||||||
uint8_t cmd; /* Command to check */
|
uint8_t cmd; /* Command to check */
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
struct ec_params_get_cmd_versions_v1 {
|
||||||
|
uint16_t cmd; /* Command to check */
|
||||||
|
} __packed;
|
||||||
|
|
||||||
struct ec_response_get_cmd_versions {
|
struct ec_response_get_cmd_versions {
|
||||||
/*
|
/*
|
||||||
* Mask of supported versions; use EC_VER_MASK() to compare with a
|
* 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 */
|
struct rgb_s color[8]; /* 0-3 are Google colors */
|
||||||
} __packed;
|
} __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 {
|
struct ec_params_lightbar {
|
||||||
uint8_t cmd; /* Command (see enum lightbar_command) */
|
uint8_t cmd; /* Command (see enum lightbar_command) */
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
/* no args */
|
/* no args */
|
||||||
} dump, off, on, init, get_seq, get_params_v0, get_params_v1,
|
} 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 {
|
struct {
|
||||||
uint8_t num;
|
uint8_t num;
|
||||||
|
@ -1182,8 +1193,13 @@ struct ec_params_lightbar {
|
||||||
uint8_t led;
|
uint8_t led;
|
||||||
} get_rgb;
|
} get_rgb;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint8_t enable;
|
||||||
|
} manual_suspend_ctrl;
|
||||||
|
|
||||||
struct lightbar_params_v0 set_params_v0;
|
struct lightbar_params_v0 set_params_v0;
|
||||||
struct lightbar_params_v1 set_params_v1;
|
struct lightbar_params_v1 set_params_v1;
|
||||||
|
struct lightbar_program set_program;
|
||||||
};
|
};
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
@ -1216,7 +1232,8 @@ struct ec_response_lightbar {
|
||||||
struct {
|
struct {
|
||||||
/* no return params */
|
/* no return params */
|
||||||
} off, on, init, set_brightness, seq, reg, set_rgb,
|
} 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;
|
} __packed;
|
||||||
|
|
||||||
|
@ -1240,6 +1257,10 @@ enum lightbar_command {
|
||||||
LIGHTBAR_CMD_GET_DEMO = 15,
|
LIGHTBAR_CMD_GET_DEMO = 15,
|
||||||
LIGHTBAR_CMD_GET_PARAMS_V1 = 16,
|
LIGHTBAR_CMD_GET_PARAMS_V1 = 16,
|
||||||
LIGHTBAR_CMD_SET_PARAMS_V1 = 17,
|
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
|
LIGHTBAR_NUM_CMDS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2285,13 +2306,28 @@ struct ec_params_charge_control {
|
||||||
#define EC_CMD_CONSOLE_SNAPSHOT 0x97
|
#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
|
* Response is null-terminated string. Empty string, if there is no more
|
||||||
* remaining output.
|
* remaining output.
|
||||||
*/
|
*/
|
||||||
#define EC_CMD_CONSOLE_READ 0x98
|
#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;
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
90
include/linux/mfd/cros_ec_lpc_mec.h
Normal file
90
include/linux/mfd/cros_ec_lpc_mec.h
Normal 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 */
|
61
include/linux/mfd/cros_ec_lpc_reg.h
Normal file
61
include/linux/mfd/cros_ec_lpc_reg.h
Normal 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 */
|
Loading…
Add table
Add a link
Reference in a new issue