Commit 36a8032d authored by Linus Torvalds's avatar Linus Torvalds

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

Pull chrome platform updates from Olof Johansson:
 "Here's a set of updates to the Chrome OS platform drivers for this
  merge window.

  Main new things this cycle is:

   - Driver changes to expose the lightbar to users.  With this, you can
     make your own blinkenlights on Chromebook Pixels.

   - Changes in the way that the atmel_mxt trackpads are probed.  The
     laptop driver is trying to be smart and not instantiate the devices
     that don't answer to probe.  For the trackpad that can come up in
     two modes (bootloader or regular), this gets complicated since the
     driver already knows how to handle the two modes including the
     actual addresses used.  So now the laptop driver needs to know more
     too, instantiating the regular address even if the bootloader one
     is the probe that passed.

   - mfd driver improvements by Javier Martines Canillas, and a few
     bugfixes from him, kbuild and myself"

* tag 'chrome-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform:
  platform/chrome: chromeos_laptop - instantiate Atmel at primary address
  platform/chrome: cros_ec_lpc - Depend on X86 || COMPILE_TEST
  platform/chrome: cros_ec_lpc - Include linux/io.h header file
  platform/chrome: fix platform_no_drv_owner.cocci warnings
  platform/chrome: cros_ec_lightbar - fix duplicate const warning
  platform/chrome: cros_ec_dev - fix Unknown escape '%' warning
  platform/chrome: Expose Chrome OS Lightbar to users
  platform/chrome: Create sysfs attributes for the ChromeOS EC
  mfd: cros_ec: Instantiate ChromeOS EC character device
  platform/chrome: Add Chrome OS EC userspace device interface
  platform/chrome: Add cros_ec_lpc driver for x86 devices
  mfd: cros_ec: Add char dev and virtual dev pointers
  mfd: cros_ec: Use fixed size arrays to transfer data with the EC
parents 7f9f4430 96cba9b0
......@@ -321,6 +321,7 @@ Code Seq#(hex) Include File Comments
0xDB 00-0F drivers/char/mwave/mwavepub.h
0xDD 00-3F ZFCP device driver see drivers/s390/scsi/
<mailto:aherrman@de.ibm.com>
0xEC 00-01 drivers/platform/chrome/cros_ec_dev.h ChromeOS EC driver
0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development)
<mailto:thomas@winischhofer.net>
0xF4 00-1F video/mbxfb.h mbxfb
......
......@@ -182,72 +182,41 @@ static int ec_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg i2c_msgs[],
const u16 bus_num = bus->remote_bus;
int request_len;
int response_len;
u8 *request = NULL;
u8 *response = NULL;
int result;
struct cros_ec_command msg;
struct cros_ec_command msg = { };
request_len = ec_i2c_count_message(i2c_msgs, num);
if (request_len < 0) {
dev_warn(dev, "Error constructing message %d\n", request_len);
result = request_len;
goto exit;
return request_len;
}
response_len = ec_i2c_count_response(i2c_msgs, num);
if (response_len < 0) {
/* Unexpected; no errors should come when NULL response */
dev_warn(dev, "Error preparing response %d\n", response_len);
result = response_len;
goto exit;
}
if (request_len <= ARRAY_SIZE(bus->request_buf)) {
request = bus->request_buf;
} else {
request = kzalloc(request_len, GFP_KERNEL);
if (request == NULL) {
result = -ENOMEM;
goto exit;
}
}
if (response_len <= ARRAY_SIZE(bus->response_buf)) {
response = bus->response_buf;
} else {
response = kzalloc(response_len, GFP_KERNEL);
if (response == NULL) {
result = -ENOMEM;
goto exit;
}
return response_len;
}
result = ec_i2c_construct_message(request, i2c_msgs, num, bus_num);
result = ec_i2c_construct_message(msg.outdata, i2c_msgs, num, bus_num);
if (result)
goto exit;
return result;
msg.version = 0;
msg.command = EC_CMD_I2C_PASSTHRU;
msg.outdata = request;
msg.outsize = request_len;
msg.indata = response;
msg.insize = response_len;
result = cros_ec_cmd_xfer(bus->ec, &msg);
if (result < 0)
goto exit;
return result;
result = ec_i2c_parse_response(response, i2c_msgs, &num);
result = ec_i2c_parse_response(msg.indata, i2c_msgs, &num);
if (result < 0)
goto exit;
return result;
/* Indicate success by saying how many messages were sent */
result = num;
exit:
if (request != bus->request_buf)
kfree(request);
if (response != bus->response_buf)
kfree(response);
return result;
return num;
}
static u32 ec_i2c_functionality(struct i2c_adapter *adap)
......
......@@ -148,16 +148,19 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
static int cros_ec_keyb_get_state(struct cros_ec_keyb *ckdev, uint8_t *kb_state)
{
int ret;
struct cros_ec_command msg = {
.version = 0,
.command = EC_CMD_MKBP_STATE,
.outdata = NULL,
.outsize = 0,
.indata = kb_state,
.insize = ckdev->cols,
};
return cros_ec_cmd_xfer(ckdev->ec, &msg);
ret = cros_ec_cmd_xfer(ckdev->ec, &msg);
if (ret < 0)
return ret;
memcpy(kb_state, msg.indata, ckdev->cols);
return 0;
}
static irqreturn_t cros_ec_keyb_irq(int irq, void *data)
......
......@@ -74,15 +74,11 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
ret = ec_dev->cmd_xfer(ec_dev, msg);
if (msg->result == EC_RES_IN_PROGRESS) {
int i;
struct cros_ec_command status_msg;
struct ec_response_get_comms_status status;
struct cros_ec_command status_msg = { };
struct ec_response_get_comms_status *status;
status_msg.version = 0;
status_msg.command = EC_CMD_GET_COMMS_STATUS;
status_msg.outdata = NULL;
status_msg.outsize = 0;
status_msg.indata = (uint8_t *)&status;
status_msg.insize = sizeof(status);
status_msg.insize = sizeof(*status);
/*
* Query the EC's status until it's no longer busy or
......@@ -98,7 +94,10 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
msg->result = status_msg.result;
if (status_msg.result != EC_RES_SUCCESS)
break;
if (!(status.flags & EC_COMMS_STATUS_PROCESSING))
status = (struct ec_response_get_comms_status *)
status_msg.indata;
if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
break;
}
}
......@@ -119,6 +118,10 @@ static const struct mfd_cell cros_devs[] = {
.id = 2,
.of_compatible = "google,cros-ec-i2c-tunnel",
},
{
.name = "cros-ec-ctl",
.id = 3,
},
};
int cros_ec_register(struct cros_ec_device *ec_dev)
......
......@@ -4,7 +4,7 @@
menuconfig CHROME_PLATFORMS
bool "Platform support for Chrome hardware"
depends on X86
depends on X86 || ARM
---help---
Say Y here to get to see options for platform support for
various Chromebooks and Chromeboxes. This option alone does
......@@ -16,8 +16,7 @@ if CHROME_PLATFORMS
config CHROMEOS_LAPTOP
tristate "Chrome OS Laptop"
depends on I2C
depends on DMI
depends on I2C && DMI && X86
---help---
This driver instantiates i2c and smbus devices such as
light sensors and touchpads.
......@@ -27,6 +26,7 @@ config CHROMEOS_LAPTOP
config CHROMEOS_PSTORE
tristate "Chrome OS pstore support"
depends on X86
---help---
This module instantiates the persistent storage on x86 ChromeOS
devices. It can be used to store away console logs and crash
......@@ -38,5 +38,25 @@ config CHROMEOS_PSTORE
If you have a supported Chromebook, choose Y or M here.
The module will be called chromeos_pstore.
config CROS_EC_CHARDEV
tristate "Chrome OS Embedded Controller userspace device interface"
depends on MFD_CROS_EC
---help---
This driver adds support to talk with the ChromeOS EC from userspace.
If you have a supported Chromebook, choose Y or M here.
The module will be called cros_ec_dev.
config CROS_EC_LPC
tristate "ChromeOS Embedded Controller (LPC)"
depends on MFD_CROS_EC && (X86 || COMPILE_TEST)
help
If you say Y here, you get support for talking to the ChromeOS EC
over an LPC bus. This uses a simple byte-level protocol with a
checksum. This is used for userspace access only. The kernel
typically has its own communication methods.
To compile this driver as a module, choose M here: the
module will be called cros_ec_lpc.
endif # CHROMEOS_PLATFORMS
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
obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o
......@@ -133,12 +133,13 @@ static struct i2c_client *__add_probed_i2c_device(
const char *name,
int bus,
struct i2c_board_info *info,
const unsigned short *addrs)
const unsigned short *alt_addr_list)
{
const struct dmi_device *dmi_dev;
const struct dmi_dev_onboard *dev_data;
struct i2c_adapter *adapter;
struct i2c_client *client;
struct i2c_client *client = NULL;
const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
if (bus < 0)
return NULL;
......@@ -169,8 +170,28 @@ static struct i2c_client *__add_probed_i2c_device(
return NULL;
}
/* add the i2c device */
client = i2c_new_probed_device(adapter, info, addrs, NULL);
/*
* Add the i2c device. If we can't detect it at the primary
* address we scan secondary addresses. In any case the client
* structure gets assigned primary address.
*/
client = i2c_new_probed_device(adapter, info, addr_list, NULL);
if (!client && alt_addr_list) {
struct i2c_board_info dummy_info = {
I2C_BOARD_INFO("dummy", info->addr),
};
struct i2c_client *dummy;
dummy = i2c_new_probed_device(adapter, &dummy_info,
alt_addr_list, NULL);
if (dummy) {
pr_debug("%s %d-%02x is probed at %02x\n",
__func__, bus, info->addr, dummy->addr);
i2c_unregister_device(dummy);
client = i2c_new_device(adapter, info);
}
}
if (!client)
pr_notice("%s failed to register device %d-%02x\n",
__func__, bus, info->addr);
......@@ -254,12 +275,10 @@ static struct i2c_client *add_i2c_device(const char *name,
enum i2c_adapter_type type,
struct i2c_board_info *info)
{
const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
return __add_probed_i2c_device(name,
find_i2c_adapter_num(type),
info,
addr_list);
NULL);
}
static int setup_cyapa_tp(enum i2c_adapter_type type)
......@@ -275,7 +294,6 @@ static int setup_cyapa_tp(enum i2c_adapter_type type)
static int setup_atmel_224s_tp(enum i2c_adapter_type type)
{
const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR,
ATMEL_TP_I2C_ADDR,
I2C_CLIENT_END };
if (tp)
return 0;
......@@ -289,7 +307,6 @@ static int setup_atmel_224s_tp(enum i2c_adapter_type type)
static int setup_atmel_1664s_ts(enum i2c_adapter_type type)
{
const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR,
ATMEL_TS_I2C_ADDR,
I2C_CLIENT_END };
if (ts)
return 0;
......
/*
* cros_ec_dev - expose the Chrome OS Embedded Controller to user-space
*
* Copyright (C) 2014 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/fs.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include "cros_ec_dev.h"
/* Device variables */
#define CROS_MAX_DEV 128
static struct class *cros_class;
static int ec_major;
/* Basic communication */
static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen)
{
struct ec_response_get_version *resp;
static const char * const current_image_name[] = {
"unknown", "read-only", "read-write", "invalid",
};
struct cros_ec_command msg = {
.version = 0,
.command = EC_CMD_GET_VERSION,
.outdata = { 0 },
.outsize = 0,
.indata = { 0 },
.insize = sizeof(*resp),
};
int ret;
ret = cros_ec_cmd_xfer(ec, &msg);
if (ret < 0)
return ret;
if (msg.result != EC_RES_SUCCESS) {
snprintf(str, maxlen,
"%s\nUnknown EC version: EC returned %d\n",
CROS_EC_DEV_VERSION, msg.result);
return 0;
}
resp = (struct ec_response_get_version *)msg.indata;
if (resp->current_image >= ARRAY_SIZE(current_image_name))
resp->current_image = 3; /* invalid */
snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
resp->version_string_ro, resp->version_string_rw,
current_image_name[resp->current_image]);
return 0;
}
/* Device file ops */
static int ec_device_open(struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev,
struct cros_ec_device, cdev);
return 0;
}
static int ec_device_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t ec_device_read(struct file *filp, char __user *buffer,
size_t length, loff_t *offset)
{
struct cros_ec_device *ec = filp->private_data;
char msg[sizeof(struct ec_response_get_version) +
sizeof(CROS_EC_DEV_VERSION)];
size_t count;
int ret;
if (*offset != 0)
return 0;
ret = ec_get_version(ec, msg, sizeof(msg));
if (ret)
return ret;
count = min(length, strlen(msg));
if (copy_to_user(buffer, msg, count))
return -EFAULT;
*offset = count;
return count;
}
/* Ioctls */
static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg)
{
long ret;
struct cros_ec_command s_cmd = { };
if (copy_from_user(&s_cmd, arg, sizeof(s_cmd)))
return -EFAULT;
ret = cros_ec_cmd_xfer(ec, &s_cmd);
/* Only copy data to userland if data was received. */
if (ret < 0)
return ret;
if (copy_to_user(arg, &s_cmd, sizeof(s_cmd)))
return -EFAULT;
return 0;
}
static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg)
{
struct cros_ec_readmem s_mem = { };
long num;
/* Not every platform supports direct reads */
if (!ec->cmd_readmem)
return -ENOTTY;
if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
return -EFAULT;
num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer);
if (num <= 0)
return num;
if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem)))
return -EFAULT;
return 0;
}
static long ec_device_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct cros_ec_device *ec = filp->private_data;
if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
return -ENOTTY;
switch (cmd) {
case CROS_EC_DEV_IOCXCMD:
return ec_device_ioctl_xcmd(ec, (void __user *)arg);
case CROS_EC_DEV_IOCRDMEM:
return ec_device_ioctl_readmem(ec, (void __user *)arg);
}
return -ENOTTY;
}
/* Module initialization */
static const struct file_operations fops = {
.open = ec_device_open,
.release = ec_device_release,
.read = ec_device_read,
.unlocked_ioctl = ec_device_ioctl,
};
static int ec_device_probe(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
int retval = -ENOTTY;
dev_t devno = MKDEV(ec_major, 0);
/* Instantiate it (and remember the EC) */
cdev_init(&ec->cdev, &fops);
retval = cdev_add(&ec->cdev, devno, 1);
if (retval) {
dev_err(&pdev->dev, ": failed to add character device\n");
return retval;
}
ec->vdev = device_create(cros_class, NULL, devno, ec,
CROS_EC_DEV_NAME);
if (IS_ERR(ec->vdev)) {
retval = PTR_ERR(ec->vdev);
dev_err(&pdev->dev, ": failed to create device\n");
cdev_del(&ec->cdev);
return retval;
}
/* Initialize extra interfaces */
ec_dev_sysfs_init(ec);
ec_dev_lightbar_init(ec);
return 0;
}
static int ec_device_remove(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
ec_dev_lightbar_remove(ec);
ec_dev_sysfs_remove(ec);
device_destroy(cros_class, MKDEV(ec_major, 0));
cdev_del(&ec->cdev);
return 0;
}
static struct platform_driver cros_ec_dev_driver = {
.driver = {
.name = "cros-ec-ctl",
},
.probe = ec_device_probe,
.remove = ec_device_remove,
};
static int __init cros_ec_dev_init(void)
{
int ret;
dev_t dev = 0;
cros_class = class_create(THIS_MODULE, "chromeos");
if (IS_ERR(cros_class)) {
pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
return PTR_ERR(cros_class);
}
/* Get a range of minor numbers (starting with 0) to work with */
ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME);
if (ret < 0) {
pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n");
goto failed_chrdevreg;
}
ec_major = MAJOR(dev);
/* Register the driver */
ret = platform_driver_register(&cros_ec_dev_driver);
if (ret < 0) {
pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret);
goto failed_devreg;
}
return 0;
failed_devreg:
unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV);
failed_chrdevreg:
class_destroy(cros_class);
return ret;
}
static void __exit cros_ec_dev_exit(void)
{
platform_driver_unregister(&cros_ec_dev_driver);
unregister_chrdev(ec_major, CROS_EC_DEV_NAME);
class_destroy(cros_class);
}
module_init(cros_ec_dev_init);
module_exit(cros_ec_dev_exit);
MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>");
MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");
/*
* cros_ec_dev - expose the Chrome OS Embedded Controller to userspace
*
* Copyright (C) 2014 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 _CROS_EC_DEV_H_
#define _CROS_EC_DEV_H_
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/mfd/cros_ec.h>
#define CROS_EC_DEV_NAME "cros_ec"
#define CROS_EC_DEV_VERSION "1.0.0"
/*
* @offset: within EC_LPC_ADDR_MEMMAP region
* @bytes: number of bytes to read. zero means "read a string" (including '\0')
* (at most only EC_MEMMAP_SIZE bytes can be read)
* @buffer: where to store the result
* ioctl returns the number of bytes read, negative on error
*/
struct cros_ec_readmem {
uint32_t offset;
uint32_t bytes;
uint8_t buffer[EC_MEMMAP_SIZE];
};
#define CROS_EC_DEV_IOC 0xEC
#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)
void ec_dev_sysfs_init(struct cros_ec_device *);
void ec_dev_sysfs_remove(struct cros_ec_device *);
void ec_dev_lightbar_init(struct cros_ec_device *);
void ec_dev_lightbar_remove(struct cros_ec_device *);
#endif /* _CROS_EC_DEV_H_ */
/*
* cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace
*
* Copyright (C) 2014 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