From 1b84f2a4cd4a6f517a313261f6f7c8caae5696c6 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 2 Feb 2015 12:26:22 +0100 Subject: mfd: cros_ec: Use fixed size arrays to transfer data with the EC The struct cros_ec_command will be used as an ioctl() argument for the API to control the ChromeOS EC from user-space. So the data structure has to be 64-bit safe to make it compatible between 32 and 64 avoiding the need for a compat ioctl interface. Since pointers are self-aligned to different byte boundaries, use fixed size arrays instead of pointers for transferring ingoing and outgoing data with the Embedded Controller. Also, re-arrange struct members by decreasing alignment requirements to reduce the needing padding size. Signed-off-by: Javier Martinez Canillas Acked-by: Lee Jones Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/i2c/busses/i2c-cros-ec-tunnel.c | 51 +++++++-------------------------- drivers/input/keyboard/cros_ec_keyb.c | 13 +++++---- drivers/mfd/cros_ec.c | 15 +++++----- include/linux/mfd/cros_ec.h | 8 +++--- 4 files changed, 29 insertions(+), 58 deletions(-) diff --git a/drivers/i2c/busses/i2c-cros-ec-tunnel.c b/drivers/i2c/busses/i2c-cros-ec-tunnel.c index 875c22ae5400..fa8dedd8c3a2 100644 --- a/drivers/i2c/busses/i2c-cros-ec-tunnel.c +++ b/drivers/i2c/busses/i2c-cros-ec-tunnel.c @@ -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) diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index ffa989f2c785..769f8f7f62b7 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -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) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index fc0c81ef04ff..c872e1b0eaf8 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -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; } } diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 0e166b92f5b4..71675b14b5ca 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -38,20 +38,20 @@ enum { /* * @version: Command version number (often 0) * @command: Command to send (EC_CMD_...) - * @outdata: Outgoing data to EC * @outsize: Outgoing length in bytes - * @indata: Where to put the incoming data from EC * @insize: Max number of bytes to accept from EC * @result: EC's response to the command (separate from communication failure) + * @outdata: Outgoing data to EC + * @indata: Where to put the incoming data from EC */ struct cros_ec_command { uint32_t version; uint32_t command; - uint8_t *outdata; uint32_t outsize; - uint8_t *indata; uint32_t insize; uint32_t result; + uint8_t outdata[EC_PROTO2_MAX_PARAM_SIZE]; + uint8_t indata[EC_PROTO2_MAX_PARAM_SIZE]; }; /** -- cgit v1.2.3-59-g8ed1b From 05c11ac4e0712def44cccbff82ad980d9e1d1b80 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 2 Feb 2015 12:26:23 +0100 Subject: mfd: cros_ec: Add char dev and virtual dev pointers The ChromeOS Embedded Controller has to be accessed by applications. A virtual character device is used as an interface with user-space. Extend the struct cros_ec_device with the fields needed by the driver of this virtual character device. Signed-off-by: Javier Martinez Canillas Acked-by: Lee Jones Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- include/linux/mfd/cros_ec.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 71675b14b5ca..324a34683971 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -16,6 +16,7 @@ #ifndef __LINUX_MFD_CROS_EC_H #define __LINUX_MFD_CROS_EC_H +#include #include #include #include @@ -59,9 +60,17 @@ struct cros_ec_command { * * @ec_name: name of EC device (e.g. 'chromeos-ec') * @phys_name: name of physical comms layer (e.g. 'i2c-4') - * @dev: Device pointer + * @dev: Device pointer for physical comms device + * @vdev: Device pointer for virtual comms device + * @cdev: Character device structure for virtual comms device * @was_wake_device: true if this device was set to wake the system from * sleep at the last suspend + * @cmd_readmem: direct read of the EC memory-mapped region, if supported + * @offset is within EC_LPC_ADDR_MEMMAP region. + * @bytes: number of bytes to read. zero means "read a string" (including + * the trailing '\0'). At most only EC_MEMMAP_SIZE bytes can be read. + * Caller must ensure that the buffer is large enough for the result when + * reading a string. * * @priv: Private data * @irq: Interrupt to use @@ -90,8 +99,12 @@ struct cros_ec_device { const char *ec_name; const char *phys_name; struct device *dev; + struct device *vdev; + struct cdev cdev; bool was_wake_device; struct class *cros_class; + int (*cmd_readmem)(struct cros_ec_device *ec, unsigned int offset, + unsigned int bytes, void *dest); /* These are used to implement the platform-specific interface */ void *priv; -- cgit v1.2.3-59-g8ed1b From ec2f33ab582bf6bb14ef3e1ce4ed5f1973edd9f6 Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:24 +0100 Subject: platform/chrome: Add cros_ec_lpc driver for x86 devices Chromebooks have an Embedded Controller (EC) that is used to implement various functions such as keyboard, power and battery. The AP can communicate with the EC through different bus types such as I2C, SPI or LPC. The cros_ec mfd driver is then composed of a core driver that register the sub-devices as mfd cells and provide a high level communication interface that is used by the rest of the kernel and bus specific interfaces modules. Each connection method then has its own driver, which register with the EC driver interface-agnostic interface. Currently, there are drivers to communicate with the EC over I2C and SPI and this driver adds support for LPC. Signed-off-by: Bill Richardson Signed-off-by: Javier Martinez Canillas Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/Kconfig | 12 ++ drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_ec_lpc.c | 319 ++++++++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 drivers/platform/chrome/cros_ec_lpc.c diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 440ed776efd4..6c5f5a1e1175 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -39,4 +39,16 @@ config CHROMEOS_PSTORE The module will be called chromeos_pstore. +config CROS_EC_LPC + tristate "ChromeOS Embedded Controller (LPC)" + depends on MFD_CROS_EC + 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 diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 2b860ca7450f..03c0260369d9 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o +obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c new file mode 100644 index 000000000000..822fdb36ded9 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -0,0 +1,319 @@ +/* + * cros_ec_lpc - LPC access to the Chrome OS Embedded Controller + * + * Copyright (C) 2012-2015 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 +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros_ec_lpc" + +static int ec_response_timed_out(void) +{ + unsigned long one_second = jiffies + HZ; + + usleep_range(200, 300); + do { + if (!(inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK)) + return 0; + usleep_range(100, 200); + } while (time_before(jiffies, one_second)); + + return 1; +} + +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; + int ret = 0; + + if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE || + msg->insize > EC_PROTO2_MAX_PARAM_SIZE) { + dev_err(ec->dev, + "invalid buffer sizes (out %d, in %d)\n", + msg->outsize, msg->insize); + return -EINVAL; + } + + /* Now actually send the command to the EC and get the result */ + args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; + args.command_version = msg->version; + args.data_size = msg->outsize; + + /* Initialize checksum */ + csum = msg->command + args.flags + + args.command_version + args.data_size; + + /* Copy data and update checksum */ + for (i = 0; i < msg->outsize; i++) { + outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->outdata[i]; + } + + /* 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); + + /* Here we go */ + outb(msg->command, EC_LPC_ADDR_HOST_CMD); + + if (ec_response_timed_out()) { + dev_warn(ec->dev, "EC responsed timed out\n"); + ret = -EIO; + goto done; + } + + /* Check result */ + msg->result = inb(EC_LPC_ADDR_HOST_DATA); + + switch (msg->result) { + case EC_RES_SUCCESS: + break; + case EC_RES_IN_PROGRESS: + ret = -EAGAIN; + dev_dbg(ec->dev, "command 0x%02x in progress\n", + msg->command); + goto done; + default: + dev_dbg(ec->dev, "command 0x%02x returned %d\n", + msg->command, msg->result); + } + + /* 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); + + if (args.data_size > msg->insize) { + dev_err(ec->dev, + "packet too long (%d bytes, expected %d)", + args.data_size, msg->insize); + ret = -ENOSPC; + goto done; + } + + /* Start calculating response checksum */ + csum = msg->command + args.flags + + args.command_version + args.data_size; + + /* Read response and update checksum */ + for (i = 0; i < args.data_size; i++) { + msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); + csum += msg->indata[i]; + } + + /* Verify checksum */ + if (args.checksum != (csum & 0xFF)) { + dev_err(ec->dev, + "bad packet checksum, expected %02x, got %02x\n", + args.checksum, csum & 0xFF); + ret = -EBADMSG; + goto done; + } + + /* Return actual amount of data received */ + ret = args.data_size; +done: + return ret; +} + +/* Returns num bytes read, or negative on error. Doesn't need locking. */ +static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, + unsigned int bytes, void *dest) +{ + int i = offset; + char *s = dest; + int cnt = 0; + + if (offset >= EC_MEMMAP_SIZE - bytes) + return -EINVAL; + + /* fixed length */ + if (bytes) { + for (; cnt < bytes; i++, s++, cnt++) + *s = inb(EC_LPC_ADDR_MEMMAP + i); + return cnt; + } + + /* string */ + for (; i < EC_MEMMAP_SIZE; i++, s++) { + *s = inb(EC_LPC_ADDR_MEMMAP + i); + cnt++; + if (!*s) + break; + } + + return cnt; +} + +static int cros_ec_lpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec_dev; + int ret; + + if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE, + dev_name(dev))) { + dev_err(dev, "couldn't reserve memmap region\n"); + return -EBUSY; + } + + if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') || + (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) { + dev_err(dev, "EC ID not detected\n"); + return -ENODEV; + } + + if (!devm_request_region(dev, EC_HOST_CMD_REGION0, + EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { + dev_err(dev, "couldn't reserve region0\n"); + return -EBUSY; + } + if (!devm_request_region(dev, EC_HOST_CMD_REGION1, + EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { + dev_err(dev, "couldn't reserve region1\n"); + return -EBUSY; + } + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, ec_dev); + ec_dev->dev = dev; + ec_dev->ec_name = pdev->name; + ec_dev->phys_name = dev_name(dev); + ec_dev->parent = dev; + ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; + ec_dev->cmd_readmem = cros_ec_lpc_readmem; + + ret = cros_ec_register(ec_dev); + if (ret) { + dev_err(dev, "couldn't register ec_dev (%d)\n", ret); + return ret; + } + + return 0; +} + +static int cros_ec_lpc_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec_dev; + + ec_dev = platform_get_drvdata(pdev); + cros_ec_remove(ec_dev); + + return 0; +} + +static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { + { + /* + * Today all Chromebooks/boxes ship with Google_* as version and + * coreboot as bios vendor. No other systems with this + * combination are known to date. + */ + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), + DMI_MATCH(DMI_BIOS_VERSION, "Google_"), + }, + }, + { + /* x86-link, the Chromebook Pixel. */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + }, + { + /* x86-peppy, the Acer C720 Chromebook. */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), + }, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); + +static struct platform_driver cros_ec_lpc_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .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; + + if (!dmi_check_system(cros_ec_lpc_dmi_table)) { + pr_err(DRV_NAME ": unsupported system.\n"); + return -ENODEV; + } + + /* 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); + return ret; + } + + return 0; +} + +static void __exit cros_ec_lpc_exit(void) +{ + platform_device_unregister(&cros_ec_lpc_device); + platform_driver_unregister(&cros_ec_lpc_driver); +} + +module_init(cros_ec_lpc_init); +module_exit(cros_ec_lpc_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC LPC driver"); -- cgit v1.2.3-59-g8ed1b From e7c256fbfb157885d36ffcf03d981fa8b21e8fec Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:25 +0100 Subject: platform/chrome: Add Chrome OS EC userspace device interface This patch adds a device interface to access the Chrome OS Embedded Controller from user-space. Signed-off-by: Bill Richardson Reviewed-by: Simon Glass Signed-off-by: Javier Martinez Canillas Reviewed-by: Gwendal Grignou Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- Documentation/ioctl/ioctl-number.txt | 1 + drivers/platform/chrome/Kconfig | 14 +- drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_ec_dev.c | 268 ++++++++++++++++++++++++++++++++++ drivers/platform/chrome/cros_ec_dev.h | 47 ++++++ 5 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 drivers/platform/chrome/cros_ec_dev.c create mode 100644 drivers/platform/chrome/cros_ec_dev.h diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 8136e1fd30fd..51f4221657bf 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -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/ +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) 0xF4 00-1F video/mbxfb.h mbxfb diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 6c5f5a1e1175..d4befbffae85 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -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,6 +38,14 @@ 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)" diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 03c0260369d9..ec682900f889 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_dev.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c new file mode 100644 index 000000000000..94c1442d5104 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -0,0 +1,268 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#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; + } + + return 0; +} + +static int ec_device_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + + 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 "); +MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h new file mode 100644 index 000000000000..15c54c4c5531 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +#ifndef _CROS_EC_DEV_H_ +#define _CROS_EC_DEV_H_ + +#include +#include +#include + +#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) + +#endif /* _CROS_EC_DEV_H_ */ -- cgit v1.2.3-59-g8ed1b From 8a4be850b6a1eb8c7b910d98d3683a803ff5ddb4 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 2 Feb 2015 12:26:26 +0100 Subject: mfd: cros_ec: Instantiate ChromeOS EC character device The ChromeOS EC character device is an user-space interface to allow applications to access the Embedded Controller. Add a cell for this device so it's spawned from the mfd driver. Signed-off-by: Javier Martinez Canillas Acked-by: Lee Jones Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/mfd/cros_ec.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index c872e1b0eaf8..c4aecc6f8373 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -118,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) -- cgit v1.2.3-59-g8ed1b From 71af4b52cc22a8d0f7b66a51427a804741a045b6 Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:27 +0100 Subject: platform/chrome: Create sysfs attributes for the ChromeOS EC This adds the first few sysfs attributes for the Chrome OS EC. These controls are made available under /sys/devices/virtual/chromeos/cros_ec flashinfo - display current flash info reboot - tell the EC to reboot in various ways version - information about the EC software and hardware Future changes will build on this to add additional controls. From a root shell, you should be able to do things like this: cd /sys/devices/virtual/chromeos/cros_ec cat flashinfo cat version echo rw > reboot cat version echo ro > reboot cat version echo rw > reboot cat version echo cold > reboot That last command will reboot the AP too. Signed-off-by: Bill Richardson Reviewed-by: Olof Johansson Signed-off-by: Javier Martinez Canillas Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/Makefile | 3 +- drivers/platform/chrome/cros_ec_dev.c | 4 + drivers/platform/chrome/cros_ec_dev.h | 3 + drivers/platform/chrome/cros_ec_sysfs.c | 271 ++++++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/cros_ec_sysfs.c diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index ec682900f889..34b631ceff67 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o -obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_dev.o +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 94c1442d5104..33f37ad36892 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -198,6 +198,9 @@ static int ec_device_probe(struct platform_device *pdev) return retval; } + /* Initialize extra interfaces */ + ec_dev_sysfs_init(ec); + return 0; } @@ -205,6 +208,7 @@ static int ec_device_remove(struct platform_device *pdev) { struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + ec_dev_sysfs_remove(ec); device_destroy(cros_class, MKDEV(ec_major, 0)); cdev_del(&ec->cdev); return 0; diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index 15c54c4c5531..f03613290ecf 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -44,4 +44,7 @@ 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) +void ec_dev_sysfs_init(struct cros_ec_device *); +void ec_dev_sysfs_remove(struct cros_ec_device *); + #endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c new file mode 100644 index 000000000000..fb62ab6cc659 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -0,0 +1,271 @@ +/* + * cros_ec_sysfs - expose the Chrome OS EC through sysfs + * + * 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 . + */ + +#define pr_fmt(fmt) "cros_ec_sysfs: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_dev.h" + +/* Accessor functions */ + +static ssize_t show_ec_reboot(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "ro|rw|cancel|cold|disable-jump|hibernate"); + count += scnprintf(buf + count, PAGE_SIZE - count, + " [at-shutdown]\n"); + return count; +} + +static ssize_t store_ec_reboot(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + static const struct { + const char * const str; + uint8_t cmd; + uint8_t flags; + } words[] = { + {"cancel", EC_REBOOT_CANCEL, 0}, + {"ro", EC_REBOOT_JUMP_RO, 0}, + {"rw", EC_REBOOT_JUMP_RW, 0}, + {"cold", EC_REBOOT_COLD, 0}, + {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, + {"hibernate", EC_REBOOT_HIBERNATE, 0}, + {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, + }; + struct cros_ec_command msg = { 0 }; + struct ec_params_reboot_ec *param = + (struct ec_params_reboot_ec *)msg.outdata; + int got_cmd = 0, offset = 0; + int i; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param->flags = 0; + while (1) { + /* Find word to start scanning */ + while (buf[offset] && isspace(buf[offset])) + offset++; + if (!buf[offset]) + break; + + for (i = 0; i < ARRAY_SIZE(words); i++) { + if (!strncasecmp(words[i].str, buf+offset, + strlen(words[i].str))) { + if (words[i].flags) { + param->flags |= words[i].flags; + } else { + param->cmd = words[i].cmd; + got_cmd = 1; + } + break; + } + } + + /* On to the next word, if any */ + while (buf[offset] && !isspace(buf[offset])) + offset++; + } + + if (!got_cmd) + return -EINVAL; + + msg.command = EC_CMD_REBOOT_EC; + msg.outsize = sizeof(param); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) { + dev_dbg(ec->dev, "EC result %d\n", msg.result); + return -EINVAL; + } + + return count; +} + +static ssize_t show_ec_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static const char * const image_names[] = {"unknown", "RO", "RW"}; + struct ec_response_get_version *r_ver; + struct ec_response_get_chip_info *r_chip; + struct ec_response_board_version *r_board; + struct cros_ec_command msg = { 0 }; + int ret; + int count = 0; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* Get versions. RW may change. */ + msg.command = EC_CMD_GET_VERSION; + msg.insize = sizeof(*r_ver); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + r_ver = (struct ec_response_get_version *)msg.indata; + /* Strings should be null-terminated, but let's be sure. */ + r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; + r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "RO version: %s\n", r_ver->version_string_ro); + count += scnprintf(buf + count, PAGE_SIZE - count, + "RW version: %s\n", r_ver->version_string_rw); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Firmware copy: %s\n", + (r_ver->current_image < ARRAY_SIZE(image_names) ? + image_names[r_ver->current_image] : "?")); + + /* Get build info. */ + msg.command = EC_CMD_GET_BUILD_INFO; + msg.insize = sizeof(msg.indata); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: EC error %d\n", msg.result); + else { + msg.indata[sizeof(msg.indata) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: %s\n", msg.indata); + } + + /* Get chip info. */ + msg.command = EC_CMD_GET_CHIP_INFO; + msg.insize = sizeof(*r_chip); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: EC error %d\n", msg.result); + else { + r_chip = (struct ec_response_get_chip_info *)msg.indata; + + r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; + r_chip->name[sizeof(r_chip->name) - 1] = '\0'; + r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip vendor: %s\n", r_chip->vendor); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip name: %s\n", r_chip->name); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip revision: %s\n", r_chip->revision); + } + + /* Get board version */ + msg.command = EC_CMD_GET_BOARD_VERSION; + msg.insize = sizeof(*r_board); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: EC error %d\n", msg.result); + else { + r_board = (struct ec_response_board_version *)msg.indata; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: %d\n", + r_board->board_version); + } + + return count; +} + +static ssize_t show_ec_flashinfo(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_response_flash_info *resp; + struct cros_ec_command msg = { 0 }; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* The flash info shouldn't ever change, but ask each time anyway. */ + msg.command = EC_CMD_FLASH_INFO; + msg.insize = sizeof(*resp); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_flash_info *)msg.indata; + + return scnprintf(buf, PAGE_SIZE, + "FlashSize %d\nWriteSize %d\n" + "EraseSize %d\nProtectSize %d\n", + resp->flash_size, resp->write_block_size, + resp->erase_block_size, resp->protect_block_size); +} + +/* Module initialization */ + +static DEVICE_ATTR(reboot, S_IWUSR | S_IRUGO, show_ec_reboot, store_ec_reboot); +static DEVICE_ATTR(version, S_IRUGO, show_ec_version, NULL); +static DEVICE_ATTR(flashinfo, S_IRUGO, show_ec_flashinfo, NULL); + +static struct attribute *__ec_attrs[] = { + &dev_attr_reboot.attr, + &dev_attr_version.attr, + &dev_attr_flashinfo.attr, + NULL, +}; + +static struct attribute_group ec_attr_group = { + .attrs = __ec_attrs, +}; + +void ec_dev_sysfs_init(struct cros_ec_device *ec) +{ + int error; + + error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); + if (error) + pr_warn("failed to create group: %d\n", error); +} + +void ec_dev_sysfs_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); +} -- cgit v1.2.3-59-g8ed1b From f3f837e52b14bf84c2db65f622b5c31cd261100c Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:28 +0100 Subject: platform/chrome: Expose Chrome OS Lightbar to users This adds some sysfs entries to provide userspace control of the four-element LED "lightbar" on the Chromebook Pixel. This only instantiates the lightbar controls if the device actually exists. To prevent DoS attacks, this interface is limited to 20 accesses/second, although that rate can be adjusted by a privileged user. On Chromebooks without a lightbar, this should have no effect. On the Chromebook Pixel, you should be able to do things like this: $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo 0x80 > brightness $ echo 255 > brightness $ $ cat sequence S0 $ echo konami > sequence $ cat sequence KONAMI $ $ cat sequence S0 And $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo stop > sequence $ echo "4 255 255 255" > led_rgb $ echo "0 255 0 0 1 0 255 0 2 0 0 255 3 255 255 0" > led_rgb $ echo run > sequence Test the DoS prevention with this: $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo 500 > interval_msec $ time (cat version version version version version version version) Signed-off-by: Bill Richardson Reviewed-by: Olof Johansson Tested-by: Doug Anderson Reviewed-by: Benson Leung Signed-off-by: Javier Martinez Canillas Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/Makefile | 2 +- drivers/platform/chrome/cros_ec_dev.c | 2 + drivers/platform/chrome/cros_ec_dev.h | 3 + drivers/platform/chrome/cros_ec_lightbar.c | 367 +++++++++++++++++++++++++++++ 4 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/cros_ec_lightbar.c diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 34b631ceff67..bd8d8601e875 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,6 +1,6 @@ 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_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 diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 33f37ad36892..ce714317a59e 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -200,6 +200,7 @@ static int ec_device_probe(struct platform_device *pdev) /* Initialize extra interfaces */ ec_dev_sysfs_init(ec); + ec_dev_lightbar_init(ec); return 0; } @@ -208,6 +209,7 @@ 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); diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index f03613290ecf..45d67f7e518c 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -47,4 +47,7 @@ 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_ */ diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c new file mode 100644 index 000000000000..35fc892e4c95 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -0,0 +1,367 @@ +/* + * 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 + * 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 . + */ + +#define pr_fmt(fmt) "cros_ec_lightbar: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_dev.h" + +/* Rate-limit the lightbar interface to prevent DoS. */ +static unsigned long lb_interval_jiffies = 50 * HZ / 1000; + +static ssize_t interval_msec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long msec = lb_interval_jiffies * 1000 / HZ; + + return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); +} + +static ssize_t interval_msec_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long msec; + + if (kstrtoul(buf, 0, &msec)) + return -EINVAL; + + lb_interval_jiffies = msec * HZ / 1000; + + return count; +} + +static DEFINE_MUTEX(lb_mutex); +/* Return 0 if able to throttle correctly, error otherwise */ +static int lb_throttle(void) +{ + static unsigned long last_access; + unsigned long now, next_timeslot; + long delay; + int ret = 0; + + mutex_lock(&lb_mutex); + + now = jiffies; + next_timeslot = last_access + lb_interval_jiffies; + + if (time_before(now, next_timeslot)) { + delay = (long)(next_timeslot) - (long)now; + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(delay) > 0) { + /* interrupted - just abort */ + ret = -EINTR; + goto out; + } + now = jiffies; + } + + last_access = now; +out: + mutex_unlock(&lb_mutex); + + return ret; +} + +#define INIT_MSG(P, R) { \ + .command = EC_CMD_LIGHTBAR_CMD, \ + .outsize = sizeof(*P), \ + .insize = sizeof(*R), \ + } + +static int get_lightbar_version(struct cros_ec_device *ec, + uint32_t *ver_ptr, uint32_t *flg_ptr) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_VERSION; + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return 0; + + switch (msg.result) { + case EC_RES_INVALID_PARAM: + /* Pixel had no version command. */ + if (ver_ptr) + *ver_ptr = 0; + if (flg_ptr) + *flg_ptr = 0; + return 1; + + case EC_RES_SUCCESS: + resp = (struct ec_response_lightbar *)msg.indata; + + /* Future devices w/lightbars should implement this command */ + if (ver_ptr) + *ver_ptr = resp->version.num; + if (flg_ptr) + *flg_ptr = resp->version.flags; + return 1; + } + + /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ + return 0; +} + +static ssize_t version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint32_t version, flags; + struct cros_ec_device *ec = dev_get_drvdata(dev); + int ret; + + ret = lb_throttle(); + if (ret) + return ret; + + /* This should always succeed, because we check during init. */ + if (!get_lightbar_version(ec, &version, &flags)) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); +} + +static ssize_t brightness_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + unsigned int val; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_BRIGHTNESS; + param->brightness.num = val; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + + +/* + * We expect numbers, and we'll keep reading until we find them, skipping over + * any whitespace (sysfs guarantees that the input is null-terminated). Every + * four numbers are sent to the lightbar as . We fail at the first + * parsing error, if we don't parse any numbers, or if we have numbers left + * over. + */ +static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_device *ec = dev_get_drvdata(dev); + unsigned int val[4]; + int ret, i = 0, j = 0, ok = 0; + + do { + /* Skip any whitespace */ + while (*buf && isspace(*buf)) + buf++; + + if (!*buf) + break; + + ret = sscanf(buf, "%i", &val[i++]); + if (ret == 0) + return -EINVAL; + + if (i == 4) { + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_RGB; + param->rgb.led = val[0]; + param->rgb.red = val[1]; + param->rgb.green = val[2]; + param->rgb.blue = val[3]; + /* + * Throttle only the first of every four transactions, + * so that the user can update all four LEDs at once. + */ + if ((j++ % 4) == 0) { + ret = lb_throttle(); + if (ret) + return ret; + } + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + i = 0; + ok = 1; + } + + /* Skip over the number we just read */ + while (*buf && !isspace(*buf)) + buf++; + + } while (*buf); + + return (ok && i == 0) ? count : -EINVAL; +} + +static const char const *seqname[] = { + "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", + "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", +}; + +static ssize_t sequence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_GET_SEQ; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_lightbar *)msg.indata; + if (resp->get_seq.num >= ARRAY_SIZE(seqname)) + return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + else + return scnprintf(buf, PAGE_SIZE, "%s\n", + seqname[resp->get_seq.num]); +} + +static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + unsigned int num; + int ret, len; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + for (len = 0; len < count; len++) + if (!isalnum(buf[len])) + break; + + for (num = 0; num < ARRAY_SIZE(seqname); num++) + if (!strncasecmp(seqname[num], buf, len)) + break; + + if (num >= ARRAY_SIZE(seqname)) { + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + } + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_SEQ; + param->seq.num = num; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + +/* Module initialization */ + +static DEVICE_ATTR_RW(interval_msec); +static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_WO(brightness); +static DEVICE_ATTR_WO(led_rgb); +static DEVICE_ATTR_RW(sequence); +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, + NULL, +}; +static struct attribute_group lb_cmds_attr_group = { + .name = "lightbar", + .attrs = __lb_cmds_attrs, +}; + +void ec_dev_lightbar_init(struct cros_ec_device *ec) +{ + int ret = 0; + + /* Only instantiate this stuff if the EC has a lightbar */ + if (!get_lightbar_version(ec, NULL, NULL)) + return; + + ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); + if (ret) + pr_warn("sysfs_create_group() failed: %d\n", ret); +} + +void ec_dev_lightbar_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); +} -- cgit v1.2.3-59-g8ed1b From ef59c25d1be9539890c03c4915cdd4b868191b6f Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Tue, 3 Mar 2015 13:22:32 +0100 Subject: platform/chrome: cros_ec_dev - fix Unknown escape '%' warning Fix the following sparse warning: drivers/platform/chrome/cros_ec_dev.c:64:45: sparse: Unknown escape '%' Reported-by: Dan Carpenter Signed-off-by: Olof Johansson Reviewed-by: Gwendal Grignou Signed-off-by: Javier Martinez Canillas Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index ce714317a59e..6090d0b2826f 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -61,7 +61,7 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) 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, + 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]); -- cgit v1.2.3-59-g8ed1b From 377415abab7b49c982475990cd06eef9f33e2ccc Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Tue, 3 Mar 2015 13:22:33 +0100 Subject: platform/chrome: cros_ec_lightbar - fix duplicate const warning Fix the following sparse warning: drivers/platform/chrome/cros_ec_lightbar.c:254:25: sparse: duplicate const Reported-by: Dan Carpenter Signed-off-by: Olof Johansson Reviewed-by: Gwendal Grignou Signed-off-by: Javier Martinez Canillas Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_lightbar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 35fc892e4c95..b4ff47a9069a 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -251,7 +251,7 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, return (ok && i == 0) ? count : -EINVAL; } -static const char const *seqname[] = { +static char const *seqname[] = { "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", }; -- cgit v1.2.3-59-g8ed1b From 88999ae23bbd490d14e15e22b4e8650c75e7e90a Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Fri, 27 Feb 2015 09:09:01 +0800 Subject: platform/chrome: fix platform_no_drv_owner.cocci warnings drivers/platform/chrome/cros_ec_lpc.c:272:3-8: No need to set .owner here. The core will do it. Remove .owner field if calls are used which set it automatically Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci CC: Bill Richardson Signed-off-by: Fengguang Wu Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_lpc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 822fdb36ded9..4bba5ecef9e0 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -269,7 +269,6 @@ MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); static struct platform_driver cros_ec_lpc_driver = { .driver = { .name = DRV_NAME, - .owner = THIS_MODULE, }, .probe = cros_ec_lpc_probe, .remove = cros_ec_lpc_remove, -- cgit v1.2.3-59-g8ed1b From 18d0dc24661b42c03074fc89bc89bc0f7c63d2bc Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 27 Feb 2015 06:37:48 +0100 Subject: platform/chrome: cros_ec_lpc - Include linux/io.h header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver uses the inb() and outb() I/O functions so should include the header file that has these functions definitions. This patch fixes the following error when the header is not explicitly included: drivers/platform/chrome//cros_ec_lpc.c: In function ‘ec_response_timed_out’: drivers/platform/chrome//cros_ec_lpc.c:40:3: error: implicit declaration of function ‘inb’ [-Werror=implicit-function-declaration] drivers/platform/chrome//cros_ec_lpc.c: In function ‘cros_ec_cmd_xfer_lpc’: drivers/platform/chrome//cros_ec_lpc.c:75:3: error: implicit declaration of function ‘outb’ [-Werror=implicit-function-declaration] Signed-off-by: Javier Martinez Canillas Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_lpc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 4bba5ecef9e0..8f9ac4d7bbd0 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include -- cgit v1.2.3-59-g8ed1b From 8ce580932f8ee40903017a6f1408ccfff319a6a5 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 27 Feb 2015 06:37:49 +0100 Subject: platform/chrome: cros_ec_lpc - Depend on X86 || COMPILE_TEST The ChromeOS EC is connected by LPC only on x86 platforms and no others, so add a dependency describing that. But also build the driver if the COMPILE_TEST option is enabled to have build coverage in other architectures. Signed-off-by: Javier Martinez Canillas [olof: reworded commit message] Signed-off-by: Olof Johansson --- drivers/platform/chrome/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index d4befbffae85..2a6531a5fde8 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -49,7 +49,7 @@ config CROS_EC_CHARDEV config CROS_EC_LPC tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC + 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 -- cgit v1.2.3-59-g8ed1b From 96cba9b00e297303774bec59e192064d20adeb3d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 14 Apr 2015 13:50:09 -0700 Subject: platform/chrome: chromeos_laptop - instantiate Atmel at primary address The new Atmel MXT driver expects i2c client's address contain the primary (main address) of the chip, and calculates the expected bootloader address form the primary address. Unfortunately chrome_laptop does probe the devices and if touchpad (or touchscreen, or both) comes up in bootloader mode the i2c device gets instantiated with the bootloader address which confuses the driver. To work around this issue let's probe the primary address first. If the device is not detected at the primary address we'll probe alternative addresses as "dummy" devices. If any of them are found, destroy the dummy client and instantiate client with proper name at primary address still. Signed-off-by: Dmitry Torokhov Signed-off-by: Olof Johansson --- drivers/platform/chrome/chromeos_laptop.c | 35 +++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index b84fdd6b629b..a04019ab9feb 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -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; -- cgit v1.2.3-59-g8ed1b