chrome platform changes for v5.3
* CrOS EC: - Add new CrOS ISHTP transport protocol - Add proper documentation for debugfs entries and expose resume and uptime files - Select LPC transport protocol variant at runtime. - Add lid angle sensor driver - Fix oops on suspend/resume for lightbar driver - Set CrOS SPI transport protol in realtime * Wilco EC: - Add telemetry char device interface - Add support for event handling - Add new sysfs attributes * Misc: - Contains ib-mfd-cros-v5.3 immutable branch from mfd, with cros_ec_commands.h header freshly synced with Chrome OS's EC project. -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQQCtZK6p/AktxXfkOlzbaomhzOwwgUCXSbP3AAKCRBzbaomhzOw wjoNAP4lrY3UboMaQklHLOCxPTFXwIHjImXxJUCrezJj4eBRcwEAz+adSNKieVEY xNf/yetCkjVnQNMVjGaBJRUp3F+2LwQ= =/Xj3 -----END PGP SIGNATURE----- Merge tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux Pull chrome platform updates from Benson Leung "CrOS EC: - Add new CrOS ISHTP transport protocol - Add proper documentation for debugfs entries and expose resume and uptime files - Select LPC transport protocol variant at runtime. - Add lid angle sensor driver - Fix oops on suspend/resume for lightbar driver - Set CrOS SPI transport protol in realtime Wilco EC: - Add telemetry char device interface - Add support for event handling - Add new sysfs attributes Misc: - Contains ib-mfd-cros-v5.3 immutable branch from mfd, with cros_ec_commands.h header freshly synced with Chrome OS's EC project" * tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (54 commits) mfd / platform: cros_ec_debugfs: Expose resume result via debugfs platform/chrome: lightbar: Get drvdata from parent in suspend/resume iio: cros_ec: Add lid angle driver platform/chrome: wilco_ec: Add circular buffer as event queue platform/chrome: cros_ec_lpc_mec: Fix kernel-doc comment first line platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime platform/chrome: cros_ec_lpc: Merge cros_ec_lpc and cros_ec_lpc_reg Input: cros_ec_keyb: mask out extra flags in event_type platform/chrome: wilco_ec: Fix unreleased lock in event_read() platform/chrome: cros_ec_debugfs: cros_ec_uptime_fops can be static platform/chrome: cros_ec_debugfs: Add debugfs ABI documentation platform/chrome: cros_ec_debugfs: Fix kernel-doc comment first line platform/chrome: cros_ec_debugfs: Add debugfs entry to retrieve EC uptime mfd: cros_ec: Update I2S API mfd: cros_ec: Add Management API entry points mfd: cros_ec: Add SKU ID and Secure storage API mfd: cros_ec: Add API for rwsig mfd: cros_ec: Add API for Fingerprint support mfd: cros_ec: Add API for Touchpad support mfd: cros_ec: Add API for EC-EC communication ...
This commit is contained in:
Коммит
d7d170a8e3
|
@ -0,0 +1,56 @@
|
|||
What: /sys/kernel/debug/<cros-ec-device>/console_log
|
||||
Date: September 2017
|
||||
KernelVersion: 4.13
|
||||
Description:
|
||||
If the EC supports the CONSOLE_READ command type, this file
|
||||
can be used to grab the EC logs. The kernel polls for the log
|
||||
and keeps its own buffer but userspace should grab this and
|
||||
write it out to some logs.
|
||||
|
||||
What: /sys/kernel/debug/<cros-ec-device>/panicinfo
|
||||
Date: September 2017
|
||||
KernelVersion: 4.13
|
||||
Description:
|
||||
This file dumps the EC panic information from the previous
|
||||
reboot. This file will only exist if the PANIC_INFO command
|
||||
type is supported by the EC.
|
||||
|
||||
What: /sys/kernel/debug/<cros-ec-device>/pdinfo
|
||||
Date: June 2018
|
||||
KernelVersion: 4.17
|
||||
Description:
|
||||
This file provides the port role, muxes and power debug
|
||||
information for all the USB PD/type-C ports available. If
|
||||
the are no ports available, this file will be just an empty
|
||||
file.
|
||||
|
||||
What: /sys/kernel/debug/<cros-ec-device>/uptime
|
||||
Date: June 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
A u32 providing the time since EC booted in ms. This is
|
||||
is used for synchronizing the AP host time with the EC
|
||||
log. An error is returned if the command is not supported
|
||||
by the EC or there is a communication problem.
|
||||
|
||||
What: /sys/kernel/debug/<cros-ec-device>/last_resume_result
|
||||
Date: June 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Some ECs have a feature where they will track transitions to
|
||||
the (Intel) processor's SLP_S0 line, in order to detect cases
|
||||
where a system failed to go into S0ix. When the system resumes,
|
||||
an EC with this feature will return a summary of SLP_S0
|
||||
transitions that occurred. The last_resume_result file returns
|
||||
the most recent response from the AP's resume message to the EC.
|
||||
|
||||
The bottom 31 bits contain a count of the number of SLP_S0
|
||||
transitions that occurred since the suspend message was
|
||||
received. Bit 31 is set if the EC attempted to wake the
|
||||
system due to a timeout when watching for SLP_S0 transitions.
|
||||
Callers can use this to detect a wake from the EC due to
|
||||
S0ix timeouts. The result will be zero if no suspend
|
||||
transitions have been attempted, or the EC does not support
|
||||
this feature.
|
||||
|
||||
Output will be in the format: "0x%08x\n".
|
|
@ -23,11 +23,9 @@ Description:
|
|||
|
||||
For writing, bytes 0-1 indicate the message type, one of enum
|
||||
wilco_ec_msg_type. Byte 2+ consist of the data passed in the
|
||||
request, starting at MBOX[0]
|
||||
|
||||
At least three bytes are required for writing, two for the type
|
||||
and at least a single byte of data. Only the first
|
||||
EC_MAILBOX_DATA_SIZE bytes of MBOX will be used.
|
||||
request, starting at MBOX[0]. At least three bytes are required
|
||||
for writing, two for the type and at least a single byte of
|
||||
data.
|
||||
|
||||
Example:
|
||||
// Request EC info type 3 (EC firmware build date)
|
||||
|
@ -40,7 +38,7 @@ Description:
|
|||
$ cat /sys/kernel/debug/wilco_ec/raw
|
||||
00 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 ..12/21/18.8...
|
||||
|
||||
Note that the first 32 bytes of the received MBOX[] will be
|
||||
printed, even if some of the data is junk. It is up to you to
|
||||
know how many of the first bytes of data are the actual
|
||||
response.
|
||||
Note that the first 16 bytes of the received MBOX[] will be
|
||||
printed, even if some of the data is junk, and skipping bytes
|
||||
17 to 32. It is up to you to know how many of the first bytes of
|
||||
data are the actual response.
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
What: /sys/bus/platform/devices/GOOG000C\:00/boot_on_ac
|
||||
Date: April 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Boot on AC is a policy which makes the device boot from S5
|
||||
when AC power is connected. This is useful for users who
|
||||
want to run their device headless or with a dock.
|
||||
|
||||
Input should be parseable by kstrtou8() to 0 or 1.
|
||||
|
||||
What: /sys/bus/platform/devices/GOOG000C\:00/build_date
|
||||
Date: May 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Display Wilco Embedded Controller firmware build date.
|
||||
Output will a MM/DD/YY string.
|
||||
|
||||
What: /sys/bus/platform/devices/GOOG000C\:00/build_revision
|
||||
Date: May 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Display Wilco Embedded Controller build revision.
|
||||
Output will a version string be similar to the example below:
|
||||
d2592cae0
|
||||
|
||||
What: /sys/bus/platform/devices/GOOG000C\:00/model_number
|
||||
Date: May 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Display Wilco Embedded Controller model number.
|
||||
Output will a version string be similar to the example below:
|
||||
08B6
|
||||
|
||||
What: /sys/bus/platform/devices/GOOG000C\:00/version
|
||||
Date: May 2019
|
||||
KernelVersion: 5.3
|
||||
Description:
|
||||
Display Wilco Embedded Controller firmware version.
|
||||
The format of the string is x.y.z. Where x is major, y is minor
|
||||
and z is the build number. For example: 95.00.06
|
|
@ -21,3 +21,12 @@ config IIO_CROS_EC_SENSORS
|
|||
Accelerometers, Gyroscope and Magnetometer that are
|
||||
presented by the ChromeOS EC Sensor hub.
|
||||
Creates an IIO device for each functions.
|
||||
|
||||
config IIO_CROS_EC_SENSORS_LID_ANGLE
|
||||
tristate "ChromeOS EC Sensor for lid angle"
|
||||
depends on IIO_CROS_EC_SENSORS_CORE
|
||||
help
|
||||
Module to report the angle between lid and base for some
|
||||
convertible devices.
|
||||
This module is loaded when the EC can calculate the angle between the base
|
||||
and the lid.
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
|
||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros_ec_sensors_core.o
|
||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o
|
||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* cros_ec_lid_angle - Driver for CrOS EC lid angle sensor.
|
||||
*
|
||||
* Copyright 2018 Google, Inc
|
||||
*
|
||||
* This driver uses the cros-ec interface to communicate with the Chrome OS
|
||||
* EC about counter sensors. Counters are presented through
|
||||
* iio sysfs.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/common/cros_ec_sensors_core.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DRV_NAME "cros-ec-lid-angle"
|
||||
|
||||
/*
|
||||
* One channel for the lid angle, the other for timestamp.
|
||||
*/
|
||||
static const struct iio_chan_spec cros_ec_lid_angle_channels[] = {
|
||||
{
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.scan_type.realbits = CROS_EC_SENSOR_BITS,
|
||||
.scan_type.storagebits = CROS_EC_SENSOR_BITS,
|
||||
.scan_type.sign = 'u',
|
||||
.type = IIO_ANGL
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(1)
|
||||
};
|
||||
|
||||
/* State data for ec_sensors iio driver. */
|
||||
struct cros_ec_lid_angle_state {
|
||||
/* Shared by all sensors */
|
||||
struct cros_ec_sensors_core_state core;
|
||||
};
|
||||
|
||||
static int cros_ec_sensors_read_lid_angle(struct iio_dev *indio_dev,
|
||||
unsigned long scan_mask, s16 *data)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
st->param.cmd = MOTIONSENSE_CMD_LID_ANGLE;
|
||||
ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->lid_angle));
|
||||
if (ret) {
|
||||
dev_warn(&indio_dev->dev, "Unable to read lid angle\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*data = st->resp->lid_angle.value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_lid_angle_read(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cros_ec_lid_angle_state *st = iio_priv(indio_dev);
|
||||
s16 data;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&st->core.cmd_lock);
|
||||
ret = cros_ec_sensors_read_lid_angle(indio_dev, 1, &data);
|
||||
if (ret == 0) {
|
||||
*val = data;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
mutex_unlock(&st->core.cmd_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info cros_ec_lid_angle_info = {
|
||||
.read_raw = &cros_ec_lid_angle_read,
|
||||
};
|
||||
|
||||
static int cros_ec_lid_angle_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct cros_ec_lid_angle_state *state;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = cros_ec_sensors_core_init(pdev, indio_dev, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->info = &cros_ec_lid_angle_info;
|
||||
state = iio_priv(indio_dev);
|
||||
indio_dev->channels = cros_ec_lid_angle_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(cros_ec_lid_angle_channels);
|
||||
|
||||
state->core.read_ec_sensors_data = cros_ec_sensors_read_lid_angle;
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
|
||||
cros_ec_sensors_capture, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_ec_lid_angle_ids[] = {
|
||||
{
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids);
|
||||
|
||||
static struct platform_driver cros_ec_lid_angle_platform_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.pm = &cros_ec_sensors_pm_ops,
|
||||
},
|
||||
.probe = cros_ec_lid_angle_probe,
|
||||
.id_table = cros_ec_lid_angle_ids,
|
||||
};
|
||||
module_platform_driver(cros_ec_lid_angle_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ChromeOS EC driver for reporting convertible lid angle.");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -237,7 +237,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
|
|||
if (queued_during_suspend && !device_may_wakeup(ckdev->dev))
|
||||
return NOTIFY_OK;
|
||||
|
||||
switch (ckdev->ec->event_data.event_type) {
|
||||
switch (ckdev->ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK) {
|
||||
case EC_MKBP_EVENT_KEY_MATRIX:
|
||||
pm_wakeup_event(ckdev->dev, 0);
|
||||
|
||||
|
|
|
@ -102,12 +102,16 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
|
|||
|
||||
/* 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))
|
||||
(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;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,19 @@ config CROS_EC_RPMSG
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_rpmsg.
|
||||
|
||||
config CROS_EC_ISHTP
|
||||
tristate "ChromeOS Embedded Controller (ISHTP)"
|
||||
depends on MFD_CROS_EC
|
||||
depends on INTEL_ISH_HID
|
||||
help
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
firmware running on Intel Integrated Sensor Hub (ISH), using the
|
||||
ISH Transport protocol (ISH-TP). This uses a simple byte-level
|
||||
protocol with a checksum.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_ishtp.
|
||||
|
||||
config CROS_EC_SPI
|
||||
tristate "ChromeOS Embedded Controller (SPI)"
|
||||
depends on MFD_CROS_EC && SPI
|
||||
|
@ -83,28 +96,17 @@ config CROS_EC_SPI
|
|||
'pre-amble' bytes before the response actually starts.
|
||||
|
||||
config CROS_EC_LPC
|
||||
tristate "ChromeOS Embedded Controller (LPC)"
|
||||
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
||||
help
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
over an LPC bus. This uses a simple byte-level protocol with a
|
||||
checksum. This is used for userspace access only. The kernel
|
||||
typically has its own communication methods.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_lpc.
|
||||
|
||||
config CROS_EC_LPC_MEC
|
||||
bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant"
|
||||
depends on CROS_EC_LPC
|
||||
default n
|
||||
tristate "ChromeOS Embedded Controller (LPC)"
|
||||
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
||||
help
|
||||
If you say Y here, a variant LPC protocol for the Microchip EC
|
||||
will be used. Note that this variant is not backward compatible
|
||||
with non-Microchip ECs.
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
over an LPC bus, including the LPC Microchip EC (MEC) variant.
|
||||
This uses a simple byte-level protocol with a checksum. This is
|
||||
used for userspace access only. The kernel typically has its own
|
||||
communication methods.
|
||||
|
||||
If you have a ChromeOS Embedded Controller Microchip EC variant
|
||||
choose Y here.
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_lpcs.
|
||||
|
||||
config CROS_EC_PROTO
|
||||
bool
|
||||
|
|
|
@ -7,10 +7,10 @@ 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_I2C) += cros_ec_i2c.o
|
||||
obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
|
||||
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
|
||||
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o
|
||||
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o
|
||||
cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
|
||||
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
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
|
||||
#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
|
||||
|
||||
/* struct cros_ec_debugfs - ChromeOS EC debugging information
|
||||
/**
|
||||
* struct cros_ec_debugfs - EC debugging information.
|
||||
*
|
||||
* @ec: EC device this debugfs information belongs to
|
||||
* @dir: dentry for debugfs files
|
||||
|
@ -241,7 +242,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file,
|
|||
read_buf, p - read_buf);
|
||||
}
|
||||
|
||||
const struct file_operations cros_ec_console_log_fops = {
|
||||
static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct cros_ec_debugfs *debug_info = file->private_data;
|
||||
struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
|
||||
struct {
|
||||
struct cros_ec_command cmd;
|
||||
struct ec_response_uptime_info resp;
|
||||
} __packed msg = {};
|
||||
struct ec_response_uptime_info *resp;
|
||||
char read_buf[32];
|
||||
int ret;
|
||||
|
||||
resp = (struct ec_response_uptime_info *)&msg.resp;
|
||||
|
||||
msg.cmd.command = EC_CMD_GET_UPTIME_INFO;
|
||||
msg.cmd.insize = sizeof(*resp);
|
||||
|
||||
ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = scnprintf(read_buf, sizeof(read_buf), "%u\n",
|
||||
resp->time_since_ec_boot_ms);
|
||||
|
||||
return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret);
|
||||
}
|
||||
|
||||
static const struct file_operations cros_ec_console_log_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cros_ec_console_log_open,
|
||||
.read = cros_ec_console_log_read,
|
||||
|
@ -250,13 +279,20 @@ const struct file_operations cros_ec_console_log_fops = {
|
|||
.release = cros_ec_console_log_release,
|
||||
};
|
||||
|
||||
const struct file_operations cros_ec_pdinfo_fops = {
|
||||
static const struct file_operations cros_ec_pdinfo_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = cros_ec_pdinfo_read,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static const struct file_operations cros_ec_uptime_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = cros_ec_uptime_read,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static int ec_read_version_supported(struct cros_ec_dev *ec)
|
||||
{
|
||||
struct ec_params_get_cmd_versions_v1 *params;
|
||||
|
@ -408,6 +444,12 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
|
|||
debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
|
||||
&cros_ec_pdinfo_fops);
|
||||
|
||||
debugfs_create_file("uptime", 0444, debug_info->dir, debug_info,
|
||||
&cros_ec_uptime_fops);
|
||||
|
||||
debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
|
||||
&ec->ec_dev->last_resume_result);
|
||||
|
||||
ec->debug_info = debug_info;
|
||||
|
||||
dev_set_drvdata(&pd->dev, ec);
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// ISHTP interface for ChromeOS Embedded Controller
|
||||
//
|
||||
// Copyright (c) 2019, Intel Corporation.
|
||||
//
|
||||
// ISHTP client driver for talking to the Chrome OS EC firmware running
|
||||
// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
|
||||
// (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/intel-ish-client-if.h>
|
||||
|
||||
/*
|
||||
* ISH TX/RX ring buffer pool size
|
||||
*
|
||||
* The AP->ISH messages and corresponding ISH->AP responses are
|
||||
* serialized. We need 1 TX and 1 RX buffer for these.
|
||||
*
|
||||
* The MKBP ISH->AP events are serialized. We need one additional RX
|
||||
* buffer for them.
|
||||
*/
|
||||
#define CROS_ISH_CL_TX_RING_SIZE 8
|
||||
#define CROS_ISH_CL_RX_RING_SIZE 8
|
||||
|
||||
/* ISH CrOS EC Host Commands */
|
||||
enum cros_ec_ish_channel {
|
||||
CROS_EC_COMMAND = 1, /* AP->ISH message */
|
||||
CROS_MKBP_EVENT = 2, /* ISH->AP events */
|
||||
};
|
||||
|
||||
/*
|
||||
* ISH firmware timeout for 1 message send failure is 1Hz, and the
|
||||
* firmware will retry 2 times, so 3Hz is used for timeout.
|
||||
*/
|
||||
#define ISHTP_SEND_TIMEOUT (3 * HZ)
|
||||
|
||||
/* ISH Transport CrOS EC ISH client unique GUID */
|
||||
static const guid_t cros_ish_guid =
|
||||
GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
|
||||
0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
|
||||
|
||||
struct header {
|
||||
u8 channel;
|
||||
u8 status;
|
||||
u8 reserved[2];
|
||||
} __packed;
|
||||
|
||||
struct cros_ish_out_msg {
|
||||
struct header hdr;
|
||||
struct ec_host_request ec_request;
|
||||
} __packed;
|
||||
|
||||
struct cros_ish_in_msg {
|
||||
struct header hdr;
|
||||
struct ec_host_response ec_response;
|
||||
} __packed;
|
||||
|
||||
#define IN_MSG_EC_RESPONSE_PREAMBLE \
|
||||
offsetof(struct cros_ish_in_msg, ec_response)
|
||||
|
||||
#define OUT_MSG_EC_REQUEST_PREAMBLE \
|
||||
offsetof(struct cros_ish_out_msg, ec_request)
|
||||
|
||||
#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
|
||||
|
||||
/*
|
||||
* The Read-Write Semaphore is used to prevent message TX or RX while
|
||||
* the ishtp client is being initialized or undergoing reset.
|
||||
*
|
||||
* The readers are the kernel function calls responsible for IA->ISH
|
||||
* and ISH->AP messaging.
|
||||
*
|
||||
* The writers are .reset() and .probe() function.
|
||||
*/
|
||||
DECLARE_RWSEM(init_lock);
|
||||
|
||||
/**
|
||||
* struct response_info - Encapsulate firmware response related
|
||||
* information for passing between function ish_send() and
|
||||
* process_recv() callback.
|
||||
*
|
||||
* @data: Copy the data received from firmware here.
|
||||
* @max_size: Max size allocated for the @data buffer. If the received
|
||||
* data exceeds this value, we log an error.
|
||||
* @size: Actual size of data received from firmware.
|
||||
* @error: 0 for success, negative error code for a failure in process_recv().
|
||||
* @received: Set to true on receiving a valid firmware response to host command
|
||||
* @wait_queue: Wait queue for host to wait for firmware response.
|
||||
*/
|
||||
struct response_info {
|
||||
void *data;
|
||||
size_t max_size;
|
||||
size_t size;
|
||||
int error;
|
||||
bool received;
|
||||
wait_queue_head_t wait_queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ishtp_cl_data - Encapsulate per ISH TP Client.
|
||||
*
|
||||
* @cros_ish_cl: ISHTP firmware client instance.
|
||||
* @cl_device: ISHTP client device instance.
|
||||
* @response: Response info passing between ish_send() and process_recv().
|
||||
* @work_ishtp_reset: Work queue reset handling.
|
||||
* @work_ec_evt: Work queue for EC events.
|
||||
* @ec_dev: CrOS EC MFD device.
|
||||
*
|
||||
* This structure is used to store per client data.
|
||||
*/
|
||||
struct ishtp_cl_data {
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
|
||||
/*
|
||||
* Used for passing firmware response information between
|
||||
* ish_send() and process_recv() callback.
|
||||
*/
|
||||
struct response_info response;
|
||||
|
||||
struct work_struct work_ishtp_reset;
|
||||
struct work_struct work_ec_evt;
|
||||
struct cros_ec_device *ec_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* ish_evt_handler - ISH to AP event handler
|
||||
* @work: Work struct
|
||||
*/
|
||||
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;
|
||||
|
||||
if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
|
||||
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||
0, ec_dev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ish_send() - Send message from host to firmware
|
||||
*
|
||||
* @client_data: Client data instance
|
||||
* @out_msg: Message buffer to be sent to firmware
|
||||
* @out_size: Size of out going message
|
||||
* @in_msg: Message buffer where the incoming data is copied. This buffer
|
||||
* is allocated by calling
|
||||
* @in_size: Max size of incoming message
|
||||
*
|
||||
* Return: Number of bytes copied in the in_msg on success, negative
|
||||
* error code on failure.
|
||||
*/
|
||||
static int ish_send(struct ishtp_cl_data *client_data,
|
||||
u8 *out_msg, size_t out_size,
|
||||
u8 *in_msg, size_t in_size)
|
||||
{
|
||||
int rv;
|
||||
struct header *out_hdr = (struct header *)out_msg;
|
||||
struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
|
||||
|
||||
dev_dbg(cl_data_to_dev(client_data),
|
||||
"%s: channel=%02u status=%02u\n",
|
||||
__func__, out_hdr->channel, out_hdr->status);
|
||||
|
||||
/* Setup for incoming response */
|
||||
client_data->response.data = in_msg;
|
||||
client_data->response.max_size = in_size;
|
||||
client_data->response.error = 0;
|
||||
client_data->response.received = false;
|
||||
|
||||
rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_send error %d\n", rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
wait_event_interruptible_timeout(client_data->response.wait_queue,
|
||||
client_data->response.received,
|
||||
ISHTP_SEND_TIMEOUT);
|
||||
if (!client_data->response.received) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"Timed out for response to host message\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (client_data->response.error < 0)
|
||||
return client_data->response.error;
|
||||
|
||||
return client_data->response.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* process_recv() - Received and parse incoming packet
|
||||
* @cros_ish_cl: Client instance to get stats
|
||||
* @rb_in_proc: Host interface message buffer
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
size_t data_len = rb_in_proc->buf_idx;
|
||||
struct ishtp_cl_data *client_data =
|
||||
ishtp_get_client_data(cros_ish_cl);
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
struct cros_ish_in_msg *in_msg =
|
||||
(struct cros_ish_in_msg *)rb_in_proc->buffer.data;
|
||||
|
||||
/* Proceed only if reset or init is not in progress */
|
||||
if (!down_read_trylock(&init_lock)) {
|
||||
/* Free the buffer */
|
||||
ishtp_cl_io_rb_recycle(rb_in_proc);
|
||||
dev_warn(dev,
|
||||
"Host is not ready to receive incoming messages\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* All firmware messages contain a header. Check the buffer size
|
||||
* before accessing elements inside.
|
||||
*/
|
||||
if (!rb_in_proc->buffer.data) {
|
||||
dev_warn(dev, "rb_in_proc->buffer.data returned null");
|
||||
client_data->response.error = -EBADMSG;
|
||||
goto end_error;
|
||||
}
|
||||
|
||||
if (data_len < sizeof(struct header)) {
|
||||
dev_err(dev, "data size %zu is less than header %zu\n",
|
||||
data_len, sizeof(struct header));
|
||||
client_data->response.error = -EMSGSIZE;
|
||||
goto end_error;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "channel=%02u status=%02u\n",
|
||||
in_msg->hdr.channel, in_msg->hdr.status);
|
||||
|
||||
switch (in_msg->hdr.channel) {
|
||||
case CROS_EC_COMMAND:
|
||||
/* Sanity check */
|
||||
if (!client_data->response.data) {
|
||||
dev_err(dev,
|
||||
"Receiving buffer is null. Should be allocated by calling function\n");
|
||||
client_data->response.error = -EINVAL;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (client_data->response.received) {
|
||||
dev_err(dev,
|
||||
"Previous firmware message not yet processed\n");
|
||||
client_data->response.error = -EINVAL;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (data_len > client_data->response.max_size) {
|
||||
dev_err(dev,
|
||||
"Received buffer size %zu is larger than allocated buffer %zu\n",
|
||||
data_len, client_data->response.max_size);
|
||||
client_data->response.error = -EMSGSIZE;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (in_msg->hdr.status) {
|
||||
dev_err(dev, "firmware returned status %d\n",
|
||||
in_msg->hdr.status);
|
||||
client_data->response.error = -EIO;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
/* Update the actual received buffer size */
|
||||
client_data->response.size = data_len;
|
||||
|
||||
/*
|
||||
* Copy the buffer received in firmware response for the
|
||||
* calling thread.
|
||||
*/
|
||||
memcpy(client_data->response.data,
|
||||
rb_in_proc->buffer.data, data_len);
|
||||
|
||||
/* Set flag before waking up the caller */
|
||||
client_data->response.received = true;
|
||||
error_wake_up:
|
||||
/* Wake the calling thread */
|
||||
wake_up_interruptible(&client_data->response.wait_queue);
|
||||
|
||||
break;
|
||||
|
||||
case CROS_MKBP_EVENT:
|
||||
/* The event system doesn't send any data in buffer */
|
||||
schedule_work(&client_data->work_ec_evt);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
|
||||
}
|
||||
|
||||
end_error:
|
||||
/* Free the buffer */
|
||||
ishtp_cl_io_rb_recycle(rb_in_proc);
|
||||
|
||||
up_read(&init_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* ish_event_cb() - bus driver callback for incoming message
|
||||
* @cl_device: ISHTP client device for which this message is targeted.
|
||||
*
|
||||
* Remove the packet from the list and process the message by calling
|
||||
* process_recv.
|
||||
*/
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ish_init() - Init function for ISHTP client
|
||||
* @cros_ish_cl: ISHTP client instance
|
||||
*
|
||||
* This function complete the initializtion of the client.
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_device *dev;
|
||||
struct ishtp_fw_client *fw_client;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
rv = ishtp_cl_link(cros_ish_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_link failed\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
dev = ishtp_get_ishtp_device(cros_ish_cl);
|
||||
|
||||
/* Connect to firmware client */
|
||||
ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
|
||||
ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
|
||||
|
||||
fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
|
||||
if (!fw_client) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ish client uuid not found\n");
|
||||
rv = -ENOENT;
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_cl_set_fw_client_id(cros_ish_cl,
|
||||
ishtp_get_fw_client_id(fw_client));
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
rv = ishtp_cl_connect(cros_ish_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"client connect fail\n");
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
|
||||
return 0;
|
||||
|
||||
err_cl_unlink:
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ish_deinit() - Deinit function for ISHTP client
|
||||
* @cros_ish_cl: ISHTP client instance
|
||||
*
|
||||
* Unlink and free cros_ec client
|
||||
*/
|
||||
static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
|
||||
{
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
|
||||
/* Disband and free all Tx and Rx client-level rings */
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare_cros_ec_rx() - Check & prepare receive buffer
|
||||
* @ec_dev: CrOS EC MFD device.
|
||||
* @in_msg: Incoming message buffer
|
||||
* @msg: cros_ec command used to send & receive data
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*
|
||||
* Check the received buffer. Convert to cros_ec_command format.
|
||||
*/
|
||||
static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
|
||||
const struct cros_ish_in_msg *in_msg,
|
||||
struct cros_ec_command *msg)
|
||||
{
|
||||
u8 sum = 0;
|
||||
int i, rv, offset;
|
||||
|
||||
/* Check response error code */
|
||||
msg->result = in_msg->ec_response.result;
|
||||
rv = cros_ec_check_result(ec_dev, msg);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
if (in_msg->ec_response.data_len > msg->insize) {
|
||||
dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
|
||||
in_msg->ec_response.data_len, msg->insize);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Copy response packet payload and compute checksum */
|
||||
for (i = 0; i < sizeof(struct ec_host_response); i++)
|
||||
sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
|
||||
|
||||
offset = sizeof(struct cros_ish_in_msg);
|
||||
for (i = 0; i < in_msg->ec_response.data_len; i++)
|
||||
sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
|
||||
|
||||
if (sum) {
|
||||
dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_command *msg)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_cl *cros_ish_cl = ec_dev->priv;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
|
||||
struct cros_ish_out_msg *out_msg =
|
||||
(struct cros_ish_out_msg *)ec_dev->dout;
|
||||
size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
|
||||
size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
|
||||
|
||||
/* Sanity checks */
|
||||
if (in_size > ec_dev->din_size) {
|
||||
dev_err(dev,
|
||||
"Incoming payload size %zu is too large for ec_dev->din_size %d\n",
|
||||
in_size, ec_dev->din_size);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if (out_size > ec_dev->dout_size) {
|
||||
dev_err(dev,
|
||||
"Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
|
||||
out_size, ec_dev->dout_size);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* Proceed only if reset-init is not in progress */
|
||||
if (!down_read_trylock(&init_lock)) {
|
||||
dev_warn(dev,
|
||||
"Host is not ready to send messages to ISH. Try again\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* Prepare the package to be sent over ISH TP */
|
||||
out_msg->hdr.channel = CROS_EC_COMMAND;
|
||||
out_msg->hdr.status = 0;
|
||||
|
||||
ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
|
||||
cros_ec_prepare_tx(ec_dev, msg);
|
||||
ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
|
||||
|
||||
dev_dbg(dev,
|
||||
"out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
|
||||
out_msg->ec_request.struct_version,
|
||||
out_msg->ec_request.checksum,
|
||||
out_msg->ec_request.command,
|
||||
out_msg->ec_request.command_version,
|
||||
out_msg->ec_request.data_len);
|
||||
|
||||
/* Send command to ISH EC firmware and read response */
|
||||
rv = ish_send(client_data,
|
||||
(u8 *)out_msg, out_size,
|
||||
(u8 *)in_msg, in_size);
|
||||
if (rv < 0)
|
||||
goto end_error;
|
||||
|
||||
rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
|
||||
if (rv)
|
||||
goto end_error;
|
||||
|
||||
rv = in_msg->ec_response.data_len;
|
||||
|
||||
dev_dbg(dev,
|
||||
"in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
|
||||
in_msg->ec_response.struct_version,
|
||||
in_msg->ec_response.checksum,
|
||||
in_msg->ec_response.result,
|
||||
in_msg->ec_response.data_len);
|
||||
|
||||
end_error:
|
||||
if (msg->command == EC_CMD_REBOOT_EC)
|
||||
msleep(EC_REBOOT_DELAY_MS);
|
||||
|
||||
up_read(&init_lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
|
||||
{
|
||||
struct cros_ec_device *ec_dev;
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
|
||||
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
|
||||
if (!ec_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
client_data->ec_dev = ec_dev;
|
||||
dev->driver_data = ec_dev;
|
||||
|
||||
ec_dev->dev = dev;
|
||||
ec_dev->priv = client_data->cros_ish_cl;
|
||||
ec_dev->cmd_xfer = NULL;
|
||||
ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
|
||||
ec_dev->phys_name = dev_name(dev);
|
||||
ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
|
||||
sizeof(struct ec_response_get_protocol_info);
|
||||
ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
|
||||
|
||||
return cros_ec_register(ec_dev);
|
||||
}
|
||||
|
||||
static void reset_handler(struct work_struct *work)
|
||||
{
|
||||
int rv;
|
||||
struct device *dev;
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
struct ishtp_cl_data *client_data =
|
||||
container_of(work, struct ishtp_cl_data, work_ishtp_reset);
|
||||
|
||||
/* Lock for reset to complete */
|
||||
down_write(&init_lock);
|
||||
|
||||
cros_ish_cl = client_data->cros_ish_cl;
|
||||
cl_device = client_data->cl_device;
|
||||
|
||||
/* Unlink, flush queues & start again */
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
|
||||
cros_ish_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!cros_ish_cl) {
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
ishtp_set_drvdata(cl_device, cros_ish_cl);
|
||||
ishtp_set_client_data(cros_ish_cl, client_data);
|
||||
client_data->cros_ish_cl = cros_ish_cl;
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
if (rv) {
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Refresh ec_dev device pointers */
|
||||
client_data->ec_dev->priv = client_data->cros_ish_cl;
|
||||
dev = cl_data_to_dev(client_data);
|
||||
dev->driver_data = client_data->ec_dev;
|
||||
|
||||
dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
|
||||
|
||||
up_write(&init_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_probe() - ISHTP client driver probe callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_data *client_data =
|
||||
devm_kzalloc(ishtp_device(cl_device),
|
||||
sizeof(*client_data), GFP_KERNEL);
|
||||
if (!client_data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Lock for initialization to complete */
|
||||
down_write(&init_lock);
|
||||
|
||||
cros_ish_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!cros_ish_cl) {
|
||||
rv = -ENOMEM;
|
||||
goto end_ishtp_cl_alloc_error;
|
||||
}
|
||||
|
||||
ishtp_set_drvdata(cl_device, cros_ish_cl);
|
||||
ishtp_set_client_data(cros_ish_cl, client_data);
|
||||
client_data->cros_ish_cl = cros_ish_cl;
|
||||
client_data->cl_device = cl_device;
|
||||
|
||||
init_waitqueue_head(&client_data->response.wait_queue);
|
||||
|
||||
INIT_WORK(&client_data->work_ishtp_reset,
|
||||
reset_handler);
|
||||
INIT_WORK(&client_data->work_ec_evt,
|
||||
ish_evt_handler);
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
if (rv)
|
||||
goto end_ishtp_cl_init_error;
|
||||
|
||||
ishtp_get_device(cl_device);
|
||||
|
||||
up_write(&init_lock);
|
||||
|
||||
/* Register croc_ec_dev mfd */
|
||||
rv = cros_ec_dev_init(client_data);
|
||||
if (rv)
|
||||
goto end_cros_ec_dev_init_error;
|
||||
|
||||
return 0;
|
||||
|
||||
end_cros_ec_dev_init_error:
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_put_device(cl_device);
|
||||
end_ishtp_cl_init_error:
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
end_ishtp_cl_alloc_error:
|
||||
up_write(&init_lock);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_remove() - ISHTP client driver remove callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_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);
|
||||
|
||||
cancel_work_sync(&client_data->work_ishtp_reset);
|
||||
cancel_work_sync(&client_data->work_ec_evt);
|
||||
cros_ish_deinit(cros_ish_cl);
|
||||
ishtp_put_device(cl_device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_reset() - ISHTP client driver reset callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_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);
|
||||
|
||||
schedule_work(&client_data->work_ishtp_reset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
|
||||
* @device: device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
|
||||
{
|
||||
struct ishtp_cl_device *cl_device = dev_get_drvdata(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);
|
||||
|
||||
return cros_ec_suspend(client_data->ec_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_resume() - ISHTP client driver resume callback
|
||||
* @device: device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
|
||||
{
|
||||
struct ishtp_cl_device *cl_device = dev_get_drvdata(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);
|
||||
|
||||
return cros_ec_resume(client_data->ec_dev);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
|
||||
cros_ec_ishtp_resume);
|
||||
|
||||
static struct ishtp_cl_driver cros_ec_ishtp_driver = {
|
||||
.name = "cros_ec_ishtp",
|
||||
.guid = &cros_ish_guid,
|
||||
.probe = cros_ec_ishtp_probe,
|
||||
.remove = cros_ec_ishtp_remove,
|
||||
.reset = cros_ec_ishtp_reset,
|
||||
.driver = {
|
||||
.pm = &cros_ec_ishtp_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init cros_ec_ishtp_mod_init(void)
|
||||
{
|
||||
return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
|
||||
}
|
||||
|
||||
static void __exit cros_ec_ishtp_mod_exit(void)
|
||||
{
|
||||
ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
|
||||
}
|
||||
|
||||
module_init(cros_ec_ishtp_mod_init);
|
||||
module_exit(cros_ec_ishtp_mod_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
|
||||
MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("ishtp:*");
|
|
@ -547,7 +547,7 @@ static struct attribute *__lb_cmds_attrs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
struct attribute_group cros_ec_lightbar_attr_group = {
|
||||
static struct attribute_group cros_ec_lightbar_attr_group = {
|
||||
.name = "lightbar",
|
||||
.attrs = __lb_cmds_attrs,
|
||||
};
|
||||
|
@ -600,7 +600,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd)
|
|||
|
||||
static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
|
||||
{
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (userspace_control)
|
||||
return 0;
|
||||
|
@ -610,7 +610,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
|
|||
|
||||
static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
|
||||
{
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (userspace_control)
|
||||
return 0;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include <linux/printk.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include "cros_ec_lpc_reg.h"
|
||||
#include "cros_ec_lpc_mec.h"
|
||||
|
||||
#define DRV_NAME "cros_ec_lpcs"
|
||||
#define ACPI_DRV_NAME "GOOG0004"
|
||||
|
@ -31,6 +31,96 @@
|
|||
/* True if ACPI device is present */
|
||||
static bool cros_ec_lpc_acpi_device_found;
|
||||
|
||||
/**
|
||||
* struct lpc_driver_ops - LPC driver operations
|
||||
* @read: Copy length bytes from EC address offset into buffer dest. Returns
|
||||
* the 8-bit checksum of all bytes read.
|
||||
* @write: Copy length bytes from buffer msg into EC address offset. Returns
|
||||
* the 8-bit checksum of all bytes written.
|
||||
*/
|
||||
struct lpc_driver_ops {
|
||||
u8 (*read)(unsigned int offset, unsigned int length, u8 *dest);
|
||||
u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg);
|
||||
};
|
||||
|
||||
static struct lpc_driver_ops cros_ec_lpc_ops = { };
|
||||
|
||||
/*
|
||||
* A generic instance of the read function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
dest[i] = inb(offset + i);
|
||||
sum += dest[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes read */
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* A generic instance of the write function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
outb(msg[i], offset + i);
|
||||
sum += msg[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes written */
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* An instance of the read function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, dest) :
|
||||
cros_ec_lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
/*
|
||||
* An instance of the write function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, (u8 *)msg) :
|
||||
cros_ec_lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
static int ec_response_timed_out(void)
|
||||
{
|
||||
unsigned long one_second = jiffies + HZ;
|
||||
|
@ -38,7 +128,7 @@ static int ec_response_timed_out(void)
|
|||
|
||||
usleep_range(200, 300);
|
||||
do {
|
||||
if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) &
|
||||
if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) &
|
||||
EC_LPC_STATUS_BUSY_MASK))
|
||||
return 0;
|
||||
usleep_range(100, 200);
|
||||
|
@ -58,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|||
ret = cros_ec_prepare_tx(ec, msg);
|
||||
|
||||
/* Write buffer */
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
|
||||
/* Here we go */
|
||||
sum = EC_COMMAND_PROTOCOL_3;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
||||
|
@ -71,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back response */
|
||||
dout = (u8 *)&response;
|
||||
sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
dout);
|
||||
sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
dout);
|
||||
|
||||
msg->result = response.result;
|
||||
|
||||
|
@ -92,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|||
}
|
||||
|
||||
/* Read response and process checksum */
|
||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
|
||||
if (sum) {
|
||||
dev_err(ec->dev,
|
||||
|
@ -134,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Copy data and update checksum */
|
||||
sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
|
||||
/* Finalize checksum and write args */
|
||||
args.checksum = sum;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
|
||||
/* Here we go */
|
||||
sum = msg->command;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
||||
|
@ -153,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back args */
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
||||
|
||||
if (args.data_size > msg->insize) {
|
||||
dev_err(ec->dev,
|
||||
|
@ -174,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Read response and update checksum */
|
||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
|
||||
/* Verify checksum */
|
||||
if (args.checksum != sum) {
|
||||
|
@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
|||
|
||||
/* fixed length */
|
||||
if (bytes) {
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* string */
|
||||
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s);
|
||||
cnt++;
|
||||
if (!*s)
|
||||
break;
|
||||
|
@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
/*
|
||||
* Read the mapped ID twice, the first one is assuming the
|
||||
* EC is a Microchip Embedded Controller (MEC) variant, if the
|
||||
* protocol fails, fallback to the non MEC variant and try to
|
||||
* read again the ID.
|
||||
*/
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes;
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
dev_err(dev, "EC ID not detected\n");
|
||||
return -ENODEV;
|
||||
/* Re-assign read/write operations for the non MEC variant */
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes;
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2,
|
||||
buf);
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
dev_err(dev, "EC ID not detected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
|
||||
|
@ -405,7 +509,7 @@ static int cros_ec_lpc_resume(struct device *dev)
|
|||
}
|
||||
#endif
|
||||
|
||||
const struct dev_pm_ops cros_ec_lpc_pm_ops = {
|
||||
static const struct dev_pm_ops cros_ec_lpc_pm_ops = {
|
||||
SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume)
|
||||
};
|
||||
|
||||
|
@ -446,13 +550,14 @@ static int __init cros_ec_lpc_init(void)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
cros_ec_lpc_reg_init();
|
||||
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
|
||||
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
|
||||
|
||||
/* Register the driver */
|
||||
ret = platform_driver_register(&cros_ec_lpc_driver);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -462,7 +567,7 @@ static int __init cros_ec_lpc_init(void)
|
|||
if (ret) {
|
||||
pr_err(DRV_NAME ": can't register device: %d\n", ret);
|
||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,7 +579,7 @@ static void __exit cros_ec_lpc_exit(void)
|
|||
if (!cros_ec_lpc_acpi_device_found)
|
||||
platform_device_unregister(&cros_ec_lpc_device);
|
||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
|
||||
module_init(cros_ec_lpc_init);
|
||||
|
|
|
@ -17,12 +17,10 @@
|
|||
static struct mutex io_mutex;
|
||||
static u16 mec_emi_base, mec_emi_end;
|
||||
|
||||
/*
|
||||
* cros_ec_lpc_mec_emi_write_address
|
||||
/**
|
||||
* cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
|
||||
*
|
||||
* Initialize EMI read / write at a given address.
|
||||
*
|
||||
* @addr: Starting read / write address
|
||||
* @addr: Starting read / write address
|
||||
* @access_type: Type of access, typically 32-bit auto-increment
|
||||
*/
|
||||
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
|
||||
|
@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port
|
||||
/**
|
||||
* cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port.
|
||||
*
|
||||
* @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request
|
||||
* @offset: Base read / write address
|
||||
* @length: Number of bytes to read / write
|
||||
* @buf: Destination / source buffer
|
||||
*
|
||||
* @return 8-bit checksum of all bytes read / written
|
||||
* Return: 8-bit checksum of all bytes read / written
|
||||
*/
|
||||
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length,
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// LPC interface for ChromeOS Embedded Controller
|
||||
//
|
||||
// Copyright (C) 2016 Google, Inc
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
|
||||
#include "cros_ec_lpc_mec.h"
|
||||
|
||||
static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
int i;
|
||||
int sum = 0;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
dest[i] = inb(offset + i);
|
||||
sum += dest[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes read */
|
||||
return sum;
|
||||
}
|
||||
|
||||
static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
int i;
|
||||
int sum = 0;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
outb(msg[i], offset + i);
|
||||
sum += msg[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes written */
|
||||
return sum;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CROS_EC_LPC_MEC
|
||||
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, dest) :
|
||||
lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, msg) :
|
||||
lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_init(void)
|
||||
{
|
||||
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
|
||||
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_destroy(void)
|
||||
{
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
|
||||
#else /* CONFIG_CROS_EC_LPC_MEC */
|
||||
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
return lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
return lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_destroy(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_CROS_EC_LPC_MEC */
|
|
@ -1,45 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* LPC interface for ChromeOS Embedded Controller
|
||||
*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
*/
|
||||
|
||||
#ifndef __CROS_EC_LPC_REG_H
|
||||
#define __CROS_EC_LPC_REG_H
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address.
|
||||
* Returns 8-bit checksum of all bytes read.
|
||||
*
|
||||
* @offset: Base read address
|
||||
* @length: Number of bytes to read
|
||||
* @dest: Destination buffer
|
||||
*/
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address.
|
||||
* Returns 8-bit checksum of all bytes written.
|
||||
*
|
||||
* @offset: Base write address
|
||||
* @length: Number of bytes to write
|
||||
* @msg: Write data buffer
|
||||
*/
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_reg_init
|
||||
*
|
||||
* Initialize register I/O.
|
||||
*/
|
||||
void cros_ec_lpc_reg_init(void);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_reg_destroy
|
||||
*
|
||||
* Cleanup reg I/O.
|
||||
*/
|
||||
void cros_ec_lpc_reg_destroy(void);
|
||||
|
||||
#endif /* __CROS_EC_LPC_REG_H */
|
|
@ -12,7 +12,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <uapi/linux/sched/types.h>
|
||||
|
||||
/* The header byte, which follows the preamble */
|
||||
#define EC_MSG_HEADER 0xec
|
||||
|
@ -67,12 +67,14 @@
|
|||
* is sent when we want to turn on CS at the start of a transaction.
|
||||
* @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that
|
||||
* is sent when we want to turn off CS at the end of a transaction.
|
||||
* @high_pri_worker: Used to schedule high priority work.
|
||||
*/
|
||||
struct cros_ec_spi {
|
||||
struct spi_device *spi;
|
||||
s64 last_transfer_ns;
|
||||
unsigned int start_of_msg_delay;
|
||||
unsigned int end_of_msg_delay;
|
||||
struct kthread_worker *high_pri_worker;
|
||||
};
|
||||
|
||||
typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
|
||||
|
@ -89,7 +91,7 @@ typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
|
|||
*/
|
||||
|
||||
struct cros_ec_xfer_work_params {
|
||||
struct work_struct work;
|
||||
struct kthread_work work;
|
||||
cros_ec_xfer_fn_t fn;
|
||||
struct cros_ec_device *ec_dev;
|
||||
struct cros_ec_command *ec_msg;
|
||||
|
@ -632,7 +634,7 @@ exit:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void cros_ec_xfer_high_pri_work(struct work_struct *work)
|
||||
static void cros_ec_xfer_high_pri_work(struct kthread_work *work)
|
||||
{
|
||||
struct cros_ec_xfer_work_params *params;
|
||||
|
||||
|
@ -644,12 +646,14 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
|
|||
struct cros_ec_command *ec_msg,
|
||||
cros_ec_xfer_fn_t fn)
|
||||
{
|
||||
struct cros_ec_xfer_work_params params;
|
||||
|
||||
INIT_WORK_ONSTACK(¶ms.work, cros_ec_xfer_high_pri_work);
|
||||
params.ec_dev = ec_dev;
|
||||
params.ec_msg = ec_msg;
|
||||
params.fn = fn;
|
||||
struct cros_ec_spi *ec_spi = ec_dev->priv;
|
||||
struct cros_ec_xfer_work_params params = {
|
||||
.work = KTHREAD_WORK_INIT(params.work,
|
||||
cros_ec_xfer_high_pri_work),
|
||||
.ec_dev = ec_dev,
|
||||
.ec_msg = ec_msg,
|
||||
.fn = fn,
|
||||
};
|
||||
|
||||
/*
|
||||
* This looks a bit ridiculous. Why do the work on a
|
||||
|
@ -660,9 +664,8 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
|
|||
* context switched out for too long and the EC giving up on
|
||||
* the transfer.
|
||||
*/
|
||||
queue_work(system_highpri_wq, ¶ms.work);
|
||||
flush_work(¶ms.work);
|
||||
destroy_work_on_stack(¶ms.work);
|
||||
kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work);
|
||||
kthread_flush_work(¶ms.work);
|
||||
|
||||
return params.ret;
|
||||
}
|
||||
|
@ -694,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
|
|||
ec_spi->end_of_msg_delay = val;
|
||||
}
|
||||
|
||||
static void cros_ec_spi_high_pri_release(void *worker)
|
||||
{
|
||||
kthread_destroy_worker(worker);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
int err;
|
||||
|
||||
ec_spi->high_pri_worker =
|
||||
kthread_create_worker(0, "cros_ec_spi_high_pri");
|
||||
|
||||
if (IS_ERR(ec_spi->high_pri_worker)) {
|
||||
err = PTR_ERR(ec_spi->high_pri_worker);
|
||||
dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release,
|
||||
ec_spi->high_pri_worker);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task,
|
||||
SCHED_FIFO, &sched_priority);
|
||||
if (err)
|
||||
dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cros_ec_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
|
@ -703,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi)
|
|||
|
||||
spi->bits_per_word = 8;
|
||||
spi->mode = SPI_MODE_0;
|
||||
spi->rt = true;
|
||||
err = spi_setup(spi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
@ -732,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
|
|||
|
||||
ec_spi->last_transfer_ns = ktime_get_ns();
|
||||
|
||||
err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = cros_ec_register(ec_dev);
|
||||
if (err) {
|
||||
dev_err(dev, "cannot register EC\n");
|
||||
|
@ -777,7 +819,7 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id);
|
|||
static struct spi_driver cros_ec_driver_spi = {
|
||||
.driver = {
|
||||
.name = "cros-ec-spi",
|
||||
.of_match_table = of_match_ptr(cros_ec_spi_of_match),
|
||||
.of_match_table = cros_ec_spi_of_match,
|
||||
.pm = &cros_ec_spi_pm_ops,
|
||||
},
|
||||
.probe = cros_ec_spi_probe,
|
||||
|
|
|
@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
|
|||
return a->mode;
|
||||
}
|
||||
|
||||
struct attribute_group cros_ec_attr_group = {
|
||||
static struct attribute_group cros_ec_attr_group = {
|
||||
.attrs = __ec_attrs,
|
||||
.is_visible = cros_ec_ctrl_visible,
|
||||
};
|
||||
|
|
|
@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
struct attribute_group cros_ec_vbc_attr_group = {
|
||||
static struct attribute_group cros_ec_vbc_attr_group = {
|
||||
.name = "vbc",
|
||||
.bin_attrs = cros_ec_vbc_bin_attrs,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config WILCO_EC
|
||||
tristate "ChromeOS Wilco Embedded Controller"
|
||||
depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC
|
||||
depends on ACPI && X86 && CROS_EC_LPC
|
||||
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
|
||||
|
@ -19,3 +19,19 @@ config WILCO_EC_DEBUGFS
|
|||
manipulation and allow for testing arbitrary commands. This
|
||||
interface is intended for debug only and will not be present
|
||||
on production devices.
|
||||
|
||||
config WILCO_EC_EVENTS
|
||||
tristate "Enable event forwarding from EC to userspace"
|
||||
depends on WILCO_EC
|
||||
help
|
||||
If you say Y here, you get support for the EC to send events
|
||||
(such as power state changes) to userspace. The EC sends the events
|
||||
over ACPI, and a driver queues up the events to be read by a
|
||||
userspace daemon from /dev/wilco_event using read() and poll().
|
||||
|
||||
config WILCO_EC_TELEMETRY
|
||||
tristate "Enable querying telemetry data from EC"
|
||||
depends on WILCO_EC
|
||||
help
|
||||
If you say Y here, you get support to query EC telemetry data from
|
||||
/dev/wilco_telem0 using write() and then read().
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
wilco_ec-objs := core.o mailbox.o
|
||||
wilco_ec-objs := core.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
|
||||
wilco_ec_events-objs := event.o
|
||||
obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o
|
||||
wilco_ec_telem-objs := telemetry.o
|
||||
obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o
|
||||
|
|
|
@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
|
|||
ec->dev = dev;
|
||||
mutex_init(&ec->mailbox_lock);
|
||||
|
||||
/* Largest data buffer size requirement is extended data response */
|
||||
ec->data_size = sizeof(struct wilco_ec_response) +
|
||||
EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
|
||||
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
|
||||
if (!ec->data_buffer)
|
||||
return -ENOMEM;
|
||||
|
@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev)
|
|||
goto unregister_debugfs;
|
||||
}
|
||||
|
||||
ret = wilco_ec_add_sysfs(ec);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to create sysfs entries: %d", ret);
|
||||
goto unregister_rtc;
|
||||
}
|
||||
|
||||
/* Register child device that will be found by the telemetry driver. */
|
||||
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
|
||||
PLATFORM_DEVID_AUTO,
|
||||
ec, sizeof(*ec));
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
remove_sysfs:
|
||||
wilco_ec_remove_sysfs(ec);
|
||||
unregister_rtc:
|
||||
platform_device_unregister(ec->rtc_pdev);
|
||||
unregister_debugfs:
|
||||
if (ec->debugfs_pdev)
|
||||
platform_device_unregister(ec->debugfs_pdev);
|
||||
|
@ -102,6 +120,8 @@ 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->rtc_pdev);
|
||||
if (ec->debugfs_pdev)
|
||||
platform_device_unregister(ec->debugfs_pdev);
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
|
||||
#define DRV_NAME "wilco-ec-debugfs"
|
||||
|
||||
/* The 256 raw bytes will take up more space when represented as a hex string */
|
||||
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4)
|
||||
/* The raw bytes will take up more space when represented as a hex string */
|
||||
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
|
||||
|
||||
struct wilco_ec_debugfs {
|
||||
struct wilco_ec_device *ec;
|
||||
struct dentry *dir;
|
||||
size_t response_size;
|
||||
u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED];
|
||||
u8 raw_data[EC_MAILBOX_DATA_SIZE];
|
||||
u8 formatted_data[FORMATTED_BUFFER_SIZE];
|
||||
};
|
||||
static struct wilco_ec_debugfs *debug_info;
|
||||
|
@ -124,12 +124,6 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf,
|
|||
msg.response_data = debug_info->raw_data;
|
||||
msg.response_size = EC_MAILBOX_DATA_SIZE;
|
||||
|
||||
/* Telemetry commands use extended response data */
|
||||
if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) {
|
||||
msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA;
|
||||
msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
}
|
||||
|
||||
ret = wilco_ec_mailbox(debug_info->ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
|
@ -0,0 +1,581 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ACPI event handling for Wilco Embedded Controller
|
||||
*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* The Wilco Embedded Controller can create custom events that
|
||||
* are not handled as standard ACPI objects. These events can
|
||||
* contain information about changes in EC controlled features,
|
||||
* such as errors and events in the dock or display. For example,
|
||||
* an event is triggered if the dock is plugged into a display
|
||||
* incorrectly. These events are needed for telemetry and
|
||||
* diagnostics reasons, and for possibly alerting the user.
|
||||
|
||||
* These events are triggered by the EC with an ACPI Notify(0x90),
|
||||
* and then the BIOS reads the event buffer from EC RAM via an
|
||||
* ACPI method. When the OS receives these events via ACPI,
|
||||
* it passes them along to this driver. The events are put into
|
||||
* a queue which can be read by a userspace daemon via a char device
|
||||
* that implements read() and poll(). The event queue acts as a
|
||||
* circular buffer of size 64, so if there are no userspace consumers
|
||||
* the kernel will not run out of memory. The char device will appear at
|
||||
* /dev/wilco_event{n}, where n is some small non-negative integer,
|
||||
* starting from 0. Standard ACPI events such as the battery getting
|
||||
* plugged/unplugged can also come through this path, but they are
|
||||
* dealt with via other paths, and are ignored here.
|
||||
|
||||
* To test, you can tail the binary data with
|
||||
* $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"'
|
||||
* and then create an event by plugging/unplugging the battery.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
/* ACPI Notify event code indicating event data is available. */
|
||||
#define EC_ACPI_NOTIFY_EVENT 0x90
|
||||
/* ACPI Method to execute to retrieve event data buffer from the EC. */
|
||||
#define EC_ACPI_GET_EVENT "QSET"
|
||||
/* Maximum number of words in event data returned by the EC. */
|
||||
#define EC_ACPI_MAX_EVENT_WORDS 6
|
||||
#define EC_ACPI_MAX_EVENT_SIZE \
|
||||
(sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16))
|
||||
|
||||
/* Node will appear in /dev/EVENT_DEV_NAME */
|
||||
#define EVENT_DEV_NAME "wilco_event"
|
||||
#define EVENT_CLASS_NAME EVENT_DEV_NAME
|
||||
#define DRV_NAME EVENT_DEV_NAME
|
||||
#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d")
|
||||
static struct class event_class = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = EVENT_CLASS_NAME,
|
||||
};
|
||||
|
||||
/* Keep track of all the device numbers used. */
|
||||
#define EVENT_MAX_DEV 128
|
||||
static int event_major;
|
||||
static DEFINE_IDA(event_ida);
|
||||
|
||||
/* Size of circular queue of events. */
|
||||
#define MAX_NUM_EVENTS 64
|
||||
|
||||
/**
|
||||
* struct ec_event - Extended event returned by the EC.
|
||||
* @size: Number of 16bit words in structure after the size word.
|
||||
* @type: Extended event type, meaningless for us.
|
||||
* @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS.
|
||||
*/
|
||||
struct ec_event {
|
||||
u16 size;
|
||||
u16 type;
|
||||
u16 event[0];
|
||||
} __packed;
|
||||
|
||||
#define ec_event_num_words(ev) (ev->size - 1)
|
||||
#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16)))
|
||||
|
||||
/**
|
||||
* struct ec_event_queue - Circular queue for events.
|
||||
* @capacity: Number of elements the queue can hold.
|
||||
* @head: Next index to write to.
|
||||
* @tail: Next index to read from.
|
||||
* @entries: Array of events.
|
||||
*/
|
||||
struct ec_event_queue {
|
||||
int capacity;
|
||||
int head;
|
||||
int tail;
|
||||
struct ec_event *entries[0];
|
||||
};
|
||||
|
||||
/* Maximum number of events to store in ec_event_queue */
|
||||
static int queue_size = 64;
|
||||
module_param(queue_size, int, 0644);
|
||||
|
||||
static struct ec_event_queue *event_queue_new(int capacity)
|
||||
{
|
||||
struct ec_event_queue *q;
|
||||
|
||||
q = kzalloc(struct_size(q, entries, capacity), GFP_KERNEL);
|
||||
if (!q)
|
||||
return NULL;
|
||||
|
||||
q->capacity = capacity;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static inline bool event_queue_empty(struct ec_event_queue *q)
|
||||
{
|
||||
/* head==tail when both full and empty, but head==NULL when empty */
|
||||
return q->head == q->tail && !q->entries[q->head];
|
||||
}
|
||||
|
||||
static inline bool event_queue_full(struct ec_event_queue *q)
|
||||
{
|
||||
/* head==tail when both full and empty, but head!=NULL when full */
|
||||
return q->head == q->tail && q->entries[q->head];
|
||||
}
|
||||
|
||||
static struct ec_event *event_queue_pop(struct ec_event_queue *q)
|
||||
{
|
||||
struct ec_event *ev;
|
||||
|
||||
if (event_queue_empty(q))
|
||||
return NULL;
|
||||
|
||||
ev = q->entries[q->tail];
|
||||
q->entries[q->tail] = NULL;
|
||||
q->tail = (q->tail + 1) % q->capacity;
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
/*
|
||||
* If full, overwrite the oldest event and return it so the caller
|
||||
* can kfree it. If not full, return NULL.
|
||||
*/
|
||||
static struct ec_event *event_queue_push(struct ec_event_queue *q,
|
||||
struct ec_event *ev)
|
||||
{
|
||||
struct ec_event *popped = NULL;
|
||||
|
||||
if (event_queue_full(q))
|
||||
popped = event_queue_pop(q);
|
||||
q->entries[q->head] = ev;
|
||||
q->head = (q->head + 1) % q->capacity;
|
||||
|
||||
return popped;
|
||||
}
|
||||
|
||||
static void event_queue_free(struct ec_event_queue *q)
|
||||
{
|
||||
struct ec_event *event;
|
||||
|
||||
while ((event = event_queue_pop(q)) != NULL)
|
||||
kfree(event);
|
||||
|
||||
kfree(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* struct event_device_data - Data for a Wilco EC device that responds to ACPI.
|
||||
* @events: Circular queue of EC events to be provided to userspace.
|
||||
* @queue_lock: Protect the queue from simultaneous read/writes.
|
||||
* @wq: Wait queue to notify processes when events are available or the
|
||||
* device has been removed.
|
||||
* @cdev: Char dev that userspace reads() and polls() from.
|
||||
* @dev: Device associated with the %cdev.
|
||||
* @exist: Has the device been not been removed? Once a device has been removed,
|
||||
* writes, reads, and new opens will fail.
|
||||
* @available: Guarantee only one client can open() file and read from queue.
|
||||
*
|
||||
* There will be one of these structs for each ACPI device registered. This data
|
||||
* is the queue of events received from ACPI that still need to be read from
|
||||
* userspace, the device and char device that userspace is using, a wait queue
|
||||
* used to notify different threads when something has changed, plus a flag
|
||||
* on whether the ACPI device has been removed.
|
||||
*/
|
||||
struct event_device_data {
|
||||
struct ec_event_queue *events;
|
||||
spinlock_t queue_lock;
|
||||
wait_queue_head_t wq;
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
bool exist;
|
||||
atomic_t available;
|
||||
};
|
||||
|
||||
/**
|
||||
* enqueue_events() - Place EC events in queue to be read by userspace.
|
||||
* @adev: Device the events came from.
|
||||
* @buf: Buffer of event data.
|
||||
* @length: Length of event data buffer.
|
||||
*
|
||||
* %buf contains a number of ec_event's, packed one after the other.
|
||||
* Each ec_event is of variable length. Start with the first event, copy it
|
||||
* into a persistent ec_event, store that entry in the queue, move on
|
||||
* to the next ec_event in buf, and repeat.
|
||||
*
|
||||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length)
|
||||
{
|
||||
struct event_device_data *dev_data = adev->driver_data;
|
||||
struct ec_event *event, *queue_event, *old_event;
|
||||
size_t num_words, event_size;
|
||||
u32 offset = 0;
|
||||
|
||||
while (offset < length) {
|
||||
event = (struct ec_event *)(buf + offset);
|
||||
|
||||
num_words = ec_event_num_words(event);
|
||||
event_size = ec_event_size(event);
|
||||
if (num_words > EC_ACPI_MAX_EVENT_WORDS) {
|
||||
dev_err(&adev->dev, "Too many event words: %zu > %d\n",
|
||||
num_words, EC_ACPI_MAX_EVENT_WORDS);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* Ensure event does not overflow the available buffer */
|
||||
if ((offset + event_size) > length) {
|
||||
dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n",
|
||||
offset + event_size, length);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* Point to the next event in the buffer */
|
||||
offset += event_size;
|
||||
|
||||
/* Copy event into the queue */
|
||||
queue_event = kmemdup(event, event_size, GFP_KERNEL);
|
||||
if (!queue_event)
|
||||
return -ENOMEM;
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
old_event = event_queue_push(dev_data->events, queue_event);
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
kfree(old_event);
|
||||
wake_up_interruptible(&dev_data->wq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* event_device_notify() - Callback when EC generates an event over ACPI.
|
||||
* @adev: The device that the event is coming from.
|
||||
* @value: Value passed to Notify() in ACPI.
|
||||
*
|
||||
* This function will read the events from the device and enqueue them.
|
||||
*/
|
||||
static void event_device_notify(struct acpi_device *adev, u32 value)
|
||||
{
|
||||
struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
if (value != EC_ACPI_NOTIFY_EVENT) {
|
||||
dev_err(&adev->dev, "Invalid event: 0x%08x\n", value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Execute ACPI method to get event data buffer. */
|
||||
status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT,
|
||||
NULL, &event_buffer);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&adev->dev, "Error executing ACPI method %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
return;
|
||||
}
|
||||
|
||||
obj = (union acpi_object *)event_buffer.pointer;
|
||||
if (!obj) {
|
||||
dev_err(&adev->dev, "Nothing returned from %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
return;
|
||||
}
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
dev_err(&adev->dev, "Invalid object returned from %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
kfree(obj);
|
||||
return;
|
||||
}
|
||||
if (obj->buffer.length < sizeof(struct ec_event)) {
|
||||
dev_err(&adev->dev, "Invalid buffer length %d from %s()\n",
|
||||
obj->buffer.length, EC_ACPI_GET_EVENT);
|
||||
kfree(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
enqueue_events(adev, obj->buffer.pointer, obj->buffer.length);
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
static int event_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(inode->i_cdev, struct event_device_data, cdev);
|
||||
if (!dev_data->exist)
|
||||
return -ENODEV;
|
||||
|
||||
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
|
||||
return -EBUSY;
|
||||
|
||||
/* Increase refcount on device so dev_data is not freed */
|
||||
get_device(&dev_data->dev);
|
||||
stream_open(inode, filp);
|
||||
filp->private_data = dev_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __poll_t event_poll(struct file *filp, poll_table *wait)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
__poll_t mask = 0;
|
||||
|
||||
poll_wait(filp, &dev_data->wq, wait);
|
||||
if (!dev_data->exist)
|
||||
return EPOLLHUP;
|
||||
if (!event_queue_empty(dev_data->events))
|
||||
mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI;
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* event_read() - Callback for passing event data to userspace via read().
|
||||
* @filp: The file we are reading from.
|
||||
* @buf: Pointer to userspace buffer to fill with one event.
|
||||
* @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE.
|
||||
* @pos: File position pointer, irrelevant since we don't support seeking.
|
||||
*
|
||||
* Removes the first event from the queue, places it in the passed buffer.
|
||||
*
|
||||
* If there are no events in the the queue, then one of two things happens,
|
||||
* depending on if the file was opened in nonblocking mode: If in nonblocking
|
||||
* mode, then return -EAGAIN to say there's no data. If in blocking mode, then
|
||||
* block until an event is available.
|
||||
*
|
||||
* Return: Number of bytes placed in buffer, negative error code on failure.
|
||||
*/
|
||||
static ssize_t event_read(struct file *filp, char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
struct ec_event *event;
|
||||
ssize_t n_bytes_written = 0;
|
||||
int err;
|
||||
|
||||
/* We only will give them the entire event at once */
|
||||
if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
while (event_queue_empty(dev_data->events)) {
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
if (filp->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
err = wait_event_interruptible(dev_data->wq,
|
||||
!event_queue_empty(dev_data->events) ||
|
||||
!dev_data->exist);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Device was removed as we waited? */
|
||||
if (!dev_data->exist)
|
||||
return -ENODEV;
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
}
|
||||
event = event_queue_pop(dev_data->events);
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
n_bytes_written = ec_event_size(event);
|
||||
if (copy_to_user(buf, event, n_bytes_written))
|
||||
n_bytes_written = -EFAULT;
|
||||
kfree(event);
|
||||
|
||||
return n_bytes_written;
|
||||
}
|
||||
|
||||
static int event_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
|
||||
atomic_set(&dev_data->available, 1);
|
||||
put_device(&dev_data->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations event_fops = {
|
||||
.open = event_open,
|
||||
.poll = event_poll,
|
||||
.read = event_read,
|
||||
.release = event_release,
|
||||
.llseek = no_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/**
|
||||
* free_device_data() - Callback to free the event_device_data structure.
|
||||
* @d: The device embedded in our device data, which we have been ref counting.
|
||||
*
|
||||
* This is called only after event_device_remove() has been called and all
|
||||
* userspace programs have called event_release() on all the open file
|
||||
* descriptors.
|
||||
*/
|
||||
static void free_device_data(struct device *d)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(d, struct event_device_data, dev);
|
||||
event_queue_free(dev_data->events);
|
||||
kfree(dev_data);
|
||||
}
|
||||
|
||||
static void hangup_device(struct event_device_data *dev_data)
|
||||
{
|
||||
dev_data->exist = false;
|
||||
/* Wake up the waiting processes so they can close. */
|
||||
wake_up_interruptible(&dev_data->wq);
|
||||
put_device(&dev_data->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* event_device_add() - Callback when creating a new device.
|
||||
* @adev: ACPI device that we will be receiving events from.
|
||||
*
|
||||
* This finds a free minor number for the device, allocates and initializes
|
||||
* some device data, and creates a new device and char dev node.
|
||||
*
|
||||
* The device data is freed in free_device_data(), which is called when
|
||||
* %dev_data->dev is release()ed. This happens after all references to
|
||||
* %dev_data->dev are dropped, which happens once both event_device_remove()
|
||||
* has been called and every open()ed file descriptor has been release()ed.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int event_device_add(struct acpi_device *adev)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
int error, minor;
|
||||
|
||||
minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL);
|
||||
if (minor < 0) {
|
||||
error = minor;
|
||||
dev_err(&adev->dev, "Failed to find minor number: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
|
||||
if (!dev_data) {
|
||||
error = -ENOMEM;
|
||||
goto free_minor;
|
||||
}
|
||||
|
||||
/* Initialize the device data. */
|
||||
adev->driver_data = dev_data;
|
||||
dev_data->events = event_queue_new(queue_size);
|
||||
if (!dev_data->events) {
|
||||
kfree(dev_data);
|
||||
error = -ENOMEM;
|
||||
goto free_minor;
|
||||
}
|
||||
spin_lock_init(&dev_data->queue_lock);
|
||||
init_waitqueue_head(&dev_data->wq);
|
||||
dev_data->exist = true;
|
||||
atomic_set(&dev_data->available, 1);
|
||||
|
||||
/* Initialize the device. */
|
||||
dev_data->dev.devt = MKDEV(event_major, minor);
|
||||
dev_data->dev.class = &event_class;
|
||||
dev_data->dev.release = free_device_data;
|
||||
dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor);
|
||||
device_initialize(&dev_data->dev);
|
||||
|
||||
/* Initialize the character device, and add it to userspace. */
|
||||
cdev_init(&dev_data->cdev, &event_fops);
|
||||
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
|
||||
if (error)
|
||||
goto free_dev_data;
|
||||
|
||||
return 0;
|
||||
|
||||
free_dev_data:
|
||||
hangup_device(dev_data);
|
||||
free_minor:
|
||||
ida_simple_remove(&event_ida, minor);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int event_device_remove(struct acpi_device *adev)
|
||||
{
|
||||
struct event_device_data *dev_data = adev->driver_data;
|
||||
|
||||
cdev_device_del(&dev_data->cdev, &dev_data->dev);
|
||||
ida_simple_remove(&event_ida, MINOR(dev_data->dev.devt));
|
||||
hangup_device(dev_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id event_acpi_ids[] = {
|
||||
{ "GOOG000D", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, event_acpi_ids);
|
||||
|
||||
static struct acpi_driver event_driver = {
|
||||
.name = DRV_NAME,
|
||||
.class = DRV_NAME,
|
||||
.ids = event_acpi_ids,
|
||||
.ops = {
|
||||
.add = event_device_add,
|
||||
.notify = event_device_notify,
|
||||
.remove = event_device_remove,
|
||||
},
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init event_module_init(void)
|
||||
{
|
||||
dev_t dev_num = 0;
|
||||
int ret;
|
||||
|
||||
ret = class_register(&event_class);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Request device numbers, starting with minor=0. Save the major num. */
|
||||
ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
|
||||
goto destroy_class;
|
||||
}
|
||||
event_major = MAJOR(dev_num);
|
||||
|
||||
ret = acpi_bus_register_driver(&event_driver);
|
||||
if (ret < 0) {
|
||||
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
|
||||
goto unregister_region;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_region:
|
||||
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
|
||||
destroy_class:
|
||||
class_unregister(&event_class);
|
||||
ida_destroy(&event_ida);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit event_module_exit(void)
|
||||
{
|
||||
acpi_bus_unregister_driver(&event_driver);
|
||||
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
|
||||
class_unregister(&event_class);
|
||||
ida_destroy(&event_ida);
|
||||
}
|
||||
|
||||
module_init(event_module_init);
|
||||
module_exit(event_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
|
||||
MODULE_DESCRIPTION("Wilco EC ACPI event driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
|
@ -119,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
|||
struct wilco_ec_response *rs;
|
||||
u8 checksum;
|
||||
u8 flag;
|
||||
size_t size;
|
||||
|
||||
/* Write request header, then data */
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
|
||||
|
@ -148,21 +147,11 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
|||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* The EC always returns either EC_MAILBOX_DATA_SIZE or
|
||||
* EC_MAILBOX_DATA_SIZE_EXTENDED bytes of data, so we need to
|
||||
* calculate the checksum on **all** of this data, even if we
|
||||
* won't use all of it.
|
||||
*/
|
||||
if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA)
|
||||
size = EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
else
|
||||
size = EC_MAILBOX_DATA_SIZE;
|
||||
|
||||
/* Read back response */
|
||||
rs = ec->data_buffer;
|
||||
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
|
||||
sizeof(*rs) + size, (u8 *)rs);
|
||||
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
|
||||
(u8 *)rs);
|
||||
if (checksum) {
|
||||
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
|
||||
return -EBADMSG;
|
||||
|
@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
|||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (rs->data_size != size) {
|
||||
dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
|
||||
rs->data_size, size);
|
||||
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
|
||||
dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
|
||||
rs->data_size, EC_MAILBOX_DATA_SIZE);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*/
|
||||
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/unaligned/le_memmove.h>
|
||||
|
||||
/* Operation code; what the EC should do with the property */
|
||||
enum ec_property_op {
|
||||
EC_OP_GET = 0,
|
||||
EC_OP_SET = 1,
|
||||
};
|
||||
|
||||
struct ec_property_request {
|
||||
u8 op; /* One of enum ec_property_op */
|
||||
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
|
||||
u8 length;
|
||||
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
|
||||
} __packed;
|
||||
|
||||
struct ec_property_response {
|
||||
u8 reserved[2];
|
||||
u8 op; /* One of enum ec_property_op */
|
||||
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
|
||||
u8 length;
|
||||
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
|
||||
} __packed;
|
||||
|
||||
static int send_property_msg(struct wilco_ec_device *ec,
|
||||
struct ec_property_request *rq,
|
||||
struct ec_property_response *rs)
|
||||
{
|
||||
struct wilco_ec_message ec_msg;
|
||||
int ret;
|
||||
|
||||
memset(&ec_msg, 0, sizeof(ec_msg));
|
||||
ec_msg.type = WILCO_EC_MSG_PROPERTY;
|
||||
ec_msg.request_data = rq;
|
||||
ec_msg.request_size = sizeof(*rq);
|
||||
ec_msg.response_data = rs;
|
||||
ec_msg.response_size = sizeof(*rs);
|
||||
|
||||
ret = wilco_ec_mailbox(ec, &ec_msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rs->op != rq->op)
|
||||
return -EBADMSG;
|
||||
if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id)))
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wilco_ec_get_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg)
|
||||
{
|
||||
struct ec_property_request rq;
|
||||
struct ec_property_response rs;
|
||||
int ret;
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.op = EC_OP_GET;
|
||||
put_unaligned_le32(prop_msg->property_id, rq.property_id);
|
||||
|
||||
ret = send_property_msg(ec, &rq, &rs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prop_msg->length = rs.length;
|
||||
memcpy(prop_msg->data, rs.data, rs.length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_get_property);
|
||||
|
||||
int wilco_ec_set_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg)
|
||||
{
|
||||
struct ec_property_request rq;
|
||||
struct ec_property_response rs;
|
||||
int ret;
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.op = EC_OP_SET;
|
||||
put_unaligned_le32(prop_msg->property_id, rq.property_id);
|
||||
rq.length = prop_msg->length;
|
||||
memcpy(rq.data, prop_msg->data, prop_msg->length);
|
||||
|
||||
ret = send_property_msg(ec, &rq, &rs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rs.length != prop_msg->length)
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_set_property);
|
||||
|
||||
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 *val)
|
||||
{
|
||||
struct wilco_ec_property_msg msg;
|
||||
int ret;
|
||||
|
||||
msg.property_id = property_id;
|
||||
|
||||
ret = wilco_ec_get_property(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (msg.length != 1)
|
||||
return -EBADMSG;
|
||||
|
||||
*val = msg.data[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property);
|
||||
|
||||
int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 val)
|
||||
{
|
||||
struct wilco_ec_property_msg msg;
|
||||
|
||||
msg.property_id = property_id;
|
||||
msg.data[0] = val;
|
||||
msg.length = 1;
|
||||
|
||||
return wilco_ec_set_property(ec, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property);
|
|
@ -0,0 +1,156 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Sysfs properties to view and modify EC-controlled features on Wilco devices.
|
||||
* The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
|
||||
*
|
||||
* See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
|
||||
*/
|
||||
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define CMD_KB_CMOS 0x7C
|
||||
#define SUB_CMD_KB_CMOS_AUTO_ON 0x03
|
||||
|
||||
struct boot_on_ac_request {
|
||||
u8 cmd; /* Always CMD_KB_CMOS */
|
||||
u8 reserved1;
|
||||
u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */
|
||||
u8 reserved3to5[3];
|
||||
u8 val; /* Either 0 or 1 */
|
||||
u8 reserved7;
|
||||
} __packed;
|
||||
|
||||
#define CMD_EC_INFO 0x38
|
||||
enum get_ec_info_op {
|
||||
CMD_GET_EC_LABEL = 0,
|
||||
CMD_GET_EC_REV = 1,
|
||||
CMD_GET_EC_MODEL = 2,
|
||||
CMD_GET_EC_BUILD_DATE = 3,
|
||||
};
|
||||
|
||||
struct get_ec_info_req {
|
||||
u8 cmd; /* Always CMD_EC_INFO */
|
||||
u8 reserved;
|
||||
u8 op; /* One of enum get_ec_info_op */
|
||||
} __packed;
|
||||
|
||||
struct get_ec_info_resp {
|
||||
u8 reserved[2];
|
||||
char value[9]; /* __nonstring: might not be null terminated */
|
||||
} __packed;
|
||||
|
||||
static ssize_t boot_on_ac_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct wilco_ec_device *ec = dev_get_drvdata(dev);
|
||||
struct boot_on_ac_request rq;
|
||||
struct wilco_ec_message msg;
|
||||
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_KB_CMOS;
|
||||
rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
|
||||
rq.val = val;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.type = WILCO_EC_MSG_LEGACY;
|
||||
msg.request_data = &rq;
|
||||
msg.request_size = sizeof(rq);
|
||||
ret = wilco_ec_mailbox(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_WO(boot_on_ac);
|
||||
|
||||
static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
|
||||
{
|
||||
struct wilco_ec_device *ec = dev_get_drvdata(dev);
|
||||
struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
|
||||
struct get_ec_info_resp resp;
|
||||
int ret;
|
||||
|
||||
struct wilco_ec_message msg = {
|
||||
.type = WILCO_EC_MSG_LEGACY,
|
||||
.request_data = &req,
|
||||
.request_size = sizeof(req),
|
||||
.response_data = &resp,
|
||||
.response_size = sizeof(resp),
|
||||
};
|
||||
|
||||
ret = wilco_ec_mailbox(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value),
|
||||
(char *)&resp.value);
|
||||
}
|
||||
|
||||
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_LABEL);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(version);
|
||||
|
||||
static ssize_t build_revision_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_REV);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(build_revision);
|
||||
|
||||
static ssize_t build_date_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(build_date);
|
||||
|
||||
static ssize_t model_number_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_MODEL);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(model_number);
|
||||
|
||||
|
||||
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_version.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group wilco_dev_attr_group = {
|
||||
.attrs = wilco_dev_attrs,
|
||||
};
|
||||
|
||||
int wilco_ec_add_sysfs(struct wilco_ec_device *ec)
|
||||
{
|
||||
return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
|
||||
}
|
||||
|
||||
void wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
|
||||
{
|
||||
sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Telemetry communication for Wilco EC
|
||||
*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* The Wilco Embedded Controller is able to send telemetry data
|
||||
* which is useful for enterprise applications. A daemon running on
|
||||
* 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
|
||||
* being transmitted to the EC. The response is passed directly
|
||||
* back to the reader with no modification.
|
||||
*
|
||||
* The character device will appear as /dev/wilco_telemN, where N
|
||||
* is some small non-negative integer, starting with 0. Only one
|
||||
* process may have the file descriptor open at a time. The calling
|
||||
* userspace program needs to keep the device file descriptor open
|
||||
* between the calls to write() and read() in order to preserve the
|
||||
* response. Up to 32 bytes will be available for reading.
|
||||
*
|
||||
* For testing purposes, try requesting the EC's firmware build
|
||||
* date, by sending the WILCO_EC_TELEM_GET_VERSION command with
|
||||
* argument index=3. i.e. write [0x38, 0x00, 0x03]
|
||||
* to the device node. An ASCII string of the build date is
|
||||
* returned.
|
||||
*/
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#define TELEM_DEV_NAME "wilco_telem"
|
||||
#define TELEM_CLASS_NAME TELEM_DEV_NAME
|
||||
#define DRV_NAME TELEM_DEV_NAME
|
||||
#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")
|
||||
static struct class telem_class = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = TELEM_CLASS_NAME,
|
||||
};
|
||||
|
||||
/* Keep track of all the device numbers used. */
|
||||
#define TELEM_MAX_DEV 128
|
||||
static int telem_major;
|
||||
static DEFINE_IDA(telem_ida);
|
||||
|
||||
/* EC telemetry command codes */
|
||||
#define WILCO_EC_TELEM_GET_LOG 0x99
|
||||
#define WILCO_EC_TELEM_GET_VERSION 0x38
|
||||
#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E
|
||||
#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA
|
||||
#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 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.
|
||||
*/
|
||||
|
||||
struct telem_args_get_log {
|
||||
u8 log_type;
|
||||
u8 log_index;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Get a piece of info about the EC firmware version:
|
||||
* 0 = label
|
||||
* 1 = svn_rev
|
||||
* 2 = model_no
|
||||
* 3 = build_date
|
||||
* 4 = frio_version
|
||||
*/
|
||||
struct telem_args_get_version {
|
||||
u8 index;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_fan_info {
|
||||
u8 command;
|
||||
u8 fan_number;
|
||||
u8 arg;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_diag_info {
|
||||
u8 type;
|
||||
u8 sub_type;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_temp_info {
|
||||
u8 command;
|
||||
u8 index;
|
||||
u8 field;
|
||||
u8 zone;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_temp_read {
|
||||
u8 sensor_index;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_batt_ext_info {
|
||||
u8 var_args[5];
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* check_telem_request() - Ensure that a request from userspace is valid.
|
||||
* @rq: Request buffer copied from userspace.
|
||||
* @size: Number of bytes copied from userspace.
|
||||
*
|
||||
* Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
|
||||
* -EMSGSIZE if the request is too long.
|
||||
*
|
||||
* 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.
|
||||
* 3. The request only contains the necessary data for the header and arguments.
|
||||
*/
|
||||
static int check_telem_request(struct wilco_ec_telem_request *rq,
|
||||
size_t size)
|
||||
{
|
||||
size_t max_size = offsetof(struct wilco_ec_telem_request, args);
|
||||
|
||||
if (rq->reserved)
|
||||
return -EINVAL;
|
||||
|
||||
switch (rq->command) {
|
||||
case WILCO_EC_TELEM_GET_LOG:
|
||||
max_size += sizeof(struct telem_args_get_log);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_VERSION:
|
||||
max_size += sizeof(struct telem_args_get_version);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_FAN_INFO:
|
||||
max_size += sizeof(struct telem_args_get_fan_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_DIAG_INFO:
|
||||
max_size += sizeof(struct telem_args_get_diag_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_TEMP_INFO:
|
||||
max_size += sizeof(struct telem_args_get_temp_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_TEMP_READ:
|
||||
max_size += sizeof(struct telem_args_get_temp_read);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
|
||||
max_size += sizeof(struct telem_args_get_batt_ext_info);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return (size <= max_size) ? 0 : -EMSGSIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct telem_device_data - Data for a Wilco EC device that queries telemetry.
|
||||
* @cdev: Char dev that userspace reads and polls from.
|
||||
* @dev: Device associated with the %cdev.
|
||||
* @ec: Wilco EC that we will be communicating with using the mailbox interface.
|
||||
* @available: Boolean of if the device can be opened.
|
||||
*/
|
||||
struct telem_device_data {
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
struct wilco_ec_device *ec;
|
||||
atomic_t available;
|
||||
};
|
||||
|
||||
#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
|
||||
|
||||
/**
|
||||
* struct telem_session_data - Data that exists between open() and release().
|
||||
* @dev_data: Pointer to get back to the device data and EC.
|
||||
* @request: Command and arguments sent to EC.
|
||||
* @response: Response buffer of data from EC.
|
||||
* @has_msg: Is there data available to read from a previous write?
|
||||
*/
|
||||
struct telem_session_data {
|
||||
struct telem_device_data *dev_data;
|
||||
struct wilco_ec_telem_request request;
|
||||
u8 response[TELEM_RESPONSE_SIZE];
|
||||
bool has_msg;
|
||||
};
|
||||
|
||||
/**
|
||||
* telem_open() - Callback for when the device node is opened.
|
||||
* @inode: inode for this char device node.
|
||||
* @filp: file for this char device node.
|
||||
*
|
||||
* We need to ensure that after writing a command to the device,
|
||||
* the same userspace process reads the corresponding result.
|
||||
* Therefore, we increment a refcount on opening the device, so that
|
||||
* only one process can communicate with the EC at a time.
|
||||
*
|
||||
* Return: 0 on success, or negative error code on failure.
|
||||
*/
|
||||
static int telem_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
struct telem_session_data *sess_data;
|
||||
|
||||
/* Ensure device isn't already open */
|
||||
dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
|
||||
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
|
||||
return -EBUSY;
|
||||
|
||||
get_device(&dev_data->dev);
|
||||
|
||||
sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
|
||||
if (!sess_data) {
|
||||
atomic_set(&dev_data->available, 1);
|
||||
return -ENOMEM;
|
||||
}
|
||||
sess_data->dev_data = dev_data;
|
||||
sess_data->has_msg = false;
|
||||
|
||||
nonseekable_open(inode, filp);
|
||||
filp->private_data = sess_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t telem_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
struct wilco_ec_message msg = {};
|
||||
int ret;
|
||||
|
||||
if (count > sizeof(sess_data->request))
|
||||
return -EMSGSIZE;
|
||||
if (copy_from_user(&sess_data->request, buf, count))
|
||||
return -EFAULT;
|
||||
ret = check_telem_request(&sess_data->request, count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
memset(sess_data->response, 0, sizeof(sess_data->response));
|
||||
msg.type = WILCO_EC_MSG_TELEMETRY;
|
||||
msg.request_data = &sess_data->request;
|
||||
msg.request_size = sizeof(sess_data->request);
|
||||
msg.response_data = sess_data->response;
|
||||
msg.response_size = sizeof(sess_data->response);
|
||||
|
||||
ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != sizeof(sess_data->response))
|
||||
return -EMSGSIZE;
|
||||
|
||||
sess_data->has_msg = true;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
|
||||
if (!sess_data->has_msg)
|
||||
return -ENODATA;
|
||||
if (count > sizeof(sess_data->response))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_to_user(buf, sess_data->response, count))
|
||||
return -EFAULT;
|
||||
|
||||
sess_data->has_msg = false;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int telem_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
|
||||
atomic_set(&sess_data->dev_data->available, 1);
|
||||
put_device(&sess_data->dev_data->dev);
|
||||
kfree(sess_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations telem_fops = {
|
||||
.open = telem_open,
|
||||
.write = telem_write,
|
||||
.read = telem_read,
|
||||
.release = telem_release,
|
||||
.llseek = no_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/**
|
||||
* telem_device_free() - Callback to free the telem_device_data structure.
|
||||
* @d: The device embedded in our device data, which we have been ref counting.
|
||||
*
|
||||
* Once all open file descriptors are closed and the device has been removed,
|
||||
* the refcount of the device will fall to 0 and this will be called.
|
||||
*/
|
||||
static void telem_device_free(struct device *d)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(d, struct telem_device_data, dev);
|
||||
kfree(dev_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* telem_device_probe() - Callback when creating a new device.
|
||||
* @pdev: platform device that we will be receiving telems from.
|
||||
*
|
||||
* This finds a free minor number for the device, allocates and initializes
|
||||
* some device data, and creates a new device and char dev node.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int telem_device_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
int error, minor;
|
||||
|
||||
/* Get the next available device number */
|
||||
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);
|
||||
return error;
|
||||
}
|
||||
|
||||
dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
|
||||
if (!dev_data) {
|
||||
ida_simple_remove(&telem_ida, minor);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Initialize the device data */
|
||||
dev_data->ec = dev_get_platdata(&pdev->dev);
|
||||
atomic_set(&dev_data->available, 1);
|
||||
platform_set_drvdata(pdev, dev_data);
|
||||
|
||||
/* Initialize the device */
|
||||
dev_data->dev.devt = MKDEV(telem_major, minor);
|
||||
dev_data->dev.class = &telem_class;
|
||||
dev_data->dev.release = telem_device_free;
|
||||
dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
|
||||
device_initialize(&dev_data->dev);
|
||||
|
||||
/* Initialize the character device and add it to userspace */;
|
||||
cdev_init(&dev_data->cdev, &telem_fops);
|
||||
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
|
||||
if (error) {
|
||||
put_device(&dev_data->dev);
|
||||
ida_simple_remove(&telem_ida, minor);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver telem_driver = {
|
||||
.probe = telem_device_probe,
|
||||
.remove = telem_device_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init telem_module_init(void)
|
||||
{
|
||||
dev_t dev_num = 0;
|
||||
int ret;
|
||||
|
||||
ret = class_register(&telem_class);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed registering class: %d", 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);
|
||||
goto destroy_class;
|
||||
}
|
||||
telem_major = MAJOR(dev_num);
|
||||
|
||||
ret = platform_driver_register(&telem_driver);
|
||||
if (ret < 0) {
|
||||
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
|
||||
goto unregister_region;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_region:
|
||||
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
|
||||
destroy_class:
|
||||
class_unregister(&telem_class);
|
||||
ida_destroy(&telem_ida);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit telem_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&telem_driver);
|
||||
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
|
||||
class_unregister(&telem_class);
|
||||
ida_destroy(&telem_ida);
|
||||
}
|
||||
|
||||
module_init(telem_module_init);
|
||||
module_exit(telem_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
|
||||
MODULE_DESCRIPTION("Wilco EC telemetry driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
|
@ -155,6 +155,7 @@ struct cros_ec_device {
|
|||
struct ec_response_get_next_event_v1 event_data;
|
||||
int event_size;
|
||||
u32 host_event_wake_mask;
|
||||
u32 last_resume_result;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -13,12 +13,9 @@
|
|||
|
||||
/* Message flags for using the mailbox() interface */
|
||||
#define WILCO_EC_FLAG_NO_RESPONSE BIT(0) /* EC does not respond */
|
||||
#define WILCO_EC_FLAG_EXTENDED_DATA BIT(1) /* EC returns 256 data bytes */
|
||||
|
||||
/* Normal commands have a maximum 32 bytes of data */
|
||||
#define EC_MAILBOX_DATA_SIZE 32
|
||||
/* Extended commands have 256 bytes of response data */
|
||||
#define EC_MAILBOX_DATA_SIZE_EXTENDED 256
|
||||
|
||||
/**
|
||||
* struct wilco_ec_device - Wilco Embedded Controller handle.
|
||||
|
@ -32,6 +29,7 @@
|
|||
* @data_size: Size of the data buffer used for EC communication.
|
||||
* @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
|
||||
* @rtc_pdev: The child platform_device used by the RTC sub-driver.
|
||||
* @telem_pdev: The child platform_device used by the telemetry sub-driver.
|
||||
*/
|
||||
struct wilco_ec_device {
|
||||
struct device *dev;
|
||||
|
@ -43,6 +41,7 @@ struct wilco_ec_device {
|
|||
size_t data_size;
|
||||
struct platform_device *debugfs_pdev;
|
||||
struct platform_device *rtc_pdev;
|
||||
struct platform_device *telem_pdev;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -85,14 +84,12 @@ struct wilco_ec_response {
|
|||
* enum wilco_ec_msg_type - Message type to select a set of command codes.
|
||||
* @WILCO_EC_MSG_LEGACY: Legacy EC messages for standard EC behavior.
|
||||
* @WILCO_EC_MSG_PROPERTY: Get/Set/Sync EC controlled NVRAM property.
|
||||
* @WILCO_EC_MSG_TELEMETRY_SHORT: 32 bytes of telemetry data provided by the EC.
|
||||
* @WILCO_EC_MSG_TELEMETRY_LONG: 256 bytes of telemetry data provided by the EC.
|
||||
* @WILCO_EC_MSG_TELEMETRY: Request telemetry data from the EC.
|
||||
*/
|
||||
enum wilco_ec_msg_type {
|
||||
WILCO_EC_MSG_LEGACY = 0x00f0,
|
||||
WILCO_EC_MSG_PROPERTY = 0x00f2,
|
||||
WILCO_EC_MSG_TELEMETRY_SHORT = 0x00f5,
|
||||
WILCO_EC_MSG_TELEMETRY_LONG = 0x00f6,
|
||||
WILCO_EC_MSG_TELEMETRY = 0x00f5,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -123,4 +120,87 @@ struct wilco_ec_message {
|
|||
*/
|
||||
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg);
|
||||
|
||||
/*
|
||||
* A Property is typically a data item that is stored to NVRAM
|
||||
* by the EC. Each of these data items has an index associated
|
||||
* with it, known as the Property ID (PID). Properties may have
|
||||
* variable lengths, up to a max of WILCO_EC_PROPERTY_MAX_SIZE
|
||||
* bytes. Properties can be simple integers, or they may be more
|
||||
* complex binary data.
|
||||
*/
|
||||
|
||||
#define WILCO_EC_PROPERTY_MAX_SIZE 4
|
||||
|
||||
/**
|
||||
* struct ec_property_set_msg - Message to get or set a property.
|
||||
* @property_id: Which property to get or set.
|
||||
* @length: Number of bytes of |data| that are used.
|
||||
* @data: Actual property data.
|
||||
*/
|
||||
struct wilco_ec_property_msg {
|
||||
u32 property_id;
|
||||
int length;
|
||||
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* wilco_ec_get_property() - Retrieve a property from the EC.
|
||||
* @ec: Embedded Controller device.
|
||||
* @prop_msg: Message for request and response.
|
||||
*
|
||||
* The property_id field of |prop_msg| should be filled before calling this
|
||||
* function. The result will be stored in the data and length fields.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int wilco_ec_get_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg);
|
||||
|
||||
/**
|
||||
* wilco_ec_set_property() - Store a property on the EC.
|
||||
* @ec: Embedded Controller device.
|
||||
* @prop_msg: Message for request and response.
|
||||
*
|
||||
* The property_id, length, and data fields of |prop_msg| should be
|
||||
* filled before calling this function.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int wilco_ec_set_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg);
|
||||
|
||||
/**
|
||||
* wilco_ec_get_byte_property() - Retrieve a byte-size property from the EC.
|
||||
* @ec: Embedded Controller device.
|
||||
* @property_id: Which property to retrieve.
|
||||
* @val: The result value, will be filled by this function.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 *val);
|
||||
|
||||
/**
|
||||
* wilco_ec_get_byte_property() - Store a byte-size property on the EC.
|
||||
* @ec: Embedded Controller device.
|
||||
* @property_id: Which property to store.
|
||||
* @val: Value to store.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 val);
|
||||
|
||||
/**
|
||||
* wilco_ec_add_sysfs() - Create sysfs entries
|
||||
* @ec: Wilco EC device
|
||||
*
|
||||
* wilco_ec_remove_sysfs() needs to be called afterwards
|
||||
* to perform the necessary cleanup.
|
||||
*
|
||||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
int wilco_ec_add_sysfs(struct wilco_ec_device *ec);
|
||||
void wilco_ec_remove_sysfs(struct wilco_ec_device *ec);
|
||||
|
||||
#endif /* WILCO_EC_H */
|
||||
|
|
|
@ -38,21 +38,21 @@ static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0);
|
|||
|
||||
static int ec_command_get_gain(struct snd_soc_component *component,
|
||||
struct ec_param_codec_i2s *param,
|
||||
struct ec_response_codec_gain *resp)
|
||||
struct ec_codec_i2s_gain *resp)
|
||||
{
|
||||
struct cros_ec_codec_data *codec_data =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
struct cros_ec_device *ec_device = codec_data->ec_device;
|
||||
u8 buffer[sizeof(struct cros_ec_command) +
|
||||
max(sizeof(struct ec_param_codec_i2s),
|
||||
sizeof(struct ec_response_codec_gain))];
|
||||
sizeof(struct ec_codec_i2s_gain))];
|
||||
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
|
||||
int ret;
|
||||
|
||||
msg->version = 0;
|
||||
msg->command = EC_CMD_CODEC_I2S;
|
||||
msg->outsize = sizeof(struct ec_param_codec_i2s);
|
||||
msg->insize = sizeof(struct ec_response_codec_gain);
|
||||
msg->insize = sizeof(struct ec_codec_i2s_gain);
|
||||
|
||||
memcpy(msg->data, param, msg->outsize);
|
||||
|
||||
|
@ -226,7 +226,7 @@ static int get_ec_mic_gain(struct snd_soc_component *component,
|
|||
u8 *left, u8 *right)
|
||||
{
|
||||
struct ec_param_codec_i2s param;
|
||||
struct ec_response_codec_gain resp;
|
||||
struct ec_codec_i2s_gain resp;
|
||||
int ret;
|
||||
|
||||
param.cmd = EC_CODEC_GET_GAIN;
|
||||
|
|
Загрузка…
Ссылка в новой задаче