Merge tag 'tag-chrome-platform-for-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux
Pull chrome platform updates from Benson Leung: "CrOS EC / MFD Migration: - Move cros_ec core driver from mfd into chrome platform. Wilco EC: - Add batt_ppid_info command to Wilco telemetry driver. CrOS EC: - cros_ec_rpmsg : Add support to inform EC of suspend/resume status - cros_ec_rpmsg : Fix race condition on probe failed - cros_ec_chardev : Add a poll handler to receive MKBP events Misc: - bugfixes in cros_usbpd_logger and cros_ec_ishtp" * tag 'tag-chrome-platform-for-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: platform/chrome: cros_usbpd_logger: null check create_singlethread_workqueue platform/chrome: cros_ec_chardev: Add a poll handler to receive MKBP events platform/chrome: cros_ec_rpmsg: Fix race with host command when probe failed platform/chrome: chromeos_tbmc: Report wake events mfd: cros_ec: Use mfd_add_hotplug_devices() helper mfd: cros_ec: Add convenience struct to define autodetectable CrOS EC subdevices mfd: cros_ec: Add convenience struct to define dedicated CrOS EC MCUs mfd: cros_ec: Use kzalloc and cros_ec_cmd_xfer_status helper mfd / platform: cros_ec: Reorganize platform and mfd includes mfd / platform: cros_ec: Rename config to a better name mfd: cros_ec: Switch to use the new cros-ec-chardev driver mfd / platform: cros_ec: Miscellaneous character device to talk with the EC mfd / platform: cros_ec: Move cros-ec core driver out from MFD mfd / platform: cros_ec: Handle chained ECs as platform devices platform/chrome: cros_ec_rpmsg: Add host command AP sleep state support platform/chrome: chromeos_laptop: drop checks of NULL-safe functions platform/chrome: wilco_ec: Add batt_ppid_info command to telemetry driver
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -158,8 +192,8 @@ config CROS_EC_DEBUGFS
|
||||
|
||||
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.
|
||||
|
@@ -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,6 +15,7 @@ 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
|
||||
|
@@ -838,18 +838,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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
281
drivers/platform/chrome/cros_ec.c
Normal file
281
drivers/platform/chrome/cros_ec.c
Normal file
@@ -0,0 +1,281 @@
|
||||
// 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 <asm/unaligned.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_thread(int irq, void *data)
|
||||
{
|
||||
struct cros_ec_device *ec_dev = data;
|
||||
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);
|
||||
|
||||
if (ret > 0)
|
||||
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||
0, ec_dev);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
err = devm_request_threaded_irq(dev, ec_dev->irq, NULL,
|
||||
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);
|
||||
|
||||
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
|
||||
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) > 0)
|
||||
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||
1, ec_dev);
|
||||
}
|
||||
|
||||
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");
|
419
drivers/platform/chrome/cros_ec_chardev.c
Normal file
419
drivers/platform/chrome/cros_ec_chardev.c
Normal file
@@ -0,0 +1,419 @@
|
||||
// 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/mfd/cros_ec.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");
|
@@ -8,9 +8,10 @@
|
||||
#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>
|
||||
|
@@ -9,8 +9,8 @@
|
||||
#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>
|
||||
|
||||
@@ -307,6 +307,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 +364,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,
|
||||
};
|
||||
|
||||
|
@@ -8,11 +8,10 @@
|
||||
// (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>
|
||||
|
||||
/*
|
||||
|
@@ -9,8 +9,9 @@
|
||||
#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>
|
||||
|
@@ -16,9 +16,9 @@
|
||||
#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>
|
||||
@@ -421,6 +421,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 +429,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[] = {
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -6,9 +6,9 @@
|
||||
#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>
|
||||
@@ -41,6 +41,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 +73,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 +85,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;
|
||||
@@ -196,11 +196,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 +238,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 +257,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 +291,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);
|
||||
|
@@ -6,9 +6,9 @@
|
||||
#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>
|
||||
@@ -785,6 +785,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 +830,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,
|
||||
};
|
||||
|
||||
|
@@ -9,8 +9,9 @@
|
||||
#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>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
#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_PROTO_VERSION), \
|
||||
TRACE_SYMBOL(EC_CMD_HELLO), \
|
||||
|
@@ -11,8 +11,10 @@
|
||||
#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>
|
||||
|
||||
|
@@ -7,8 +7,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"
|
||||
|
@@ -6,10 +6,11 @@
|
||||
*/
|
||||
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
#include <linux/math64.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 +210,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);
|
||||
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user