summaryrefslogtreecommitdiffstats
path: root/drivers/platform/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/chrome')
-rw-r--r--drivers/platform/chrome/Kconfig79
-rw-r--r--drivers/platform/chrome/Makefile3
-rw-r--r--drivers/platform/chrome/chromeos_laptop.c28
-rw-r--r--drivers/platform/chrome/chromeos_tbmc.c2
-rw-r--r--drivers/platform/chrome/cros_ec.c350
-rw-r--r--drivers/platform/chrome/cros_ec.h19
-rw-r--r--drivers/platform/chrome/cros_ec_chardev.c418
-rw-r--r--drivers/platform/chrome/cros_ec_debugfs.c4
-rw-r--r--drivers/platform/chrome/cros_ec_i2c.c14
-rw-r--r--drivers/platform/chrome/cros_ec_ishtp.c38
-rw-r--r--drivers/platform/chrome/cros_ec_lightbar.c4
-rw-r--r--drivers/platform/chrome/cros_ec_lpc.c27
-rw-r--r--drivers/platform/chrome/cros_ec_proto.c276
-rw-r--r--drivers/platform/chrome/cros_ec_rpmsg.c78
-rw-r--r--drivers/platform/chrome/cros_ec_sensorhub.c198
-rw-r--r--drivers/platform/chrome/cros_ec_spi.c16
-rw-r--r--drivers/platform/chrome/cros_ec_sysfs.c4
-rw-r--r--drivers/platform/chrome/cros_ec_trace.c102
-rw-r--r--drivers/platform/chrome/cros_ec_trace.h30
-rw-r--r--drivers/platform/chrome/cros_ec_vbc.c4
-rw-r--r--drivers/platform/chrome/cros_usbpd_logger.c8
-rw-r--r--drivers/platform/chrome/wilco_ec/Kconfig3
-rw-r--r--drivers/platform/chrome/wilco_ec/Makefile3
-rw-r--r--drivers/platform/chrome/wilco_ec/core.c32
-rw-r--r--drivers/platform/chrome/wilco_ec/debugfs.c47
-rw-r--r--drivers/platform/chrome/wilco_ec/keyboard_leds.c203
-rw-r--r--drivers/platform/chrome/wilco_ec/mailbox.c4
-rw-r--r--drivers/platform/chrome/wilco_ec/sysfs.c91
-rw-r--r--drivers/platform/chrome/wilco_ec/telemetry.c72
29 files changed, 1977 insertions, 180 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
index 970679d0b6f6..5f57282a28da 100644
--- a/drivers/platform/chrome/Kconfig
+++ b/drivers/platform/chrome/Kconfig
@@ -3,6 +3,16 @@
# Platform support for Chrome OS hardware (Chromebooks and Chromeboxes)
#
+config MFD_CROS_EC
+ tristate "Platform support for Chrome hardware (transitional)"
+ select CHROME_PLATFORMS
+ select CROS_EC
+ select CONFIG_MFD_CROS_EC_DEV
+ depends on X86 || ARM || ARM64 || COMPILE_TEST
+ help
+ This is a transitional Kconfig option and will be removed after
+ everyone enables the parts individually.
+
menuconfig CHROME_PLATFORMS
bool "Platform support for Chrome hardware"
depends on X86 || ARM || ARM64 || COMPILE_TEST
@@ -50,9 +60,22 @@ config CHROMEOS_TBMC
To compile this driver as a module, choose M here: the
module will be called chromeos_tbmc.
+config CROS_EC
+ tristate "ChromeOS Embedded Controller"
+ select CROS_EC_PROTO
+ depends on X86 || ARM || ARM64 || COMPILE_TEST
+ help
+ If you say Y here you get support for the ChromeOS Embedded
+ Controller (EC) providing keyboard, battery and power services.
+ You also need to enable the driver for the bus you are using. The
+ protocol for talking to the EC is defined by the bus driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cros_ec.
+
config CROS_EC_I2C
tristate "ChromeOS Embedded Controller (I2C)"
- depends on MFD_CROS_EC && I2C
+ depends on CROS_EC && I2C
help
If you say Y here, you get support for talking to the ChromeOS
@@ -62,7 +85,7 @@ config CROS_EC_I2C
config CROS_EC_RPMSG
tristate "ChromeOS Embedded Controller (rpmsg)"
- depends on MFD_CROS_EC && RPMSG && OF
+ depends on CROS_EC && RPMSG && OF
help
If you say Y here, you get support for talking to the ChromeOS EC
through rpmsg. This uses a simple byte-level protocol with a
@@ -74,7 +97,7 @@ config CROS_EC_RPMSG
config CROS_EC_ISHTP
tristate "ChromeOS Embedded Controller (ISHTP)"
- depends on MFD_CROS_EC
+ depends on CROS_EC
depends on INTEL_ISH_HID
help
If you say Y here, you get support for talking to the ChromeOS EC
@@ -87,7 +110,7 @@ config CROS_EC_ISHTP
config CROS_EC_SPI
tristate "ChromeOS Embedded Controller (SPI)"
- depends on MFD_CROS_EC && SPI
+ depends on CROS_EC && SPI
---help---
If you say Y here, you get support for talking to the ChromeOS EC
@@ -97,7 +120,7 @@ config CROS_EC_SPI
config CROS_EC_LPC
tristate "ChromeOS Embedded Controller (LPC)"
- depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
+ depends on 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, including the LPC Microchip EC (MEC) variant.
@@ -109,9 +132,9 @@ config CROS_EC_LPC
module will be called cros_ec_lpcs.
config CROS_EC_PROTO
- bool
- help
- ChromeOS EC communication protocol helpers.
+ bool
+ help
+ ChromeOS EC communication protocol helpers.
config CROS_KBD_LED_BACKLIGHT
tristate "Backlight LED support for Chrome OS keyboards"
@@ -123,10 +146,21 @@ config CROS_KBD_LED_BACKLIGHT
To compile this driver as a module, choose M here: the
module will be called cros_kbd_led_backlight.
+config CROS_EC_CHARDEV
+ tristate "ChromeOS EC miscdevice"
+ depends on MFD_CROS_EC_DEV
+ default MFD_CROS_EC_DEV
+ help
+ This driver adds file operations support to talk with the
+ ChromeOS EC from userspace via a character device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cros_ec_chardev.
+
config CROS_EC_LIGHTBAR
tristate "Chromebook Pixel's lightbar support"
- depends on MFD_CROS_EC_CHARDEV
- default MFD_CROS_EC_CHARDEV
+ depends on MFD_CROS_EC_DEV
+ default MFD_CROS_EC_DEV
help
This option exposes the Chromebook Pixel's lightbar to
userspace.
@@ -136,8 +170,8 @@ config CROS_EC_LIGHTBAR
config CROS_EC_VBC
tristate "ChromeOS EC vboot context support"
- depends on MFD_CROS_EC_CHARDEV && OF
- default MFD_CROS_EC_CHARDEV
+ depends on MFD_CROS_EC_DEV && OF
+ default MFD_CROS_EC_DEV
help
This option exposes the ChromeOS EC vboot context nvram to
userspace.
@@ -147,8 +181,8 @@ config CROS_EC_VBC
config CROS_EC_DEBUGFS
tristate "Export ChromeOS EC internals in DebugFS"
- depends on MFD_CROS_EC_CHARDEV && DEBUG_FS
- default MFD_CROS_EC_CHARDEV
+ depends on MFD_CROS_EC_DEV && DEBUG_FS
+ default MFD_CROS_EC_DEV
help
This option exposes the ChromeOS EC device internals to
userspace.
@@ -156,10 +190,23 @@ config CROS_EC_DEBUGFS
To compile this driver as a module, choose M here: the
module will be called cros_ec_debugfs.
+config CROS_EC_SENSORHUB
+ tristate "ChromeOS EC MEMS Sensor Hub"
+ depends on MFD_CROS_EC_DEV
+ default MFD_CROS_EC_DEV
+ help
+ Allow loading IIO sensors. This driver is loaded by MFD and will in
+ turn query the EC and register the sensors.
+ It also spreads the sensor data coming from the EC to the IIO sensor
+ object.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cros_ec_sensorhub.
+
config CROS_EC_SYSFS
tristate "ChromeOS EC control and information through sysfs"
- depends on MFD_CROS_EC_CHARDEV && SYSFS
- default MFD_CROS_EC_CHARDEV
+ depends on MFD_CROS_EC_DEV && SYSFS
+ default MFD_CROS_EC_DEV
help
This option exposes some sysfs attributes to control and get
information from ChromeOS EC.
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index fd0af05cc14c..aacd5920d8a1 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -6,6 +6,7 @@ CFLAGS_cros_ec_trace.o:= -I$(src)
obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
+obj-$(CONFIG_CROS_EC) += cros_ec.o
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o
obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
@@ -14,9 +15,11 @@ cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
+obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o
obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o
obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o
obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o
+obj-$(CONFIG_CROS_EC_SENSORHUB) += cros_ec_sensorhub.o
obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o
obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o
diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c
index 7abbb6167766..4f3651fcd9fe 100644
--- a/drivers/platform/chrome/chromeos_laptop.c
+++ b/drivers/platform/chrome/chromeos_laptop.c
@@ -63,7 +63,7 @@ struct acpi_peripheral {
struct chromeos_laptop {
/*
* Note that we can't mark this pointer as const because
- * i2c_new_probed_device() changes passed in I2C board info, so.
+ * i2c_new_scanned_device() changes passed in I2C board info, so.
*/
struct i2c_peripheral *i2c_peripherals;
unsigned int num_i2c_peripherals;
@@ -87,8 +87,8 @@ chromes_laptop_instantiate_i2c_device(struct i2c_adapter *adapter,
* 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) {
+ client = i2c_new_scanned_device(adapter, info, addr_list, NULL);
+ if (IS_ERR(client) && alt_addr) {
struct i2c_board_info dummy_info = {
I2C_BOARD_INFO("dummy", info->addr),
};
@@ -97,9 +97,9 @@ chromes_laptop_instantiate_i2c_device(struct i2c_adapter *adapter,
};
struct i2c_client *dummy;
- dummy = i2c_new_probed_device(adapter, &dummy_info,
- alt_addr_list, NULL);
- if (dummy) {
+ dummy = i2c_new_scanned_device(adapter, &dummy_info,
+ alt_addr_list, NULL);
+ if (!IS_ERR(dummy)) {
pr_debug("%d-%02x is probed at %02x\n",
adapter->nr, info->addr, dummy->addr);
i2c_unregister_device(dummy);
@@ -107,12 +107,14 @@ chromes_laptop_instantiate_i2c_device(struct i2c_adapter *adapter,
}
}
- if (!client)
+ if (IS_ERR(client)) {
+ client = NULL;
pr_debug("failed to register device %d-%02x\n",
adapter->nr, info->addr);
- else
+ } else {
pr_debug("added i2c device %d-%02x\n",
adapter->nr, info->addr);
+ }
return client;
}
@@ -838,18 +840,14 @@ static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop)
i2c_dev = &cros_laptop->i2c_peripherals[i];
info = &i2c_dev->board_info;
- if (i2c_dev->client)
- i2c_unregister_device(i2c_dev->client);
-
- if (info->properties)
- property_entries_free(info->properties);
+ i2c_unregister_device(i2c_dev->client);
+ property_entries_free(info->properties);
}
for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) {
acpi_dev = &cros_laptop->acpi_peripherals[i];
- if (acpi_dev->properties)
- property_entries_free(acpi_dev->properties);
+ property_entries_free(acpi_dev->properties);
}
kfree(cros_laptop->i2c_peripherals);
diff --git a/drivers/platform/chrome/chromeos_tbmc.c b/drivers/platform/chrome/chromeos_tbmc.c
index ce259ec9f990..d1cf8f3463ce 100644
--- a/drivers/platform/chrome/chromeos_tbmc.c
+++ b/drivers/platform/chrome/chromeos_tbmc.c
@@ -47,6 +47,7 @@ static __maybe_unused int chromeos_tbmc_resume(struct device *dev)
static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event)
{
+ acpi_pm_wakeup_event(&adev->dev);
switch (event) {
case 0x80:
chromeos_tbmc_query_switch(adev, adev->driver_data);
@@ -90,6 +91,7 @@ static int chromeos_tbmc_add(struct acpi_device *adev)
dev_err(dev, "cannot register input device\n");
return ret;
}
+ device_init_wakeup(dev, true);
return 0;
}
diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c
new file mode 100644
index 000000000000..6fc8f2c3ac51
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ChromeOS EC multi-function device
+ *
+ * Copyright (C) 2012 Google, Inc
+ *
+ * The ChromeOS EC multi function device is used to mux all the requests
+ * to the EC device for its multiple features: keyboard controller,
+ * battery charging and regulator control, firmware update.
+ */
+
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/suspend.h>
+
+#include "cros_ec.h"
+
+#define CROS_EC_DEV_EC_INDEX 0
+#define CROS_EC_DEV_PD_INDEX 1
+
+static struct cros_ec_platform ec_p = {
+ .ec_name = CROS_EC_DEV_NAME,
+ .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX),
+};
+
+static struct cros_ec_platform pd_p = {
+ .ec_name = CROS_EC_DEV_PD_NAME,
+ .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX),
+};
+
+static irqreturn_t ec_irq_handler(int irq, void *data)
+{
+ struct cros_ec_device *ec_dev = data;
+
+ ec_dev->last_event_time = cros_ec_get_time_ns();
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * cros_ec_handle_event() - process and forward pending events on EC
+ * @ec_dev: Device with events to process.
+ *
+ * Call this function in a loop when the kernel is notified that the EC has
+ * pending events.
+ *
+ * Return: true if more events are still pending and this function should be
+ * called again.
+ */
+bool cros_ec_handle_event(struct cros_ec_device *ec_dev)
+{
+ bool wake_event;
+ bool ec_has_more_events;
+ int ret;
+
+ ret = cros_ec_get_next_event(ec_dev, &wake_event, &ec_has_more_events);
+
+ /*
+ * 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);
+
+ if (ret > 0)
+ blocking_notifier_call_chain(&ec_dev->event_notifier,
+ 0, ec_dev);
+
+ return ec_has_more_events;
+}
+EXPORT_SYMBOL(cros_ec_handle_event);
+
+static irqreturn_t ec_irq_thread(int irq, void *data)
+{
+ struct cros_ec_device *ec_dev = data;
+ bool ec_has_more_events;
+
+ do {
+ ec_has_more_events = cros_ec_handle_event(ec_dev);
+ } while (ec_has_more_events);
+
+ return IRQ_HANDLED;
+}
+
+static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
+{
+ int ret;
+ struct {
+ struct cros_ec_command msg;
+ union {
+ struct ec_params_host_sleep_event req0;
+ struct ec_params_host_sleep_event_v1 req1;
+ struct ec_response_host_sleep_event_v1 resp1;
+ } u;
+ } __packed buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ if (ec_dev->host_sleep_v1) {
+ buf.u.req1.sleep_event = sleep_event;
+ buf.u.req1.suspend_params.sleep_timeout_ms =
+ EC_HOST_SLEEP_TIMEOUT_DEFAULT;
+
+ buf.msg.outsize = sizeof(buf.u.req1);
+ if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) ||
+ (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME))
+ buf.msg.insize = sizeof(buf.u.resp1);
+
+ buf.msg.version = 1;
+
+ } else {
+ buf.u.req0.sleep_event = sleep_event;
+ buf.msg.outsize = sizeof(buf.u.req0);
+ }
+
+ buf.msg.command = EC_CMD_HOST_SLEEP_EVENT;
+
+ ret = cros_ec_cmd_xfer(ec_dev, &buf.msg);
+
+ /* For now, report failure to transition to S0ix with a warning. */
+ if (ret >= 0 && ec_dev->host_sleep_v1 &&
+ (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) {
+ ec_dev->last_resume_result =
+ buf.u.resp1.resume_response.sleep_transitions;
+
+ WARN_ONCE(buf.u.resp1.resume_response.sleep_transitions &
+ EC_HOST_RESUME_SLEEP_TIMEOUT,
+ "EC detected sleep transition timeout. Total slp_s0 transitions: %d",
+ buf.u.resp1.resume_response.sleep_transitions &
+ EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK);
+ }
+
+ return ret;
+}
+
+/**
+ * cros_ec_register() - Register a new ChromeOS EC, using the provided info.
+ * @ec_dev: Device to register.
+ *
+ * Before calling this, allocate a pointer to a new device and then fill
+ * in all the fields up to the --private-- marker.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cros_ec_register(struct cros_ec_device *ec_dev)
+{
+ struct device *dev = ec_dev->dev;
+ int err = 0;
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
+
+ ec_dev->max_request = sizeof(struct ec_params_hello);
+ ec_dev->max_response = sizeof(struct ec_response_get_protocol_info);
+ ec_dev->max_passthru = 0;
+
+ ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
+ if (!ec_dev->din)
+ return -ENOMEM;
+
+ ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
+ if (!ec_dev->dout)
+ return -ENOMEM;
+
+ mutex_init(&ec_dev->lock);
+
+ err = cros_ec_query_all(ec_dev);
+ if (err) {
+ dev_err(dev, "Cannot identify the EC: error %d\n", err);
+ return err;
+ }
+
+ if (ec_dev->irq > 0) {
+ err = devm_request_threaded_irq(dev, ec_dev->irq,
+ ec_irq_handler,
+ ec_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "chromeos-ec", ec_dev);
+ if (err) {
+ dev_err(dev, "Failed to request IRQ %d: %d",
+ ec_dev->irq, err);
+ return err;
+ }
+ }
+
+ /* Register a platform device for the main EC instance */
+ ec_dev->ec = platform_device_register_data(ec_dev->dev, "cros-ec-dev",
+ PLATFORM_DEVID_AUTO, &ec_p,
+ sizeof(struct cros_ec_platform));
+ if (IS_ERR(ec_dev->ec)) {
+ dev_err(ec_dev->dev,
+ "Failed to create CrOS EC platform device\n");
+ return PTR_ERR(ec_dev->ec);
+ }
+
+ if (ec_dev->max_passthru) {
+ /*
+ * Register a platform device for the PD behind the main EC.
+ * We make the following assumptions:
+ * - behind an EC, we have a pd
+ * - only one device added.
+ * - the EC is responsive at init time (it is not true for a
+ * sensor hub).
+ */
+ ec_dev->pd = platform_device_register_data(ec_dev->dev,
+ "cros-ec-dev",
+ PLATFORM_DEVID_AUTO, &pd_p,
+ sizeof(struct cros_ec_platform));
+ if (IS_ERR(ec_dev->pd)) {
+ dev_err(ec_dev->dev,
+ "Failed to create CrOS PD platform device\n");
+ platform_device_unregister(ec_dev->ec);
+ return PTR_ERR(ec_dev->pd);
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+ err = devm_of_platform_populate(dev);
+ if (err) {
+ platform_device_unregister(ec_dev->pd);
+ platform_device_unregister(ec_dev->ec);
+ dev_err(dev, "Failed to register sub-devices\n");
+ return err;
+ }
+ }
+
+ /*
+ * Clear sleep event - this will fail harmlessly on platforms that
+ * don't implement the sleep event host command.
+ */
+ err = cros_ec_sleep_event(ec_dev, 0);
+ if (err < 0)
+ dev_dbg(ec_dev->dev, "Error %d clearing sleep event to ec",
+ err);
+
+ dev_info(dev, "Chrome EC device registered\n");
+
+ return 0;
+}
+EXPORT_SYMBOL(cros_ec_register);
+
+/**
+ * cros_ec_unregister() - Remove a ChromeOS EC.
+ * @ec_dev: Device to unregister.
+ *
+ * Call this to deregister a ChromeOS EC, then clean up any private data.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cros_ec_unregister(struct cros_ec_device *ec_dev)
+{
+ if (ec_dev->pd)
+ platform_device_unregister(ec_dev->pd);
+ platform_device_unregister(ec_dev->ec);
+
+ return 0;
+}
+EXPORT_SYMBOL(cros_ec_unregister);
+
+#ifdef CONFIG_PM_SLEEP
+/**
+ * cros_ec_suspend() - Handle a suspend operation for the ChromeOS EC device.
+ * @ec_dev: Device to suspend.
+ *
+ * This can be called by drivers to handle a suspend event.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cros_ec_suspend(struct cros_ec_device *ec_dev)
+{
+ struct device *dev = ec_dev->dev;
+ int ret;
+ u8 sleep_event;
+
+ sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ?
+ HOST_SLEEP_EVENT_S3_SUSPEND :
+ HOST_SLEEP_EVENT_S0IX_SUSPEND;
+
+ ret = cros_ec_sleep_event(ec_dev, sleep_event);
+ if (ret < 0)
+ dev_dbg(ec_dev->dev, "Error %d sending suspend event to ec",
+ ret);
+
+ if (device_may_wakeup(dev))
+ ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
+
+ disable_irq(ec_dev->irq);
+ ec_dev->was_wake_device = ec_dev->wake_enabled;
+ ec_dev->suspended = true;
+
+ return 0;
+}
+EXPORT_SYMBOL(cros_ec_suspend);
+
+static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev)
+{
+ while (ec_dev->mkbp_event_supported &&
+ cros_ec_get_next_event(ec_dev, NULL, NULL) > 0)
+ blocking_notifier_call_chain(&ec_dev->event_notifier,
+ 1, ec_dev);
+}
+
+/**
+ * cros_ec_resume() - Handle a resume operation for the ChromeOS EC device.
+ * @ec_dev: Device to resume.
+ *
+ * This can be called by drivers to handle a resume event.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cros_ec_resume(struct cros_ec_device *ec_dev)
+{
+ int ret;
+ u8 sleep_event;
+
+ ec_dev->suspended = false;
+ enable_irq(ec_dev->irq);
+
+ sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ?
+ HOST_SLEEP_EVENT_S3_RESUME :
+ HOST_SLEEP_EVENT_S0IX_RESUME;
+
+ ret = cros_ec_sleep_event(ec_dev, sleep_event);
+ if (ret < 0)
+ dev_dbg(ec_dev->dev, "Error %d sending resume event to ec",
+ ret);
+
+ if (ec_dev->wake_enabled) {
+ disable_irq_wake(ec_dev->irq);
+ ec_dev->wake_enabled = 0;
+ }
+ /*
+ * Let the mfd devices know about events that occur during
+ * suspend. This way the clients know what to do with them.
+ */
+ cros_ec_report_events_during_suspend(ec_dev);
+
+
+ return 0;
+}
+EXPORT_SYMBOL(cros_ec_resume);
+
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ChromeOS EC core driver");
diff --git a/drivers/platform/chrome/cros_ec.h b/drivers/platform/chrome/cros_ec.h
new file mode 100644
index 000000000000..e69fc1ff68b4
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ChromeOS Embedded Controller core interface.
+ *
+ * Copyright (C) 2020 Google LLC
+ */
+
+#ifndef __CROS_EC_H
+#define __CROS_EC_H
+
+int cros_ec_register(struct cros_ec_device *ec_dev);
+int cros_ec_unregister(struct cros_ec_device *ec_dev);
+
+int cros_ec_suspend(struct cros_ec_device *ec_dev);
+int cros_ec_resume(struct cros_ec_device *ec_dev);
+
+bool cros_ec_handle_event(struct cros_ec_device *ec_dev);
+
+#endif /* __CROS_EC_H */
diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c
new file mode 100644
index 000000000000..c65e70bc168d
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_chardev.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Miscellaneous character driver for ChromeOS Embedded Controller
+ *
+ * Copyright 2014 Google, Inc.
+ * Copyright 2019 Google LLC
+ *
+ * This file is a rework and part of the code is ported from
+ * drivers/mfd/cros_ec_dev.c that was originally written by
+ * Bill Richardson.
+ */
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_data/cros_ec_chardev.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+#define DRV_NAME "cros-ec-chardev"
+
+/* Arbitrary bounded size for the event queue */
+#define CROS_MAX_EVENT_LEN PAGE_SIZE
+
+struct chardev_data {
+ struct cros_ec_dev *ec_dev;
+ struct miscdevice misc;
+};
+
+struct chardev_priv {
+ struct cros_ec_dev *ec_dev;
+ struct notifier_block notifier;
+ wait_queue_head_t wait_event;
+ unsigned long event_mask;
+ struct list_head events;
+ size_t event_len;
+};
+
+struct ec_event {
+ struct list_head node;
+ size_t size;
+ u8 event_type;
+ u8 data[0];
+};
+
+static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen)
+{
+ static const char * const current_image_name[] = {
+ "unknown", "read-only", "read-write", "invalid",
+ };
+ struct ec_response_get_version *resp;
+ struct cros_ec_command *msg;
+ int ret;
+
+ msg = kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
+ msg->insize = sizeof(*resp);
+
+ ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
+ if (ret < 0) {
+ snprintf(str, maxlen,
+ "Unknown EC version, returned error: %d\n",
+ msg->result);
+ goto exit;
+ }
+
+ resp = (struct ec_response_get_version *)msg->data;
+ 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]);
+
+ ret = 0;
+exit:
+ kfree(msg);
+ return ret;
+}
+
+static int cros_ec_chardev_mkbp_event(struct notifier_block *nb,
+ unsigned long queued_during_suspend,
+ void *_notify)
+{
+ struct chardev_priv *priv = container_of(nb, struct chardev_priv,
+ notifier);
+ struct cros_ec_device *ec_dev = priv->ec_dev->ec_dev;
+ struct ec_event *event;
+ unsigned long event_bit = 1 << ec_dev->event_data.event_type;
+ int total_size = sizeof(*event) + ec_dev->event_size;
+
+ if (!(event_bit & priv->event_mask) ||
+ (priv->event_len + total_size) > CROS_MAX_EVENT_LEN)
+ return NOTIFY_DONE;
+
+ event = kzalloc(total_size, GFP_KERNEL);
+ if (!event)
+ return NOTIFY_DONE;
+
+ event->size = ec_dev->event_size;
+ event->event_type = ec_dev->event_data.event_type;
+ memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size);
+
+ spin_lock(&priv->wait_event.lock);
+ list_add_tail(&event->node, &priv->events);
+ priv->event_len += total_size;
+ wake_up_locked(&priv->wait_event);
+ spin_unlock(&priv->wait_event.lock);
+
+ return NOTIFY_OK;
+}
+
+static struct ec_event *cros_ec_chardev_fetch_event(struct chardev_priv *priv,
+ bool fetch, bool block)
+{
+ struct ec_event *event;
+ int err;
+
+ spin_lock(&priv->wait_event.lock);
+ if (!block && list_empty(&priv->events)) {
+ event = ERR_PTR(-EWOULDBLOCK);
+ goto out;
+ }
+
+ if (!fetch) {
+ event = NULL;
+ goto out;
+ }
+
+ err = wait_event_interruptible_locked(priv->wait_event,
+ !list_empty(&priv->events));
+ if (err) {
+ event = ERR_PTR(err);
+ goto out;
+ }
+
+ event = list_first_entry(&priv->events, struct ec_event, node);
+ list_del(&event->node);
+ priv->event_len -= sizeof(*event) + event->size;
+
+out:
+ spin_unlock(&priv->wait_event.lock);
+ return event;
+}
+
+/*
+ * Device file ops
+ */
+static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
+{
+ struct miscdevice *mdev = filp->private_data;
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(mdev->parent);
+ struct chardev_priv *priv;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->ec_dev = ec_dev;
+ filp->private_data = priv;
+ INIT_LIST_HEAD(&priv->events);
+ init_waitqueue_head(&priv->wait_event);
+ nonseekable_open(inode, filp);
+
+ priv->notifier.notifier_call = cros_ec_chardev_mkbp_event;
+ ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier,
+ &priv->notifier);
+ if (ret) {
+ dev_err(ec_dev->dev, "failed to register event notifier\n");
+ kfree(priv);
+ }
+
+ return ret;
+}
+
+static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait)
+{
+ struct chardev_priv *priv = filp->private_data;
+
+ poll_wait(filp, &priv->wait_event, wait);
+
+ if (list_empty(&priv->events))
+ return 0;
+
+ return EPOLLIN | EPOLLRDNORM;
+}
+
+static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
+ size_t length, loff_t *offset)
+{
+ char msg[sizeof(struct ec_response_get_version) +
+ sizeof(CROS_EC_DEV_VERSION)];
+ struct chardev_priv *priv = filp->private_data;
+ struct cros_ec_dev *ec_dev = priv->ec_dev;
+ size_t count;
+ int ret;
+
+ if (priv->event_mask) { /* queued MKBP event */
+ struct ec_event *event;
+
+ event = cros_ec_chardev_fetch_event(priv, length != 0,
+ !(filp->f_flags & O_NONBLOCK));
+ if (IS_ERR(event))
+ return PTR_ERR(event);
+ /*
+ * length == 0 is special - no IO is done but we check
+ * for error conditions.
+ */
+ if (length == 0)
+ return 0;
+
+ /* The event is 1 byte of type plus the payload */
+ count = min(length, event->size + 1);
+ ret = copy_to_user(buffer, &event->event_type, count);
+ kfree(event);
+ if (ret) /* the copy failed */
+ return -EFAULT;
+ *offset = count;
+ return count;
+ }
+
+ /*
+ * Legacy behavior if no event mask is defined
+ */
+ if (*offset != 0)
+ return 0;
+
+ ret = ec_get_version(ec_dev, 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;
+}
+
+static int cros_ec_chardev_release(struct inode *inode, struct file *filp)
+{
+ struct chardev_priv *priv = filp->private_data;
+ struct cros_ec_dev *ec_dev = priv->ec_dev;
+ struct ec_event *event, *e;
+
+ blocking_notifier_chain_unregister(&ec_dev->ec_dev->event_notifier,
+ &priv->notifier);
+
+ list_for_each_entry_safe(event, e, &priv->events, node) {
+ list_del(&event->node);
+ kfree(event);
+ }
+ kfree(priv);
+
+ return 0;
+}
+
+/*
+ * Ioctls
+ */
+static long cros_ec_chardev_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg)
+{
+ struct cros_ec_command *s_cmd;
+ struct cros_ec_command u_cmd;
+ long ret;
+
+ if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
+ return -EFAULT;
+
+ if (u_cmd.outsize > EC_MAX_MSG_BYTES ||
+ u_cmd.insize > EC_MAX_MSG_BYTES)
+ return -EINVAL;
+
+ s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize),
+ GFP_KERNEL);
+ if (!s_cmd)
+ return -ENOMEM;
+
+ if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ if (u_cmd.outsize != s_cmd->outsize ||
+ u_cmd.insize != s_cmd->insize) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ s_cmd->command += ec->cmd_offset;
+ ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd);
+ /* Only copy data to userland if data was received. */
+ if (ret < 0)
+ goto exit;
+
+ if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize))
+ ret = -EFAULT;
+exit:
+ kfree(s_cmd);
+ return ret;
+}
+
+static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec,
+ void __user *arg)
+{
+ struct cros_ec_device *ec_dev = ec->ec_dev;
+ struct cros_ec_readmem s_mem = { };
+ long num;
+
+ /* Not every platform supports direct reads */
+ if (!ec_dev->cmd_readmem)
+ return -ENOTTY;
+
+ if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
+ return -EFAULT;
+
+ num = ec_dev->cmd_readmem(ec_dev, 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 num;
+}
+
+static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct chardev_priv *priv = filp->private_data;
+ struct cros_ec_dev *ec = priv->ec_dev;
+
+ if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case CROS_EC_DEV_IOCXCMD:
+ return cros_ec_chardev_ioctl_xcmd(ec, (void __user *)arg);
+ case CROS_EC_DEV_IOCRDMEM:
+ return cros_ec_chardev_ioctl_readmem(ec, (void __user *)arg);
+ case CROS_EC_DEV_IOCEVENTMASK:
+ priv->event_mask = arg;
+ return 0;
+ }
+
+ return -ENOTTY;
+}
+
+static const struct file_operations chardev_fops = {
+ .open = cros_ec_chardev_open,
+ .poll = cros_ec_chardev_poll,
+ .read = cros_ec_chardev_read,
+ .release = cros_ec_chardev_release,
+ .unlocked_ioctl = cros_ec_chardev_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = cros_ec_chardev_ioctl,
+#endif
+};
+
+static int cros_ec_chardev_probe(struct platform_device *pdev)
+{
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
+ struct cros_ec_platform *ec_platform = dev_get_platdata(ec_dev->dev);
+ struct chardev_data *data;
+
+ /* Create a char device: we want to create it anew */
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->ec_dev = ec_dev;
+ data->misc.minor = MISC_DYNAMIC_MINOR;
+ data->misc.fops = &chardev_fops;
+ data->misc.name = ec_platform->ec_name;
+ data->misc.parent = pdev->dev.parent;
+
+ dev_set_drvdata(&pdev->dev, data);
+
+ return misc_register(&data->misc);
+}
+
+static int cros_ec_chardev_remove(struct platform_device *pdev)
+{
+ struct chardev_data *data = dev_get_drvdata(&pdev->dev);
+
+ misc_deregister(&data->misc);
+
+ return 0;
+}
+
+static struct platform_driver cros_ec_chardev_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = cros_ec_chardev_probe,
+ .remove = cros_ec_chardev_remove,
+};
+
+module_platform_driver(cros_ec_chardev_driver);
+
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>");
+MODULE_DESCRIPTION("ChromeOS EC Miscellaneous Character Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c
index 8ec1cc2889f2..ecfada00e6c5 100644
--- a/drivers/platform/chrome/cros_ec_debugfs.c
+++ b/drivers/platform/chrome/cros_ec_debugfs.c
@@ -7,10 +7,10 @@
#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/module.h>
#include <linux/mutex.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c
index 61d75395f86d..6119eccd8a18 100644
--- a/drivers/platform/chrome/cros_ec_i2c.c
+++ b/drivers/platform/chrome/cros_ec_i2c.c
@@ -9,11 +9,13 @@
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include "cros_ec.h"
+
/**
* Request format for protocol v3
* byte 0 0xda (EC_COMMAND_PROTOCOL_3)
@@ -307,6 +309,13 @@ static int cros_ec_i2c_probe(struct i2c_client *client,
return 0;
}
+static int cros_ec_i2c_remove(struct i2c_client *client)
+{
+ struct cros_ec_device *ec_dev = i2c_get_clientdata(client);
+
+ return cros_ec_unregister(ec_dev);
+}
+
#ifdef CONFIG_PM_SLEEP
static int cros_ec_i2c_suspend(struct device *dev)
{
@@ -357,6 +366,7 @@ static struct i2c_driver cros_ec_driver = {
.pm = &cros_ec_i2c_pm_ops,
},
.probe = cros_ec_i2c_probe,
+ .remove = cros_ec_i2c_remove,
.id_table = cros_ec_i2c_id,
};
diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c
index e504d255d5ce..93a71e93a2f1 100644
--- a/drivers/platform/chrome/cros_ec_ishtp.c
+++ b/drivers/platform/chrome/cros_ec_ishtp.c
@@ -8,13 +8,14 @@
// (ISH-TP).
#include <linux/delay.h>
-#include <linux/mfd/core.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/intel-ish-client-if.h>
+#include "cros_ec.h"
+
/*
* ISH TX/RX ring buffer pool size
*
@@ -77,7 +78,7 @@ struct cros_ish_in_msg {
*
* The writers are .reset() and .probe() function.
*/
-DECLARE_RWSEM(init_lock);
+static DECLARE_RWSEM(init_lock);
/**
* struct response_info - Encapsulate firmware response related
@@ -137,11 +138,11 @@ static void ish_evt_handler(struct work_struct *work)
struct ishtp_cl_data *client_data =
container_of(work, struct ishtp_cl_data, work_ec_evt);
struct cros_ec_device *ec_dev = client_data->ec_dev;
+ bool ec_has_more_events;
- if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
- blocking_notifier_call_chain(&ec_dev->event_notifier,
- 0, ec_dev);
- }
+ do {
+ ec_has_more_events = cros_ec_handle_event(ec_dev);
+ } while (ec_has_more_events);
}
/**
@@ -201,13 +202,14 @@ static int ish_send(struct ishtp_cl_data *client_data,
* process_recv() - Received and parse incoming packet
* @cros_ish_cl: Client instance to get stats
* @rb_in_proc: Host interface message buffer
+ * @timestamp: Timestamp of when parent callback started
*
* Parse the incoming packet. If it is a response packet then it will
* update per instance flags and wake up the caller waiting to for the
* response. If it is an event packet then it will schedule event work.
*/
static void process_recv(struct ishtp_cl *cros_ish_cl,
- struct ishtp_cl_rb *rb_in_proc)
+ struct ishtp_cl_rb *rb_in_proc, ktime_t timestamp)
{
size_t data_len = rb_in_proc->buf_idx;
struct ishtp_cl_data *client_data =
@@ -296,6 +298,11 @@ error_wake_up:
break;
case CROS_MKBP_EVENT:
+ /*
+ * Set timestamp from beginning of function since we actually
+ * got an incoming MKBP event
+ */
+ client_data->ec_dev->last_event_time = timestamp;
/* The event system doesn't send any data in buffer */
schedule_work(&client_data->work_ec_evt);
@@ -323,10 +330,17 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device)
{
struct ishtp_cl_rb *rb_in_proc;
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
+ ktime_t timestamp;
+
+ /*
+ * Take timestamp as close to hardware interrupt as possible for sensor
+ * timestamps.
+ */
+ timestamp = cros_ec_get_time_ns();
while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
/* Decide what to do with received data */
- process_recv(cros_ish_cl, rb_in_proc);
+ process_recv(cros_ish_cl, rb_in_proc, timestamp);
}
}
@@ -707,7 +721,7 @@ static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
*/
static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
{
- struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
+ struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device);
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
@@ -722,7 +736,7 @@ static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
*/
static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
{
- struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
+ struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device);
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c
index 609598bbb6c3..b4c110c5fee0 100644
--- a/drivers/platform/chrome/cros_ec_lightbar.c
+++ b/drivers/platform/chrome/cros_ec_lightbar.c
@@ -8,9 +8,9 @@
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kobject.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
index 2c44c7f3322a..1f7861944044 100644
--- a/drivers/platform/chrome/cros_ec_lpc.c
+++ b/drivers/platform/chrome/cros_ec_lpc.c
@@ -16,13 +16,14 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/interrupt.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/suspend.h>
+#include "cros_ec.h"
#include "cros_ec_lpc_mec.h"
#define DRV_NAME "cros_ec_lpcs"
@@ -312,11 +313,20 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data)
{
struct cros_ec_device *ec_dev = data;
+ bool ec_has_more_events;
+ int ret;
+
+ ec_dev->last_event_time = cros_ec_get_time_ns();
- 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);
+ if (ec_dev->mkbp_event_supported)
+ do {
+ ret = cros_ec_get_next_event(ec_dev, NULL,
+ &ec_has_more_events);
+ if (ret > 0)
+ blocking_notifier_call_chain(
+ &ec_dev->event_notifier, 0,
+ ec_dev);
+ } while (ec_has_more_events);
if (value == ACPI_NOTIFY_DEVICE_WAKE)
pm_system_wakeup();
@@ -387,7 +397,7 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
* Some boards do not have an IRQ allotted for cros_ec_lpc,
* which makes ENXIO an expected (and safe) scenario.
*/
- irq = platform_get_irq(pdev, 0);
+ irq = platform_get_irq_optional(pdev, 0);
if (irq > 0)
ec_dev->irq = irq;
else if (irq != -ENXIO) {
@@ -421,6 +431,7 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
static int cros_ec_lpc_remove(struct platform_device *pdev)
{
+ struct cros_ec_device *ec_dev = platform_get_drvdata(pdev);
struct acpi_device *adev;
adev = ACPI_COMPANION(&pdev->dev);
@@ -428,7 +439,7 @@ static int cros_ec_lpc_remove(struct platform_device *pdev)
acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
cros_ec_lpc_acpi_notify);
- return 0;
+ return cros_ec_unregister(ec_dev);
}
static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = {
diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c
index 3d2325197a68..3cfa643f1d07 100644
--- a/drivers/platform/chrome/cros_ec_proto.c
+++ b/drivers/platform/chrome/cros_ec_proto.c
@@ -3,10 +3,11 @@
//
// Copyright (C) 2015 Google, Inc
-#include <linux/mfd/cros_ec.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
@@ -53,8 +54,6 @@ static int send_command(struct cros_ec_device *ec_dev,
int ret;
int (*xfer_fxn)(struct cros_ec_device *ec, struct cros_ec_command *msg);
- trace_cros_ec_cmd(msg);
-
if (ec_dev->proto_version > 2)
xfer_fxn = ec_dev->pkt_xfer;
else
@@ -71,7 +70,9 @@ static int send_command(struct cros_ec_device *ec_dev,
return -EIO;
}
+ trace_cros_ec_request_start(msg);
ret = (*xfer_fxn)(ec_dev, msg);
+ trace_cros_ec_request_done(msg, ret);
if (msg->result == EC_RES_IN_PROGRESS) {
int i;
struct cros_ec_command *status_msg;
@@ -94,7 +95,9 @@ static int send_command(struct cros_ec_device *ec_dev,
for (i = 0; i < EC_COMMAND_RETRIES; i++) {
usleep_range(10000, 11000);
+ trace_cros_ec_request_start(status_msg);
ret = (*xfer_fxn)(ec_dev, status_msg);
+ trace_cros_ec_request_done(status_msg, ret);
if (ret == -EAGAIN)
continue;
if (ret < 0)
@@ -116,6 +119,17 @@ static int send_command(struct cros_ec_device *ec_dev,
return ret;
}
+/**
+ * cros_ec_prepare_tx() - Prepare an outgoing message in the output buffer.
+ * @ec_dev: Device to register.
+ * @msg: Message to write.
+ *
+ * This is intended to be used by all ChromeOS EC drivers, but at present
+ * only SPI uses it. Once LPC uses the same protocol it can start using it.
+ * I2C could use it now, with a refactor of the existing code.
+ *
+ * Return: 0 on success or negative error code.
+ */
int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg)
{
@@ -140,6 +154,16 @@ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
}
EXPORT_SYMBOL(cros_ec_prepare_tx);
+/**
+ * cros_ec_check_result() - Check ec_msg->result.
+ * @ec_dev: EC device.
+ * @msg: Message to check.
+ *
+ * This is used by ChromeOS EC drivers to check the ec_msg->result for
+ * errors and to warn about them.
+ *
+ * Return: 0 on success or negative error code.
+ */
int cros_ec_check_result(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg)
{
@@ -325,6 +349,13 @@ static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev,
return ret;
}
+/**
+ * cros_ec_query_all() - Query the protocol version supported by the
+ * ChromeOS EC.
+ * @ec_dev: Device to register.
+ *
+ * Return: 0 on success or negative error code.
+ */
int cros_ec_query_all(struct cros_ec_device *ec_dev)
{
struct device *dev = ec_dev->dev;
@@ -427,7 +458,10 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev)
if (ret < 0 || ver_mask == 0)
ec_dev->mkbp_event_supported = 0;
else
- ec_dev->mkbp_event_supported = 1;
+ ec_dev->mkbp_event_supported = fls(ver_mask);
+
+ dev_dbg(ec_dev->dev, "MKBP support version %u\n",
+ ec_dev->mkbp_event_supported - 1);
/* Probe if host sleep v1 is supported for S0ix failure detection. */
ret = cros_ec_get_host_command_version_mask(ec_dev,
@@ -452,6 +486,16 @@ exit:
}
EXPORT_SYMBOL(cros_ec_query_all);
+/**
+ * cros_ec_cmd_xfer() - Send a command to the ChromeOS EC.
+ * @ec_dev: EC device.
+ * @msg: Message to write.
+ *
+ * Call this to send a command to the ChromeOS EC. This should be used
+ * instead of calling the EC's cmd_xfer() callback directly.
+ *
+ * Return: 0 on success or negative error code.
+ */
int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg)
{
@@ -499,6 +543,18 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
}
EXPORT_SYMBOL(cros_ec_cmd_xfer);
+/**
+ * cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC.
+ * @ec_dev: EC device.
+ * @msg: Message to write.
+ *
+ * This function is identical to cros_ec_cmd_xfer, except it returns success
+ * status only if both the command was transmitted successfully and the EC
+ * replied with success status. It's not necessary to check msg->result when
+ * using this function.
+ *
+ * Return: The number of bytes transferred on success or negative error code.
+ */
int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg)
{
@@ -518,6 +574,7 @@ EXPORT_SYMBOL(cros_ec_cmd_xfer_status);
static int get_next_event_xfer(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg,
+ struct ec_response_get_next_event_v1 *event,
int version, uint32_t size)
{
int ret;
@@ -530,7 +587,7 @@ static int get_next_event_xfer(struct cros_ec_device *ec_dev,
ret = cros_ec_cmd_xfer(ec_dev, msg);
if (ret > 0) {
ec_dev->event_size = ret - 1;
- memcpy(&ec_dev->event_data, msg->data, ret);
+ ec_dev->event_data = *event;
}
return ret;
@@ -538,30 +595,26 @@ static int get_next_event_xfer(struct cros_ec_device *ec_dev,
static int get_next_event(struct cros_ec_device *ec_dev)
{
- u8 buffer[sizeof(struct cros_ec_command) + sizeof(ec_dev->event_data)];
- struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
- static int cmd_version = 1;
- int ret;
+ struct {
+ struct cros_ec_command msg;
+ struct ec_response_get_next_event_v1 event;
+ } __packed buf;
+ struct cros_ec_command *msg = &buf.msg;
+ struct ec_response_get_next_event_v1 *event = &buf.event;
+ const int cmd_version = ec_dev->mkbp_event_supported - 1;
+ memset(msg, 0, sizeof(*msg));
if (ec_dev->suspended) {
dev_dbg(ec_dev->dev, "Device suspended.\n");
return -EHOSTDOWN;
}
- if (cmd_version == 1) {
- ret = get_next_event_xfer(ec_dev, msg, cmd_version,
- sizeof(struct ec_response_get_next_event_v1));
- if (ret < 0 || msg->result != EC_RES_INVALID_VERSION)
- return ret;
-
- /* Fallback to version 0 for future send attempts */
- cmd_version = 0;
- }
-
- ret = get_next_event_xfer(ec_dev, msg, cmd_version,
+ if (cmd_version == 0)
+ return get_next_event_xfer(ec_dev, msg, event, 0,
sizeof(struct ec_response_get_next_event));
- return ret;
+ return get_next_event_xfer(ec_dev, msg, event, cmd_version,
+ sizeof(struct ec_response_get_next_event_v1));
}
static int get_keyboard_state_event(struct cros_ec_device *ec_dev)
@@ -583,27 +636,60 @@ 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, bool *wake_event)
+/**
+ * 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.
+ * @has_more_events: Pointer to bool set to true if more than one event is
+ * pending.
+ * Some EC will set this flag to indicate cros_ec_get_next_event()
+ * can be called multiple times in a row.
+ * It is an optimization to prevent issuing a EC command for
+ * nothing or wait for another interrupt from the EC to process
+ * the next message.
+ * Ignored if null.
+ *
+ * Return: negative error code on errors; 0 for no data; or else number of
+ * bytes received (i.e., an event was retrieved successfully). Event types are
+ * written out to @ec_dev->event_data.event_type on success.
+ */
+int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
+ bool *wake_event,
+ bool *has_more_events)
{
u8 event_type;
u32 host_event;
int ret;
- if (!ec_dev->mkbp_event_supported) {
- ret = get_keyboard_state_event(ec_dev);
- if (ret <= 0)
- return ret;
+ /*
+ * Default value for wake_event.
+ * Wake up on keyboard event, wake up for spurious interrupt or link
+ * error to the EC.
+ */
+ if (wake_event)
+ *wake_event = true;
- if (wake_event)
- *wake_event = true;
+ /*
+ * Default value for has_more_events.
+ * EC will raise another interrupt if AP does not process all events
+ * anyway.
+ */
+ if (has_more_events)
+ *has_more_events = false;
- return ret;
- }
+ if (!ec_dev->mkbp_event_supported)
+ return get_keyboard_state_event(ec_dev);
ret = get_next_event(ec_dev);
if (ret <= 0)
return ret;
+ if (has_more_events)
+ *has_more_events = ec_dev->event_data.event_type &
+ EC_MKBP_HAS_MORE_EVENTS;
+ ec_dev->event_data.event_type &= EC_MKBP_EVENT_TYPE_MASK;
+
if (wake_event) {
event_type = ec_dev->event_data.event_type;
host_event = cros_ec_get_host_event(ec_dev);
@@ -618,15 +704,22 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event)
else if (host_event &&
!(host_event & ec_dev->host_event_wake_mask))
*wake_event = false;
- /* Consider all other events as wake events. */
- else
- *wake_event = true;
}
return ret;
}
EXPORT_SYMBOL(cros_ec_get_next_event);
+/**
+ * cros_ec_get_host_event() - Return a mask of event set by the ChromeOS EC.
+ * @ec_dev: Device to fetch event from.
+ *
+ * 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.
+ *
+ * Return: 0 on error or non-zero bitmask of one or more EC_HOST_EVENT_*.
+ */
u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev)
{
u32 host_event;
@@ -646,3 +739,120 @@ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev)
return host_event;
}
EXPORT_SYMBOL(cros_ec_get_host_event);
+
+/**
+ * cros_ec_check_features() - Test for the presence of EC features
+ *
+ * @ec: EC device, does not have to be connected directly to the AP,
+ * can be daisy chained through another device.
+ * @feature: One of ec_feature_code bit.
+ *
+ * Call this function to test whether the ChromeOS EC supports a feature.
+ *
+ * Return: 1 if supported, 0 if not
+ */
+int cros_ec_check_features(struct cros_ec_dev *ec, int feature)
+{
+ struct cros_ec_command *msg;
+ int ret;
+
+ if (ec->features[0] == -1U && ec->features[1] == -1U) {
+ /* features bitmap not read yet */
+ msg = kzalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset;
+ msg->insize = sizeof(ec->features);
+
+ ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
+ if (ret < 0) {
+ dev_warn(ec->dev, "cannot get EC features: %d/%d\n",
+ ret, msg->result);
+ memset(ec->features, 0, sizeof(ec->features));
+ } else {
+ memcpy(ec->features, msg->data, sizeof(ec->features));
+ }
+
+ dev_dbg(ec->dev, "EC features %08x %08x\n",
+ ec->features[0], ec->features[1]);
+
+ kfree(msg);
+ }
+
+ return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature);
+}
+EXPORT_SYMBOL_GPL(cros_ec_check_features);
+
+/**
+ * cros_ec_get_sensor_count() - Return the number of MEMS sensors supported.
+ *
+ * @ec: EC device, does not have to be connected directly to the AP,
+ * can be daisy chained through another device.
+ * Return: < 0 in case of error.
+ */
+int cros_ec_get_sensor_count(struct cros_ec_dev *ec)
+{
+ /*
+ * Issue a command to get the number of sensor reported.
+ * If not supported, check for legacy mode.
+ */
+ int ret, sensor_count;
+ struct ec_params_motion_sense *params;
+ struct ec_response_motion_sense *resp;
+ struct cros_ec_command *msg;
+ struct cros_ec_device *ec_dev = ec->ec_dev;
+ u8 status;
+
+ msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)),
+ GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->version = 1;
+ msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
+ msg->outsize = sizeof(*params);
+ msg->insize = sizeof(*resp);
+
+ params = (struct ec_params_motion_sense *)msg->data;
+ params->cmd = MOTIONSENSE_CMD_DUMP;
+
+ ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
+ if (ret < 0) {
+ sensor_count = ret;
+ } else if (msg->result != EC_RES_SUCCESS) {
+ sensor_count = -EPROTO;
+ } else {
+ resp = (struct ec_response_motion_sense *)msg->data;
+ sensor_count = resp->dump.sensor_count;
+ }
+ kfree(msg);
+
+ /*
+ * Check legacy mode: Let's find out if sensors are accessible
+ * via LPC interface.
+ */
+ if (sensor_count == -EPROTO &&
+ ec->cmd_offset == 0 &&
+ ec_dev->cmd_readmem) {
+ ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS,
+ 1, &status);
+ if (ret >= 0 &&
+ (status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) {
+ /*
+ * We have 2 sensors, one in the lid, one in the base.
+ */
+ sensor_count = 2;
+ } else {
+ /*
+ * EC uses LPC interface and no sensors are presented.
+ */
+ sensor_count = 0;
+ }
+ } else if (sensor_count == -EPROTO) {
+ /* EC responded, but does not understand DUMP command. */
+ sensor_count = 0;
+ }
+ return sensor_count;
+}
+EXPORT_SYMBOL_GPL(cros_ec_get_sensor_count);
diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c
index 5d3fb2abad1d..dbc3f5523b83 100644
--- a/drivers/platform/chrome/cros_ec_rpmsg.c
+++ b/drivers/platform/chrome/cros_ec_rpmsg.c
@@ -6,13 +6,15 @@
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/of.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
+#include "cros_ec.h"
+
#define EC_MSG_TIMEOUT_MS 200
#define HOST_COMMAND_MARK 1
#define HOST_EVENT_MARK 2
@@ -41,6 +43,7 @@ struct cros_ec_rpmsg {
struct rpmsg_device *rpdev;
struct completion xfer_ack;
struct work_struct host_event_work;
+ struct rpmsg_endpoint *ept;
};
/**
@@ -72,7 +75,6 @@ static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev,
struct cros_ec_command *ec_msg)
{
struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;
- struct rpmsg_device *rpdev = ec_rpmsg->rpdev;
struct ec_host_response *response;
unsigned long timeout;
int len;
@@ -85,7 +87,7 @@ static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev,
dev_dbg(ec_dev->dev, "prepared, len=%d\n", len);
reinit_completion(&ec_rpmsg->xfer_ack);
- ret = rpmsg_send(rpdev->ept, ec_dev->dout, len);
+ ret = rpmsg_send(ec_rpmsg->ept, ec_dev->dout, len);
if (ret) {
dev_err(ec_dev->dev, "rpmsg send failed\n");
return ret;
@@ -143,22 +145,11 @@ cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work)
struct cros_ec_rpmsg,
host_event_work);
struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev);
- bool wake_event = true;
- int ret;
-
- 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);
+ bool ec_has_more_events;
- if (ret > 0)
- blocking_notifier_call_chain(&ec_dev->event_notifier,
- 0, ec_dev);
+ do {
+ ec_has_more_events = cros_ec_handle_event(ec_dev);
+ } while (ec_has_more_events);
}
static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
@@ -196,11 +187,24 @@ static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
return 0;
}
+static struct rpmsg_endpoint *
+cros_ec_rpmsg_create_ept(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_channel_info chinfo = {};
+
+ strscpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
+ chinfo.src = rpdev->src;
+ chinfo.dst = RPMSG_ADDR_ANY;
+
+ return rpmsg_create_ept(rpdev, cros_ec_rpmsg_callback, NULL, chinfo);
+}
+
static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct device *dev = &rpdev->dev;
struct cros_ec_rpmsg *ec_rpmsg;
struct cros_ec_device *ec_dev;
+ int ret;
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
if (!ec_dev)
@@ -225,7 +229,18 @@ static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev)
INIT_WORK(&ec_rpmsg->host_event_work,
cros_ec_rpmsg_host_event_function);
- return cros_ec_register(ec_dev);
+ ec_rpmsg->ept = cros_ec_rpmsg_create_ept(rpdev);
+ if (!ec_rpmsg->ept)
+ return -ENOMEM;
+
+ ret = cros_ec_register(ec_dev);
+ if (ret < 0) {
+ rpmsg_destroy_ept(ec_rpmsg->ept);
+ cancel_work_sync(&ec_rpmsg->host_event_work);
+ return ret;
+ }
+
+ return 0;
}
static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev)
@@ -233,9 +248,30 @@ static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev)
struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev);
struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;
+ cros_ec_unregister(ec_dev);
+ rpmsg_destroy_ept(ec_rpmsg->ept);
cancel_work_sync(&ec_rpmsg->host_event_work);
}
+#ifdef CONFIG_PM_SLEEP
+static int cros_ec_rpmsg_suspend(struct device *dev)
+{
+ struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
+
+ return cros_ec_suspend(ec_dev);
+}
+
+static int cros_ec_rpmsg_resume(struct device *dev)
+{
+ struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
+
+ return cros_ec_resume(ec_dev);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(cros_ec_rpmsg_pm_ops, cros_ec_rpmsg_suspend,
+ cros_ec_rpmsg_resume);
+
static const struct of_device_id cros_ec_rpmsg_of_match[] = {
{ .compatible = "google,cros-ec-rpmsg", },
{ }
@@ -246,10 +282,10 @@ static struct rpmsg_driver cros_ec_driver_rpmsg = {
.drv = {
.name = "cros-ec-rpmsg",
.of_match_table = cros_ec_rpmsg_of_match,
+ .pm = &cros_ec_rpmsg_pm_ops,
},
.probe = cros_ec_rpmsg_probe,
.remove = cros_ec_rpmsg_remove,
- .callback = cros_ec_rpmsg_callback,
};
module_rpmsg_driver(cros_ec_driver_rpmsg);
diff --git a/drivers/platform/chrome/cros_ec_sensorhub.c b/drivers/platform/chrome/cros_ec_sensorhub.c
new file mode 100644
index 000000000000..79fefd3bb0fa
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_sensorhub.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sensor HUB driver that discovers sensors behind a ChromeOS Embedded
+ * Controller.
+ *
+ * Copyright 2019 Google LLC
+ */
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/platform_data/cros_ec_sensorhub.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define DRV_NAME "cros-ec-sensorhub"
+
+static void cros_ec_sensorhub_free_sensor(void *arg)
+{
+ struct platform_device *pdev = arg;
+
+ platform_device_unregister(pdev);
+}
+
+static int cros_ec_sensorhub_allocate_sensor(struct device *parent,
+ char *sensor_name,
+ int sensor_num)
+{
+ struct cros_ec_sensor_platform sensor_platforms = {
+ .sensor_num = sensor_num,
+ };
+ struct platform_device *pdev;
+
+ pdev = platform_device_register_data(parent, sensor_name,
+ PLATFORM_DEVID_AUTO,
+ &sensor_platforms,
+ sizeof(sensor_platforms));
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ return devm_add_action_or_reset(parent,
+ cros_ec_sensorhub_free_sensor,
+ pdev);
+}
+
+static int cros_ec_sensorhub_register(struct device *dev,
+ struct cros_ec_sensorhub *sensorhub)
+{
+ int sensor_type[MOTIONSENSE_TYPE_MAX] = { 0 };
+ struct cros_ec_dev *ec = sensorhub->ec;
+ struct ec_params_motion_sense *params;
+ struct ec_response_motion_sense *resp;
+ struct cros_ec_command *msg;
+ int ret, i, sensor_num;
+ char *name;
+
+ sensor_num = cros_ec_get_sensor_count(ec);
+ if (sensor_num < 0) {
+ dev_err(dev,
+ "Unable to retrieve sensor information (err:%d)\n",
+ sensor_num);
+ return sensor_num;
+ }
+
+ if (sensor_num == 0) {
+ dev_err(dev, "Zero sensors reported.\n");
+ return -EINVAL;
+ }
+
+ /* Prepare a message to send INFO command to each sensor. */
+ msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)),
+ GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->version = 1;
+ msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
+ msg->outsize = sizeof(*params);
+ msg->insize = sizeof(*resp);
+ params = (struct ec_params_motion_sense *)msg->data;
+ resp = (struct ec_response_motion_sense *)msg->data;
+
+ for (i = 0; i < sensor_num; i++) {
+ params->cmd = MOTIONSENSE_CMD_INFO;
+ params->info.sensor_num = i;
+
+ ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
+ if (ret < 0) {
+ dev_warn(dev, "no info for EC sensor %d : %d/%d\n",
+ i, ret, msg->result);
+ continue;
+ }
+
+ switch (resp->info.type) {
+ case MOTIONSENSE_TYPE_ACCEL:
+ name = "cros-ec-accel";
+ break;
+ case MOTIONSENSE_TYPE_BARO:
+ name = "cros-ec-baro";
+ break;
+ case MOTIONSENSE_TYPE_GYRO:
+ name = "cros-ec-gyro";
+ break;
+ case MOTIONSENSE_TYPE_MAG:
+ name = "cros-ec-mag";
+ break;
+ case MOTIONSENSE_TYPE_PROX:
+ name = "cros-ec-prox";
+ break;
+ case MOTIONSENSE_TYPE_LIGHT:
+ name = "cros-ec-light";
+ break;
+ case MOTIONSENSE_TYPE_ACTIVITY:
+ name = "cros-ec-activity";
+ break;
+ default:
+ dev_warn(dev, "unknown type %d\n", resp->info.type);
+ continue;
+ }
+
+ ret = cros_ec_sensorhub_allocate_sensor(dev, name, i);
+ if (ret)
+ goto error;
+
+ sensor_type[resp->info.type]++;
+ }
+
+ if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2)
+ ec->has_kb_wake_angle = true;
+
+ if (cros_ec_check_features(ec,
+ EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) {
+ ret = cros_ec_sensorhub_allocate_sensor(dev,
+ "cros-ec-lid-angle",
+ 0);
+ if (ret)
+ goto error;
+ }
+
+ kfree(msg);
+ return 0;
+
+error:
+ kfree(msg);
+ return ret;
+}
+
+static int cros_ec_sensorhub_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_sensorhub *data;
+ int ret;
+ int i;
+
+ data = devm_kzalloc(dev, sizeof(struct cros_ec_sensorhub), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->ec = dev_get_drvdata(dev->parent);
+ dev_set_drvdata(dev, data);
+
+ /* Check whether this EC is a sensor hub. */
+ if (cros_ec_check_features(data->ec, EC_FEATURE_MOTION_SENSE)) {
+ ret = cros_ec_sensorhub_register(dev, data);
+ if (ret)
+ return ret;
+ } else {
+ /*
+ * If the device has sensors but does not claim to
+ * be a sensor hub, we are in legacy mode.
+ */
+ for (i = 0; i < 2; i++) {
+ ret = cros_ec_sensorhub_allocate_sensor(dev,
+ "cros-ec-accel-legacy", i);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver cros_ec_sensorhub_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = cros_ec_sensorhub_probe,
+};
+
+module_platform_driver(cros_ec_sensorhub_driver);
+
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
+MODULE_DESCRIPTION("ChromeOS EC MEMS Sensor Hub Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c
index 006a8ff64057..46786d2d679a 100644
--- a/drivers/platform/chrome/cros_ec_spi.c
+++ b/drivers/platform/chrome/cros_ec_spi.c
@@ -6,14 +6,16 @@
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/of.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <uapi/linux/sched/types.h>
+#include "cros_ec.h"
+
/* The header byte, which follows the preamble */
#define EC_MSG_HEADER 0xec
@@ -706,7 +708,7 @@ static int cros_ec_spi_devm_high_pri_alloc(struct device *dev,
struct cros_ec_spi *ec_spi)
{
struct sched_param sched_priority = {
- .sched_priority = MAX_RT_PRIO - 1,
+ .sched_priority = MAX_RT_PRIO / 2,
};
int err;
@@ -785,6 +787,13 @@ static int cros_ec_spi_probe(struct spi_device *spi)
return 0;
}
+static int cros_ec_spi_remove(struct spi_device *spi)
+{
+ struct cros_ec_device *ec_dev = spi_get_drvdata(spi);
+
+ return cros_ec_unregister(ec_dev);
+}
+
#ifdef CONFIG_PM_SLEEP
static int cros_ec_spi_suspend(struct device *dev)
{
@@ -823,6 +832,7 @@ static struct spi_driver cros_ec_driver_spi = {
.pm = &cros_ec_spi_pm_ops,
},
.probe = cros_ec_spi_probe,
+ .remove = cros_ec_spi_remove,
.id_table = cros_ec_spi_id,
};
diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c
index 3edb237bf8ed..07dac97ad57c 100644
--- a/drivers/platform/chrome/cros_ec_sysfs.c
+++ b/drivers/platform/chrome/cros_ec_sysfs.c
@@ -8,9 +8,9 @@
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kobject.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>
diff --git a/drivers/platform/chrome/cros_ec_trace.c b/drivers/platform/chrome/cros_ec_trace.c
index 0a76412095a9..523a39bd0ff6 100644
--- a/drivers/platform/chrome/cros_ec_trace.c
+++ b/drivers/platform/chrome/cros_ec_trace.c
@@ -6,8 +6,13 @@
#define TRACE_SYMBOL(a) {a, #a}
// Generate the list using the following script:
-// sed -n 's/^#define \(EC_CMD_[[:alnum:]_]*\)\s.*/\tTRACE_SYMBOL(\1), \\/p' include/linux/mfd/cros_ec_commands.h
+// sed -n 's/^#define \(EC_CMD_[[:alnum:]_]*\)\s.*/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h
#define EC_CMDS \
+ TRACE_SYMBOL(EC_CMD_ACPI_READ), \
+ TRACE_SYMBOL(EC_CMD_ACPI_WRITE), \
+ TRACE_SYMBOL(EC_CMD_ACPI_BURST_ENABLE), \
+ TRACE_SYMBOL(EC_CMD_ACPI_BURST_DISABLE), \
+ TRACE_SYMBOL(EC_CMD_ACPI_QUERY_EVENT), \
TRACE_SYMBOL(EC_CMD_PROTO_VERSION), \
TRACE_SYMBOL(EC_CMD_HELLO), \
TRACE_SYMBOL(EC_CMD_GET_VERSION), \
@@ -22,6 +27,8 @@
TRACE_SYMBOL(EC_CMD_GET_PROTOCOL_INFO), \
TRACE_SYMBOL(EC_CMD_GSV_PAUSE_IN_S5), \
TRACE_SYMBOL(EC_CMD_GET_FEATURES), \
+ TRACE_SYMBOL(EC_CMD_GET_SKU_ID), \
+ TRACE_SYMBOL(EC_CMD_SET_SKU_ID), \
TRACE_SYMBOL(EC_CMD_FLASH_INFO), \
TRACE_SYMBOL(EC_CMD_FLASH_READ), \
TRACE_SYMBOL(EC_CMD_FLASH_WRITE), \
@@ -29,6 +36,8 @@
TRACE_SYMBOL(EC_CMD_FLASH_PROTECT), \
TRACE_SYMBOL(EC_CMD_FLASH_REGION_INFO), \
TRACE_SYMBOL(EC_CMD_VBNV_CONTEXT), \
+ TRACE_SYMBOL(EC_CMD_FLASH_SPI_INFO), \
+ TRACE_SYMBOL(EC_CMD_FLASH_SELECT), \
TRACE_SYMBOL(EC_CMD_PWM_GET_FAN_TARGET_RPM), \
TRACE_SYMBOL(EC_CMD_PWM_SET_FAN_TARGET_RPM), \
TRACE_SYMBOL(EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT), \
@@ -40,6 +49,8 @@
TRACE_SYMBOL(EC_CMD_LED_CONTROL), \
TRACE_SYMBOL(EC_CMD_VBOOT_HASH), \
TRACE_SYMBOL(EC_CMD_MOTION_SENSE_CMD), \
+ TRACE_SYMBOL(EC_CMD_FORCE_LID_OPEN), \
+ TRACE_SYMBOL(EC_CMD_CONFIG_POWER_BUTTON), \
TRACE_SYMBOL(EC_CMD_USB_CHARGE_SET_MODE), \
TRACE_SYMBOL(EC_CMD_PSTORE_INFO), \
TRACE_SYMBOL(EC_CMD_PSTORE_READ), \
@@ -50,6 +61,9 @@
TRACE_SYMBOL(EC_CMD_RTC_SET_ALARM), \
TRACE_SYMBOL(EC_CMD_PORT80_LAST_BOOT), \
TRACE_SYMBOL(EC_CMD_PORT80_READ), \
+ TRACE_SYMBOL(EC_CMD_VSTORE_INFO), \
+ TRACE_SYMBOL(EC_CMD_VSTORE_READ), \
+ TRACE_SYMBOL(EC_CMD_VSTORE_WRITE), \
TRACE_SYMBOL(EC_CMD_THERMAL_SET_THRESHOLD), \
TRACE_SYMBOL(EC_CMD_THERMAL_GET_THRESHOLD), \
TRACE_SYMBOL(EC_CMD_THERMAL_AUTO_FAN_CTRL), \
@@ -59,10 +73,12 @@
TRACE_SYMBOL(EC_CMD_MKBP_STATE), \
TRACE_SYMBOL(EC_CMD_MKBP_INFO), \
TRACE_SYMBOL(EC_CMD_MKBP_SIMULATE_KEY), \
+ TRACE_SYMBOL(EC_CMD_GET_KEYBOARD_ID), \
TRACE_SYMBOL(EC_CMD_MKBP_SET_CONFIG), \
TRACE_SYMBOL(EC_CMD_MKBP_GET_CONFIG), \
TRACE_SYMBOL(EC_CMD_KEYSCAN_SEQ_CTRL), \
TRACE_SYMBOL(EC_CMD_GET_NEXT_EVENT), \
+ TRACE_SYMBOL(EC_CMD_KEYBOARD_FACTORY_TEST), \
TRACE_SYMBOL(EC_CMD_TEMP_SENSOR_GET_INFO), \
TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_B), \
TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_SMI_MASK), \
@@ -73,6 +89,7 @@
TRACE_SYMBOL(EC_CMD_HOST_EVENT_CLEAR), \
TRACE_SYMBOL(EC_CMD_HOST_EVENT_SET_WAKE_MASK), \
TRACE_SYMBOL(EC_CMD_HOST_EVENT_CLEAR_B), \
+ TRACE_SYMBOL(EC_CMD_HOST_EVENT), \
TRACE_SYMBOL(EC_CMD_SWITCH_ENABLE_BKLIGHT), \
TRACE_SYMBOL(EC_CMD_SWITCH_ENABLE_WIRELESS), \
TRACE_SYMBOL(EC_CMD_GPIO_SET), \
@@ -92,33 +109,102 @@
TRACE_SYMBOL(EC_CMD_CHARGE_STATE), \
TRACE_SYMBOL(EC_CMD_CHARGE_CURRENT_LIMIT), \
TRACE_SYMBOL(EC_CMD_EXTERNAL_POWER_LIMIT), \
+ TRACE_SYMBOL(EC_CMD_OVERRIDE_DEDICATED_CHARGER_LIMIT), \
+ TRACE_SYMBOL(EC_CMD_HIBERNATION_DELAY), \
TRACE_SYMBOL(EC_CMD_HOST_SLEEP_EVENT), \
+ TRACE_SYMBOL(EC_CMD_DEVICE_EVENT), \
TRACE_SYMBOL(EC_CMD_SB_READ_WORD), \
TRACE_SYMBOL(EC_CMD_SB_WRITE_WORD), \
TRACE_SYMBOL(EC_CMD_SB_READ_BLOCK), \
TRACE_SYMBOL(EC_CMD_SB_WRITE_BLOCK), \
TRACE_SYMBOL(EC_CMD_BATTERY_VENDOR_PARAM), \
- TRACE_SYMBOL(EC_CMD_CODEC_I2S), \
- TRACE_SYMBOL(EC_CMD_REBOOT_EC), \
- TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \
- TRACE_SYMBOL(EC_CMD_ACPI_READ), \
- TRACE_SYMBOL(EC_CMD_ACPI_WRITE), \
- TRACE_SYMBOL(EC_CMD_ACPI_QUERY_EVENT), \
+ TRACE_SYMBOL(EC_CMD_SB_FW_UPDATE), \
+ TRACE_SYMBOL(EC_CMD_ENTERING_MODE), \
+ TRACE_SYMBOL(EC_CMD_I2C_PASSTHRU_PROTECT), \
TRACE_SYMBOL(EC_CMD_CEC_WRITE_MSG), \
TRACE_SYMBOL(EC_CMD_CEC_SET), \
TRACE_SYMBOL(EC_CMD_CEC_GET), \
+ TRACE_SYMBOL(EC_CMD_EC_CODEC), \
+ TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \
+ TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \
+ TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \
+ TRACE_SYMBOL(EC_CMD_REBOOT_EC), \
+ TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \
TRACE_SYMBOL(EC_CMD_REBOOT), \
TRACE_SYMBOL(EC_CMD_RESEND_RESPONSE), \
TRACE_SYMBOL(EC_CMD_VERSION0), \
TRACE_SYMBOL(EC_CMD_PD_EXCHANGE_STATUS), \
+ TRACE_SYMBOL(EC_CMD_PD_HOST_EVENT_STATUS), \
TRACE_SYMBOL(EC_CMD_USB_PD_CONTROL), \
TRACE_SYMBOL(EC_CMD_USB_PD_PORTS), \
TRACE_SYMBOL(EC_CMD_USB_PD_POWER_INFO), \
TRACE_SYMBOL(EC_CMD_CHARGE_PORT_COUNT), \
+ TRACE_SYMBOL(EC_CMD_USB_PD_FW_UPDATE), \
+ TRACE_SYMBOL(EC_CMD_USB_PD_RW_HASH_ENTRY), \
+ TRACE_SYMBOL(EC_CMD_USB_PD_DEV_INFO), \
TRACE_SYMBOL(EC_CMD_USB_PD_DISCOVERY), \
TRACE_SYMBOL(EC_CMD_PD_CHARGE_PORT_OVERRIDE), \
TRACE_SYMBOL(EC_CMD_PD_GET_LOG_ENTRY), \
- TRACE_SYMBOL(EC_CMD_USB_PD_MUX_INFO)
+ TRACE_SYMBOL(EC_CMD_USB_PD_GET_AMODE), \
+ TRACE_SYMBOL(EC_CMD_USB_PD_SET_AMODE), \
+ TRACE_SYMBOL(EC_CMD_PD_WRITE_LOG_ENTRY), \
+ TRACE_SYMBOL(EC_CMD_PD_CONTROL), \
+ TRACE_SYMBOL(EC_CMD_USB_PD_MUX_INFO), \
+ TRACE_SYMBOL(EC_CMD_PD_CHIP_INFO), \
+ TRACE_SYMBOL(EC_CMD_RWSIG_CHECK_STATUS), \
+ TRACE_SYMBOL(EC_CMD_RWSIG_ACTION), \
+ TRACE_SYMBOL(EC_CMD_EFS_VERIFY), \
+ TRACE_SYMBOL(EC_CMD_GET_CROS_BOARD_INFO), \
+ TRACE_SYMBOL(EC_CMD_SET_CROS_BOARD_INFO), \
+ TRACE_SYMBOL(EC_CMD_GET_UPTIME_INFO), \
+ TRACE_SYMBOL(EC_CMD_ADD_ENTROPY), \
+ TRACE_SYMBOL(EC_CMD_ADC_READ), \
+ TRACE_SYMBOL(EC_CMD_ROLLBACK_INFO), \
+ TRACE_SYMBOL(EC_CMD_AP_RESET), \
+ TRACE_SYMBOL(EC_CMD_CR51_BASE), \
+ TRACE_SYMBOL(EC_CMD_CR51_LAST), \
+ TRACE_SYMBOL(EC_CMD_FP_PASSTHRU), \
+ TRACE_SYMBOL(EC_CMD_FP_MODE), \
+ TRACE_SYMBOL(EC_CMD_FP_INFO), \
+ TRACE_SYMBOL(EC_CMD_FP_FRAME), \
+ TRACE_SYMBOL(EC_CMD_FP_TEMPLATE), \
+ TRACE_SYMBOL(EC_CMD_FP_CONTEXT), \
+ TRACE_SYMBOL(EC_CMD_FP_STATS), \
+ TRACE_SYMBOL(EC_CMD_FP_SEED), \
+ TRACE_SYMBOL(EC_CMD_FP_ENC_STATUS), \
+ TRACE_SYMBOL(EC_CMD_TP_SELF_TEST), \
+ TRACE_SYMBOL(EC_CMD_TP_FRAME_INFO), \
+ TRACE_SYMBOL(EC_CMD_TP_FRAME_SNAPSHOT), \
+ TRACE_SYMBOL(EC_CMD_TP_FRAME_GET), \
+ TRACE_SYMBOL(EC_CMD_BATTERY_GET_STATIC), \
+ TRACE_SYMBOL(EC_CMD_BATTERY_GET_DYNAMIC), \
+ TRACE_SYMBOL(EC_CMD_CHARGER_CONTROL), \
+ TRACE_SYMBOL(EC_CMD_BOARD_SPECIFIC_BASE), \
+ TRACE_SYMBOL(EC_CMD_BOARD_SPECIFIC_LAST)
+
+/* See the enum ec_status in include/linux/platform_data/cros_ec_commands.h */
+#define EC_RESULT \
+ TRACE_SYMBOL(EC_RES_SUCCESS), \
+ TRACE_SYMBOL(EC_RES_INVALID_COMMAND), \
+ TRACE_SYMBOL(EC_RES_ERROR), \
+ TRACE_SYMBOL(EC_RES_INVALID_PARAM), \
+ TRACE_SYMBOL(EC_RES_ACCESS_DENIED), \
+ TRACE_SYMBOL(EC_RES_INVALID_RESPONSE), \
+ TRACE_SYMBOL(EC_RES_INVALID_VERSION), \
+ TRACE_SYMBOL(EC_RES_INVALID_CHECKSUM), \
+ TRACE_SYMBOL(EC_RES_IN_PROGRESS), \
+ TRACE_SYMBOL(EC_RES_UNAVAILABLE), \
+ TRACE_SYMBOL(EC_RES_TIMEOUT), \
+ TRACE_SYMBOL(EC_RES_OVERFLOW), \
+ TRACE_SYMBOL(EC_RES_INVALID_HEADER), \
+ TRACE_SYMBOL(EC_RES_REQUEST_TRUNCATED), \
+ TRACE_SYMBOL(EC_RES_RESPONSE_TOO_BIG), \
+ TRACE_SYMBOL(EC_RES_BUS_ERROR), \
+ TRACE_SYMBOL(EC_RES_BUSY), \
+ TRACE_SYMBOL(EC_RES_INVALID_HEADER_VERSION), \
+ TRACE_SYMBOL(EC_RES_INVALID_HEADER_CRC), \
+ TRACE_SYMBOL(EC_RES_INVALID_DATA_CRC), \
+ TRACE_SYMBOL(EC_RES_DUP_UNAVAILABLE)
#define CREATE_TRACE_POINTS
#include "cros_ec_trace.h"
diff --git a/drivers/platform/chrome/cros_ec_trace.h b/drivers/platform/chrome/cros_ec_trace.h
index 7ae3b89c78b9..e9fb05f89ef0 100644
--- a/drivers/platform/chrome/cros_ec_trace.h
+++ b/drivers/platform/chrome/cros_ec_trace.h
@@ -11,12 +11,14 @@
#if !defined(_CROS_EC_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _CROS_EC_TRACE_H_
+#include <linux/bits.h>
#include <linux/types.h>
-#include <linux/mfd/cros_ec.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/tracepoint.h>
-DECLARE_EVENT_CLASS(cros_ec_cmd_class,
+TRACE_EVENT(cros_ec_request_start,
TP_PROTO(struct cros_ec_command *cmd),
TP_ARGS(cmd),
TP_STRUCT__entry(
@@ -31,10 +33,26 @@ DECLARE_EVENT_CLASS(cros_ec_cmd_class,
__print_symbolic(__entry->command, EC_CMDS))
);
-
-DEFINE_EVENT(cros_ec_cmd_class, cros_ec_cmd,
- TP_PROTO(struct cros_ec_command *cmd),
- TP_ARGS(cmd)
+TRACE_EVENT(cros_ec_request_done,
+ TP_PROTO(struct cros_ec_command *cmd, int retval),
+ TP_ARGS(cmd, retval),
+ TP_STRUCT__entry(
+ __field(uint32_t, version)
+ __field(uint32_t, command)
+ __field(uint32_t, result)
+ __field(int, retval)
+ ),
+ TP_fast_assign(
+ __entry->version = cmd->version;
+ __entry->command = cmd->command;
+ __entry->result = cmd->result;
+ __entry->retval = retval;
+ ),
+ TP_printk("version: %u, command: %s, ec result: %s, retval: %d",
+ __entry->version,
+ __print_symbolic(__entry->command, EC_CMDS),
+ __print_symbolic(__entry->result, EC_RESULT),
+ __entry->retval)
);
diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c
index 2aaefed87eb4..8edae465105c 100644
--- a/drivers/platform/chrome/cros_ec_vbc.c
+++ b/drivers/platform/chrome/cros_ec_vbc.c
@@ -6,9 +6,9 @@
#include <linux/of.h>
#include <linux/platform_device.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/slab.h>
#define DRV_NAME "cros-ec-vbc"
diff --git a/drivers/platform/chrome/cros_usbpd_logger.c b/drivers/platform/chrome/cros_usbpd_logger.c
index 7c7b267626a0..7de3ea75ef46 100644
--- a/drivers/platform/chrome/cros_usbpd_logger.c
+++ b/drivers/platform/chrome/cros_usbpd_logger.c
@@ -7,9 +7,9 @@
#include <linux/ktime.h>
#include <linux/math64.h>
-#include <linux/mfd/cros_ec.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
@@ -209,6 +209,9 @@ static int cros_usbpd_logger_probe(struct platform_device *pd)
/* Retrieve PD event logs periodically */
INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check);
logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log");
+ if (!logger->log_workqueue)
+ return -ENOMEM;
+
queue_delayed_work(logger->log_workqueue, &logger->log_work,
CROS_USBPD_LOG_UPDATE_DELAY);
@@ -220,6 +223,7 @@ static int cros_usbpd_logger_remove(struct platform_device *pd)
struct logger_data *logger = platform_get_drvdata(pd);
cancel_delayed_work_sync(&logger->log_work);
+ destroy_workqueue(logger->log_workqueue);
return 0;
}
diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig
index 89007b0bc743..49e8530ca0ac 100644
--- a/drivers/platform/chrome/wilco_ec/Kconfig
+++ b/drivers/platform/chrome/wilco_ec/Kconfig
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
config WILCO_EC
tristate "ChromeOS Wilco Embedded Controller"
- depends on ACPI && X86 && CROS_EC_LPC
+ depends on X86 || COMPILE_TEST
+ depends on ACPI && CROS_EC_LPC && LEDS_CLASS
help
If you say Y here, you get support for talking to the ChromeOS
Wilco EC over an eSPI bus. This uses a simple byte-level protocol
diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
index bc817164596e..ecb3145cab18 100644
--- a/drivers/platform/chrome/wilco_ec/Makefile
+++ b/drivers/platform/chrome/wilco_ec/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
-wilco_ec-objs := core.o mailbox.o properties.o sysfs.o
+wilco_ec-objs := core.o keyboard_leds.o mailbox.o \
+ properties.o sysfs.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
wilco_ec_debugfs-objs := debugfs.o
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
index 3724bf4b77c6..5b42992bff38 100644
--- a/drivers/platform/chrome/wilco_ec/core.c
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -5,10 +5,6 @@
* Copyright 2018 Google LLC
*
* This is the entry point for the drivers that control the Wilco EC.
- * This driver is responsible for several tasks:
- * - Initialize the register interface that is used by wilco_ec_mailbox()
- * - Create a platform device which is picked up by the debugfs driver
- * - Create a platform device which is picked up by the RTC driver
*/
#include <linux/acpi.h>
@@ -87,12 +83,31 @@ static int wilco_ec_probe(struct platform_device *pdev)
goto unregister_debugfs;
}
+ /* Set up the keyboard backlight LEDs. */
+ ret = wilco_keyboard_leds_init(ec);
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to initialize keyboard LEDs: %d\n",
+ ret);
+ goto unregister_rtc;
+ }
+
ret = wilco_ec_add_sysfs(ec);
if (ret < 0) {
- dev_err(dev, "Failed to create sysfs entries: %d", ret);
+ dev_err(dev, "Failed to create sysfs entries: %d\n", ret);
goto unregister_rtc;
}
+ /* Register child device to be found by charger config driver. */
+ ec->charger_pdev = platform_device_register_data(dev, "wilco-charger",
+ PLATFORM_DEVID_AUTO,
+ NULL, 0);
+ if (IS_ERR(ec->charger_pdev)) {
+ dev_err(dev, "Failed to create charger platform device\n");
+ ret = PTR_ERR(ec->charger_pdev);
+ goto remove_sysfs;
+ }
+
/* Register child device that will be found by the telemetry driver. */
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
PLATFORM_DEVID_AUTO,
@@ -100,11 +115,13 @@ static int wilco_ec_probe(struct platform_device *pdev)
if (IS_ERR(ec->telem_pdev)) {
dev_err(dev, "Failed to create telemetry platform device\n");
ret = PTR_ERR(ec->telem_pdev);
- goto remove_sysfs;
+ goto unregister_charge_config;
}
return 0;
+unregister_charge_config:
+ platform_device_unregister(ec->charger_pdev);
remove_sysfs:
wilco_ec_remove_sysfs(ec);
unregister_rtc:
@@ -120,8 +137,9 @@ static int wilco_ec_remove(struct platform_device *pdev)
{
struct wilco_ec_device *ec = platform_get_drvdata(pdev);
- wilco_ec_remove_sysfs(ec);
platform_device_unregister(ec->telem_pdev);
+ platform_device_unregister(ec->charger_pdev);
+ wilco_ec_remove_sysfs(ec);
platform_device_unregister(ec->rtc_pdev);
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c
index 8d65a1e2f1a3..df5a5f6c3ec6 100644
--- a/drivers/platform/chrome/wilco_ec/debugfs.c
+++ b/drivers/platform/chrome/wilco_ec/debugfs.c
@@ -160,29 +160,29 @@ static const struct file_operations fops_raw = {
#define CMD_KB_CHROME 0x88
#define SUB_CMD_H1_GPIO 0x0A
+#define SUB_CMD_TEST_EVENT 0x0B
-struct h1_gpio_status_request {
+struct ec_request {
u8 cmd; /* Always CMD_KB_CHROME */
u8 reserved;
- u8 sub_cmd; /* Always SUB_CMD_H1_GPIO */
+ u8 sub_cmd;
} __packed;
-struct hi_gpio_status_response {
+struct ec_response {
u8 status; /* 0 if allowed */
- u8 val; /* BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL */
+ u8 val;
} __packed;
-static int h1_gpio_get(void *arg, u64 *val)
+static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val)
{
- struct wilco_ec_device *ec = arg;
- struct h1_gpio_status_request rq;
- struct hi_gpio_status_response rs;
+ struct ec_request rq;
+ struct ec_response rs;
struct wilco_ec_message msg;
int ret;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_KB_CHROME;
- rq.sub_cmd = SUB_CMD_H1_GPIO;
+ rq.sub_cmd = sub_cmd;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
@@ -196,14 +196,39 @@ static int h1_gpio_get(void *arg, u64 *val)
if (rs.status)
return -EIO;
- *val = rs.val;
+ *out_val = rs.val;
return 0;
}
+/**
+ * h1_gpio_get() - Gets h1 gpio status.
+ * @arg: The wilco EC device.
+ * @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL
+ */
+static int h1_gpio_get(void *arg, u64 *val)
+{
+ return send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val);
+}
+
DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n");
/**
+ * test_event_set() - Sends command to EC to cause an EC test event.
+ * @arg: The wilco EC device.
+ * @val: unused.
+ */
+static int test_event_set(void *arg, u64 val)
+{
+ u8 ret;
+
+ return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret);
+}
+
+/* Format is unused since it is only required for get method which is NULL */
+DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n");
+
+/**
* wilco_ec_debugfs_probe() - Create the debugfs node
* @pdev: The platform device, probably created in core.c
*
@@ -226,6 +251,8 @@ static int wilco_ec_debugfs_probe(struct platform_device *pdev)
debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);
debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec,
&fops_h1_gpio);
+ debugfs_create_file("test_event", 0200, debug_info->dir, ec,
+ &fops_test_event);
return 0;
}
diff --git a/drivers/platform/chrome/wilco_ec/keyboard_leds.c b/drivers/platform/chrome/wilco_ec/keyboard_leds.c
new file mode 100644
index 000000000000..6ce9c6782065
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/keyboard_leds.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Keyboard backlight LED driver for the Wilco Embedded Controller
+ *
+ * Copyright 2019 Google LLC
+ *
+ * Since the EC will never change the backlight level of its own accord,
+ * we don't need to implement a brightness_get() method.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/slab.h>
+
+#define WILCO_EC_COMMAND_KBBL 0x75
+#define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */
+#define WILCO_KBBL_DEFAULT_BRIGHTNESS 0
+
+struct wilco_keyboard_leds {
+ struct wilco_ec_device *ec;
+ struct led_classdev keyboard;
+};
+
+enum wilco_kbbl_subcommand {
+ WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00,
+ WILCO_KBBL_SUBCMD_GET_STATE = 0x01,
+ WILCO_KBBL_SUBCMD_SET_STATE = 0x02,
+};
+
+/**
+ * struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control.
+ * @command: Always WILCO_EC_COMMAND_KBBL.
+ * @status: Set by EC to 0 on success, 0xFF on failure.
+ * @subcmd: One of enum wilco_kbbl_subcommand.
+ * @reserved3: Should be 0.
+ * @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM.
+ * @reserved5to8: Should be 0.
+ * @percent: Brightness in 0-100. Only meaningful in PWM mode.
+ * @reserved10to15: Should be 0.
+ */
+struct wilco_keyboard_leds_msg {
+ u8 command;
+ u8 status;
+ u8 subcmd;
+ u8 reserved3;
+ u8 mode;
+ u8 reserved5to8[4];
+ u8 percent;
+ u8 reserved10to15[6];
+} __packed;
+
+/* Send a request, get a response, and check that the response is good. */
+static int send_kbbl_msg(struct wilco_ec_device *ec,
+ struct wilco_keyboard_leds_msg *request,
+ struct wilco_keyboard_leds_msg *response)
+{
+ struct wilco_ec_message msg;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.type = WILCO_EC_MSG_LEGACY;
+ msg.request_data = request;
+ msg.request_size = sizeof(*request);
+ msg.response_data = response;
+ msg.response_size = sizeof(*response);
+
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0) {
+ dev_err(ec->dev,
+ "Failed sending keyboard LEDs command: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness)
+{
+ struct wilco_keyboard_leds_msg request;
+ struct wilco_keyboard_leds_msg response;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ request.command = WILCO_EC_COMMAND_KBBL;
+ request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE;
+ request.mode = WILCO_KBBL_MODE_FLAG_PWM;
+ request.percent = brightness;
+
+ ret = send_kbbl_msg(ec, &request, &response);
+ if (ret < 0)
+ return ret;
+
+ if (response.status) {
+ dev_err(ec->dev,
+ "EC reported failure sending keyboard LEDs command: %d\n",
+ response.status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int kbbl_exist(struct wilco_ec_device *ec, bool *exists)
+{
+ struct wilco_keyboard_leds_msg request;
+ struct wilco_keyboard_leds_msg response;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ request.command = WILCO_EC_COMMAND_KBBL;
+ request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES;
+
+ ret = send_kbbl_msg(ec, &request, &response);
+ if (ret < 0)
+ return ret;
+
+ *exists = response.status != 0xFF;
+
+ return 0;
+}
+
+/**
+ * kbbl_init() - Initialize the state of the keyboard backlight.
+ * @ec: EC device to talk to.
+ *
+ * Gets the current brightness, ensuring that the BIOS already initialized the
+ * backlight to PWM mode. If not in PWM mode, then the current brightness is
+ * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS.
+ *
+ * Return: Final brightness of the keyboard, or negative error code on failure.
+ */
+static int kbbl_init(struct wilco_ec_device *ec)
+{
+ struct wilco_keyboard_leds_msg request;
+ struct wilco_keyboard_leds_msg response;
+ int ret;
+
+ memset(&request, 0, sizeof(request));
+ request.command = WILCO_EC_COMMAND_KBBL;
+ request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE;
+
+ ret = send_kbbl_msg(ec, &request, &response);
+ if (ret < 0)
+ return ret;
+
+ if (response.status) {
+ dev_err(ec->dev,
+ "EC reported failure sending keyboard LEDs command: %d\n",
+ response.status);
+ return -EIO;
+ }
+
+ if (response.mode & WILCO_KBBL_MODE_FLAG_PWM)
+ return response.percent;
+
+ ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS);
+ if (ret < 0)
+ return ret;
+
+ return WILCO_KBBL_DEFAULT_BRIGHTNESS;
+}
+
+static int wilco_keyboard_leds_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct wilco_keyboard_leds *wkl =
+ container_of(cdev, struct wilco_keyboard_leds, keyboard);
+ return set_kbbl(wkl->ec, brightness);
+}
+
+int wilco_keyboard_leds_init(struct wilco_ec_device *ec)
+{
+ struct wilco_keyboard_leds *wkl;
+ bool leds_exist;
+ int ret;
+
+ ret = kbbl_exist(ec, &leds_exist);
+ if (ret < 0) {
+ dev_err(ec->dev,
+ "Failed checking keyboard LEDs support: %d\n", ret);
+ return ret;
+ }
+ if (!leds_exist)
+ return 0;
+
+ wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL);
+ if (!wkl)
+ return -ENOMEM;
+
+ wkl->ec = ec;
+ wkl->keyboard.name = "platform::kbd_backlight";
+ wkl->keyboard.max_brightness = 100;
+ wkl->keyboard.flags = LED_CORE_SUSPENDRESUME;
+ wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set;
+ ret = kbbl_init(ec);
+ if (ret < 0)
+ return ret;
+ wkl->keyboard.brightness = ret;
+
+ return devm_led_classdev_register(ec->dev, &wkl->keyboard);
+}
diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c
index ced1f9f3dcee..0f98358ea824 100644
--- a/drivers/platform/chrome/wilco_ec/mailbox.c
+++ b/drivers/platform/chrome/wilco_ec/mailbox.c
@@ -163,13 +163,13 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
}
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
- dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
+ dev_dbg(ec->dev, "unexpected packet size (%u != %u)\n",
rs->data_size, EC_MAILBOX_DATA_SIZE);
return -EMSGSIZE;
}
if (rs->data_size < msg->response_size) {
- dev_dbg(ec->dev, "EC didn't return enough data (%u < %zu)",
+ dev_dbg(ec->dev, "EC didn't return enough data (%u < %zu)\n",
rs->data_size, msg->response_size);
return -EMSGSIZE;
}
diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c
index 3b86a21005d3..f0d174b6bb21 100644
--- a/drivers/platform/chrome/wilco_ec/sysfs.c
+++ b/drivers/platform/chrome/wilco_ec/sysfs.c
@@ -23,6 +23,26 @@ struct boot_on_ac_request {
u8 reserved7;
} __packed;
+#define CMD_USB_CHARGE 0x39
+
+enum usb_charge_op {
+ USB_CHARGE_GET = 0,
+ USB_CHARGE_SET = 1,
+};
+
+struct usb_charge_request {
+ u8 cmd; /* Always CMD_USB_CHARGE */
+ u8 reserved;
+ u8 op; /* One of enum usb_charge_op */
+ u8 val; /* When setting, either 0 or 1 */
+} __packed;
+
+struct usb_charge_response {
+ u8 reserved;
+ u8 status; /* Set by EC to 0 on success, other value on failure */
+ u8 val; /* When getting, set by EC to either 0 or 1 */
+} __packed;
+
#define CMD_EC_INFO 0x38
enum get_ec_info_op {
CMD_GET_EC_LABEL = 0,
@@ -131,12 +151,83 @@ static ssize_t model_number_show(struct device *dev,
static DEVICE_ATTR_RO(model_number);
+static int send_usb_charge(struct wilco_ec_device *ec,
+ struct usb_charge_request *rq,
+ struct usb_charge_response *rs)
+{
+ struct wilco_ec_message msg;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.type = WILCO_EC_MSG_LEGACY;
+ msg.request_data = rq;
+ msg.request_size = sizeof(*rq);
+ msg.response_data = rs;
+ msg.response_size = sizeof(*rs);
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0)
+ return ret;
+ if (rs->status)
+ return -EIO;
+
+ return 0;
+}
+
+static ssize_t usb_charge_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev);
+ struct usb_charge_request rq;
+ struct usb_charge_response rs;
+ int ret;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.cmd = CMD_USB_CHARGE;
+ rq.op = USB_CHARGE_GET;
+
+ ret = send_usb_charge(ec, &rq, &rs);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", rs.val);
+}
+
+static ssize_t usb_charge_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev);
+ struct usb_charge_request rq;
+ struct usb_charge_response rs;
+ int ret;
+ u8 val;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+ if (val > 1)
+ return -EINVAL;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.cmd = CMD_USB_CHARGE;
+ rq.op = USB_CHARGE_SET;
+ rq.val = val;
+
+ ret = send_usb_charge(ec, &rq, &rs);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(usb_charge);
static struct attribute *wilco_dev_attrs[] = {
&dev_attr_boot_on_ac.attr,
&dev_attr_build_date.attr,
&dev_attr_build_revision.attr,
&dev_attr_model_number.attr,
+ &dev_attr_usb_charge.attr,
&dev_attr_version.attr,
NULL,
};
diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c
index 94cdc166c840..e06d96fb9426 100644
--- a/drivers/platform/chrome/wilco_ec/telemetry.c
+++ b/drivers/platform/chrome/wilco_ec/telemetry.c
@@ -9,7 +9,7 @@
* the OS sends a command to the EC via a write() to a char device,
* and can read the response with a read(). The write() request is
* verified by the driver to ensure that it is performing only one
- * of the whitelisted commands, and that no extraneous data is
+ * of the allowlisted commands, and that no extraneous data is
* being transmitted to the EC. The response is passed directly
* back to the reader with no modification.
*
@@ -59,21 +59,10 @@ static DEFINE_IDA(telem_ida);
#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95
#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C
#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07
+#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A
#define TELEM_ARGS_SIZE_MAX 30
-/**
- * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
- * @command: One of WILCO_EC_TELEM_GET_* command codes.
- * @reserved: Must be 0.
- * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
- */
-struct wilco_ec_telem_request {
- u8 command;
- u8 reserved;
- u8 args[TELEM_ARGS_SIZE_MAX];
-} __packed;
-
/*
* The following telem_args_get_* structs are embedded within the |args| field
* of wilco_ec_telem_request.
@@ -122,6 +111,32 @@ struct telem_args_get_batt_ext_info {
u8 var_args[5];
} __packed;
+struct telem_args_get_batt_ppid_info {
+ u8 always1; /* Should always be 1 */
+} __packed;
+
+/**
+ * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
+ * @command: One of WILCO_EC_TELEM_GET_* command codes.
+ * @reserved: Must be 0.
+ * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
+ */
+struct wilco_ec_telem_request {
+ u8 command;
+ u8 reserved;
+ union {
+ u8 buf[TELEM_ARGS_SIZE_MAX];
+ struct telem_args_get_log get_log;
+ struct telem_args_get_version get_version;
+ struct telem_args_get_fan_info get_fan_info;
+ struct telem_args_get_diag_info get_diag_info;
+ struct telem_args_get_temp_info get_temp_info;
+ struct telem_args_get_temp_read get_temp_read;
+ struct telem_args_get_batt_ext_info get_batt_ext_info;
+ struct telem_args_get_batt_ppid_info get_batt_ppid_info;
+ } args;
+} __packed;
+
/**
* check_telem_request() - Ensure that a request from userspace is valid.
* @rq: Request buffer copied from userspace.
@@ -133,7 +148,7 @@ struct telem_args_get_batt_ext_info {
* We do not want to allow userspace to send arbitrary telemetry commands to
* the EC. Therefore we check to ensure that
* 1. The request follows the format of struct wilco_ec_telem_request.
- * 2. The supplied command code is one of the whitelisted commands.
+ * 2. The supplied command code is one of the allowlisted commands.
* 3. The request only contains the necessary data for the header and arguments.
*/
static int check_telem_request(struct wilco_ec_telem_request *rq,
@@ -146,25 +161,31 @@ static int check_telem_request(struct wilco_ec_telem_request *rq,
switch (rq->command) {
case WILCO_EC_TELEM_GET_LOG:
- max_size += sizeof(struct telem_args_get_log);
+ max_size += sizeof(rq->args.get_log);
break;
case WILCO_EC_TELEM_GET_VERSION:
- max_size += sizeof(struct telem_args_get_version);
+ max_size += sizeof(rq->args.get_version);
break;
case WILCO_EC_TELEM_GET_FAN_INFO:
- max_size += sizeof(struct telem_args_get_fan_info);
+ max_size += sizeof(rq->args.get_fan_info);
break;
case WILCO_EC_TELEM_GET_DIAG_INFO:
- max_size += sizeof(struct telem_args_get_diag_info);
+ max_size += sizeof(rq->args.get_diag_info);
break;
case WILCO_EC_TELEM_GET_TEMP_INFO:
- max_size += sizeof(struct telem_args_get_temp_info);
+ max_size += sizeof(rq->args.get_temp_info);
break;
case WILCO_EC_TELEM_GET_TEMP_READ:
- max_size += sizeof(struct telem_args_get_temp_read);
+ max_size += sizeof(rq->args.get_temp_read);
break;
case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
- max_size += sizeof(struct telem_args_get_batt_ext_info);
+ max_size += sizeof(rq->args.get_batt_ext_info);
+ break;
+ case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
+ if (rq->args.get_batt_ppid_info.always1 != 1)
+ return -EINVAL;
+
+ max_size += sizeof(rq->args.get_batt_ppid_info);
break;
default:
return -EINVAL;
@@ -250,6 +271,7 @@ static ssize_t telem_write(struct file *filp, const char __user *buf,
if (count > sizeof(sess_data->request))
return -EMSGSIZE;
+ memset(&sess_data->request, 0, sizeof(sess_data->request));
if (copy_from_user(&sess_data->request, buf, count))
return -EFAULT;
ret = check_telem_request(&sess_data->request, count);
@@ -345,7 +367,7 @@ static int telem_device_probe(struct platform_device *pdev)
minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
if (minor < 0) {
error = minor;
- dev_err(&pdev->dev, "Failed to find minor number: %d", error);
+ dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
return error;
}
@@ -384,8 +406,8 @@ static int telem_device_remove(struct platform_device *pdev)
struct telem_device_data *dev_data = platform_get_drvdata(pdev);
cdev_device_del(&dev_data->cdev, &dev_data->dev);
- put_device(&dev_data->dev);
ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
+ put_device(&dev_data->dev);
return 0;
}
@@ -405,14 +427,14 @@ static int __init telem_module_init(void)
ret = class_register(&telem_class);
if (ret) {
- pr_err(DRV_NAME ": Failed registering class: %d", ret);
+ pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
return ret;
}
/* Request the kernel for device numbers, starting with minor=0 */
ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
if (ret) {
- pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret);
+ pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
goto destroy_class;
}
telem_major = MAJOR(dev_num);
OpenPOWER on IntegriCloud