mirror of
				https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable.git
				synced 2025-11-04 07:44:51 +10: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
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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/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,
 | 
			
		||||
 | 
			
		||||
@ -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_ */
 | 
			
		||||
 | 
			
		||||
@ -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 = {
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 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);
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
/*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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…
	
		Reference in New Issue
	
	Block a user