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 ...alistair/sunxi64-5.4-dsi
commit
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
|
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
|
wilco_ec_msg_type. Byte 2+ consist of the data passed in the
|
||||||
request, starting at MBOX[0]
|
request, starting at MBOX[0]. At least three bytes are required
|
||||||
|
for writing, two for the type and at least a single byte of
|
||||||
At least three bytes are required for writing, two for the type
|
data.
|
||||||
and at least a single byte of data. Only the first
|
|
||||||
EC_MAILBOX_DATA_SIZE bytes of MBOX will be used.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
// Request EC info type 3 (EC firmware build date)
|
// Request EC info type 3 (EC firmware build date)
|
||||||
|
@ -40,7 +38,7 @@ Description:
|
||||||
$ cat /sys/kernel/debug/wilco_ec/raw
|
$ 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...
|
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
|
Note that the first 16 bytes of the received MBOX[] will be
|
||||||
printed, even if some of the data is junk. It is up to you to
|
printed, even if some of the data is junk, and skipping bytes
|
||||||
know how many of the first bytes of data are the actual
|
17 to 32. It is up to you to know how many of the first bytes of
|
||||||
response.
|
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
|
Accelerometers, Gyroscope and Magnetometer that are
|
||||||
presented by the ChromeOS EC Sensor hub.
|
presented by the ChromeOS EC Sensor hub.
|
||||||
Creates an IIO device for each functions.
|
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_CORE) += cros_ec_sensors_core.o
|
||||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.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))
|
if (queued_during_suspend && !device_may_wakeup(ckdev->dev))
|
||||||
return NOTIFY_OK;
|
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:
|
case EC_MKBP_EVENT_KEY_MATRIX:
|
||||||
pm_wakeup_event(ckdev->dev, 0);
|
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. */
|
/* For now, report failure to transition to S0ix with a warning. */
|
||||||
if (ret >= 0 && ec_dev->host_sleep_v1 &&
|
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 &
|
WARN_ONCE(buf.u.resp1.resume_response.sleep_transitions &
|
||||||
EC_HOST_RESUME_SLEEP_TIMEOUT,
|
EC_HOST_RESUME_SLEEP_TIMEOUT,
|
||||||
"EC detected sleep transition timeout. Total slp_s0 transitions: %d",
|
"EC detected sleep transition timeout. Total slp_s0 transitions: %d",
|
||||||
buf.u.resp1.resume_response.sleep_transitions &
|
buf.u.resp1.resume_response.sleep_transitions &
|
||||||
EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK);
|
EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,19 @@ config CROS_EC_RPMSG
|
||||||
To compile this driver as a module, choose M here: the
|
To compile this driver as a module, choose M here: the
|
||||||
module will be called cros_ec_rpmsg.
|
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
|
config CROS_EC_SPI
|
||||||
tristate "ChromeOS Embedded Controller (SPI)"
|
tristate "ChromeOS Embedded Controller (SPI)"
|
||||||
depends on MFD_CROS_EC && SPI
|
depends on MFD_CROS_EC && SPI
|
||||||
|
@ -83,28 +96,17 @@ config CROS_EC_SPI
|
||||||
'pre-amble' bytes before the response actually starts.
|
'pre-amble' bytes before the response actually starts.
|
||||||
|
|
||||||
config CROS_EC_LPC
|
config CROS_EC_LPC
|
||||||
tristate "ChromeOS Embedded Controller (LPC)"
|
tristate "ChromeOS Embedded Controller (LPC)"
|
||||||
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
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
|
|
||||||
help
|
help
|
||||||
If you say Y here, a variant LPC protocol for the Microchip EC
|
If you say Y here, you get support for talking to the ChromeOS EC
|
||||||
will be used. Note that this variant is not backward compatible
|
over an LPC bus, including the LPC Microchip EC (MEC) variant.
|
||||||
with non-Microchip ECs.
|
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
|
To compile this driver as a module, choose M here: the
|
||||||
choose Y here.
|
module will be called cros_ec_lpcs.
|
||||||
|
|
||||||
config CROS_EC_PROTO
|
config CROS_EC_PROTO
|
||||||
bool
|
bool
|
||||||
|
|
|
@ -7,10 +7,10 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
|
||||||
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
|
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
|
||||||
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
|
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
|
||||||
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.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_RPMSG) += cros_ec_rpmsg.o
|
||||||
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.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-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
|
||||||
cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
|
|
||||||
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.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_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
|
||||||
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.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))
|
#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
|
* @ec: EC device this debugfs information belongs to
|
||||||
* @dir: dentry for debugfs files
|
* @dir: dentry for debugfs files
|
||||||
|
@ -241,7 +242,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file,
|
||||||
read_buf, p - read_buf);
|
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,
|
.owner = THIS_MODULE,
|
||||||
.open = cros_ec_console_log_open,
|
.open = cros_ec_console_log_open,
|
||||||
.read = cros_ec_console_log_read,
|
.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,
|
.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,
|
.owner = THIS_MODULE,
|
||||||
.open = simple_open,
|
.open = simple_open,
|
||||||
.read = cros_ec_pdinfo_read,
|
.read = cros_ec_pdinfo_read,
|
||||||
.llseek = default_llseek,
|
.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)
|
static int ec_read_version_supported(struct cros_ec_dev *ec)
|
||||||
{
|
{
|
||||||
struct ec_params_get_cmd_versions_v1 *params;
|
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,
|
debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
|
||||||
&cros_ec_pdinfo_fops);
|
&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;
|
ec->debug_info = debug_info;
|
||||||
|
|
||||||
dev_set_drvdata(&pd->dev, ec);
|
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,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct attribute_group cros_ec_lightbar_attr_group = {
|
static struct attribute_group cros_ec_lightbar_attr_group = {
|
||||||
.name = "lightbar",
|
.name = "lightbar",
|
||||||
.attrs = __lb_cmds_attrs,
|
.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)
|
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)
|
if (userspace_control)
|
||||||
return 0;
|
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)
|
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)
|
if (userspace_control)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include <linux/printk.h>
|
#include <linux/printk.h>
|
||||||
#include <linux/suspend.h>
|
#include <linux/suspend.h>
|
||||||
|
|
||||||
#include "cros_ec_lpc_reg.h"
|
#include "cros_ec_lpc_mec.h"
|
||||||
|
|
||||||
#define DRV_NAME "cros_ec_lpcs"
|
#define DRV_NAME "cros_ec_lpcs"
|
||||||
#define ACPI_DRV_NAME "GOOG0004"
|
#define ACPI_DRV_NAME "GOOG0004"
|
||||||
|
@ -31,6 +31,96 @@
|
||||||
/* True if ACPI device is present */
|
/* True if ACPI device is present */
|
||||||
static bool cros_ec_lpc_acpi_device_found;
|
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)
|
static int ec_response_timed_out(void)
|
||||||
{
|
{
|
||||||
unsigned long one_second = jiffies + HZ;
|
unsigned long one_second = jiffies + HZ;
|
||||||
|
@ -38,7 +128,7 @@ static int ec_response_timed_out(void)
|
||||||
|
|
||||||
usleep_range(200, 300);
|
usleep_range(200, 300);
|
||||||
do {
|
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))
|
EC_LPC_STATUS_BUSY_MASK))
|
||||||
return 0;
|
return 0;
|
||||||
usleep_range(100, 200);
|
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);
|
ret = cros_ec_prepare_tx(ec, msg);
|
||||||
|
|
||||||
/* Write buffer */
|
/* 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 */
|
/* Here we go */
|
||||||
sum = EC_COMMAND_PROTOCOL_3;
|
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()) {
|
if (ec_response_timed_out()) {
|
||||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
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 */
|
/* 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);
|
ret = cros_ec_check_result(ec, msg);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Read back response */
|
/* Read back response */
|
||||||
dout = (u8 *)&response;
|
dout = (u8 *)&response;
|
||||||
sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||||
dout);
|
dout);
|
||||||
|
|
||||||
msg->result = response.result;
|
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 */
|
/* Read response and process checksum */
|
||||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
|
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
|
||||||
sizeof(response), response.data_len,
|
sizeof(response), response.data_len,
|
||||||
msg->data);
|
msg->data);
|
||||||
|
|
||||||
if (sum) {
|
if (sum) {
|
||||||
dev_err(ec->dev,
|
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;
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||||
|
|
||||||
/* Copy data and update checksum */
|
/* Copy data and update checksum */
|
||||||
sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||||
msg->data);
|
msg->data);
|
||||||
|
|
||||||
/* Finalize checksum and write args */
|
/* Finalize checksum and write args */
|
||||||
args.checksum = sum;
|
args.checksum = sum;
|
||||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||||
(u8 *)&args);
|
(u8 *)&args);
|
||||||
|
|
||||||
/* Here we go */
|
/* Here we go */
|
||||||
sum = msg->command;
|
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()) {
|
if (ec_response_timed_out()) {
|
||||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
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 */
|
/* 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);
|
ret = cros_ec_check_result(ec, msg);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Read back args */
|
/* Read back args */
|
||||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
||||||
(u8 *)&args);
|
|
||||||
|
|
||||||
if (args.data_size > msg->insize) {
|
if (args.data_size > msg->insize) {
|
||||||
dev_err(ec->dev,
|
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;
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||||
|
|
||||||
/* Read response and update checksum */
|
/* Read response and update checksum */
|
||||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||||
msg->data);
|
msg->data);
|
||||||
|
|
||||||
/* Verify checksum */
|
/* Verify checksum */
|
||||||
if (args.checksum != sum) {
|
if (args.checksum != sum) {
|
||||||
|
@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
||||||
|
|
||||||
/* fixed length */
|
/* fixed length */
|
||||||
if (bytes) {
|
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;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* string */
|
/* string */
|
||||||
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
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++;
|
cnt++;
|
||||||
if (!*s)
|
if (!*s)
|
||||||
break;
|
break;
|
||||||
|
@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
||||||
return -EBUSY;
|
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') {
|
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||||
dev_err(dev, "EC ID not detected\n");
|
/* Re-assign read/write operations for the non MEC variant */
|
||||||
return -ENODEV;
|
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,
|
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
|
||||||
|
@ -405,7 +509,7 @@ static int cros_ec_lpc_resume(struct device *dev)
|
||||||
}
|
}
|
||||||
#endif
|
#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)
|
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;
|
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 */
|
/* Register the driver */
|
||||||
ret = platform_driver_register(&cros_ec_lpc_driver);
|
ret = platform_driver_register(&cros_ec_lpc_driver);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
||||||
cros_ec_lpc_reg_destroy();
|
cros_ec_lpc_mec_destroy();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +567,7 @@ static int __init cros_ec_lpc_init(void)
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err(DRV_NAME ": can't register device: %d\n", ret);
|
pr_err(DRV_NAME ": can't register device: %d\n", ret);
|
||||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
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)
|
if (!cros_ec_lpc_acpi_device_found)
|
||||||
platform_device_unregister(&cros_ec_lpc_device);
|
platform_device_unregister(&cros_ec_lpc_device);
|
||||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||||
cros_ec_lpc_reg_destroy();
|
cros_ec_lpc_mec_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(cros_ec_lpc_init);
|
module_init(cros_ec_lpc_init);
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
static struct mutex io_mutex;
|
static struct mutex io_mutex;
|
||||||
static u16 mec_emi_base, mec_emi_end;
|
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
|
* @access_type: Type of access, typically 32-bit auto-increment
|
||||||
*/
|
*/
|
||||||
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
|
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;
|
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
|
* @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request
|
||||||
* @offset: Base read / write address
|
* @offset: Base read / write address
|
||||||
* @length: Number of bytes to read / write
|
* @length: Number of bytes to read / write
|
||||||
* @buf: Destination / source buffer
|
* @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,
|
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||||
unsigned int offset, unsigned int length,
|
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/platform_device.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
|
#include <uapi/linux/sched/types.h>
|
||||||
|
|
||||||
/* The header byte, which follows the preamble */
|
/* The header byte, which follows the preamble */
|
||||||
#define EC_MSG_HEADER 0xec
|
#define EC_MSG_HEADER 0xec
|
||||||
|
@ -67,12 +67,14 @@
|
||||||
* is sent when we want to turn on CS at the start of a transaction.
|
* 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
|
* @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.
|
* 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 cros_ec_spi {
|
||||||
struct spi_device *spi;
|
struct spi_device *spi;
|
||||||
s64 last_transfer_ns;
|
s64 last_transfer_ns;
|
||||||
unsigned int start_of_msg_delay;
|
unsigned int start_of_msg_delay;
|
||||||
unsigned int end_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,
|
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 cros_ec_xfer_work_params {
|
||||||
struct work_struct work;
|
struct kthread_work work;
|
||||||
cros_ec_xfer_fn_t fn;
|
cros_ec_xfer_fn_t fn;
|
||||||
struct cros_ec_device *ec_dev;
|
struct cros_ec_device *ec_dev;
|
||||||
struct cros_ec_command *ec_msg;
|
struct cros_ec_command *ec_msg;
|
||||||
|
@ -632,7 +634,7 @@ exit:
|
||||||
return ret;
|
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;
|
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,
|
struct cros_ec_command *ec_msg,
|
||||||
cros_ec_xfer_fn_t fn)
|
cros_ec_xfer_fn_t fn)
|
||||||
{
|
{
|
||||||
struct cros_ec_xfer_work_params params;
|
struct cros_ec_spi *ec_spi = ec_dev->priv;
|
||||||
|
struct cros_ec_xfer_work_params params = {
|
||||||
INIT_WORK_ONSTACK(¶ms.work, cros_ec_xfer_high_pri_work);
|
.work = KTHREAD_WORK_INIT(params.work,
|
||||||
params.ec_dev = ec_dev;
|
cros_ec_xfer_high_pri_work),
|
||||||
params.ec_msg = ec_msg;
|
.ec_dev = ec_dev,
|
||||||
params.fn = fn;
|
.ec_msg = ec_msg,
|
||||||
|
.fn = fn,
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This looks a bit ridiculous. Why do the work on a
|
* 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
|
* context switched out for too long and the EC giving up on
|
||||||
* the transfer.
|
* the transfer.
|
||||||
*/
|
*/
|
||||||
queue_work(system_highpri_wq, ¶ms.work);
|
kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work);
|
||||||
flush_work(¶ms.work);
|
kthread_flush_work(¶ms.work);
|
||||||
destroy_work_on_stack(¶ms.work);
|
|
||||||
|
|
||||||
return params.ret;
|
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;
|
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)
|
static int cros_ec_spi_probe(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
struct device *dev = &spi->dev;
|
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->bits_per_word = 8;
|
||||||
spi->mode = SPI_MODE_0;
|
spi->mode = SPI_MODE_0;
|
||||||
|
spi->rt = true;
|
||||||
err = spi_setup(spi);
|
err = spi_setup(spi);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
@ -732,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
|
||||||
|
|
||||||
ec_spi->last_transfer_ns = ktime_get_ns();
|
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);
|
err = cros_ec_register(ec_dev);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(dev, "cannot register EC\n");
|
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 = {
|
static struct spi_driver cros_ec_driver_spi = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "cros-ec-spi",
|
.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,
|
.pm = &cros_ec_spi_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = cros_ec_spi_probe,
|
.probe = cros_ec_spi_probe,
|
||||||
|
|
|
@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
|
||||||
return a->mode;
|
return a->mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct attribute_group cros_ec_attr_group = {
|
static struct attribute_group cros_ec_attr_group = {
|
||||||
.attrs = __ec_attrs,
|
.attrs = __ec_attrs,
|
||||||
.is_visible = cros_ec_ctrl_visible,
|
.is_visible = cros_ec_ctrl_visible,
|
||||||
};
|
};
|
||||||
|
|
|
@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = {
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
struct attribute_group cros_ec_vbc_attr_group = {
|
static struct attribute_group cros_ec_vbc_attr_group = {
|
||||||
.name = "vbc",
|
.name = "vbc",
|
||||||
.bin_attrs = cros_ec_vbc_bin_attrs,
|
.bin_attrs = cros_ec_vbc_bin_attrs,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
config WILCO_EC
|
config WILCO_EC
|
||||||
tristate "ChromeOS Wilco Embedded Controller"
|
tristate "ChromeOS Wilco Embedded Controller"
|
||||||
depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC
|
depends on ACPI && X86 && CROS_EC_LPC
|
||||||
help
|
help
|
||||||
If you say Y here, you get support for talking to the ChromeOS
|
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
|
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
|
manipulation and allow for testing arbitrary commands. This
|
||||||
interface is intended for debug only and will not be present
|
interface is intended for debug only and will not be present
|
||||||
on production devices.
|
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
|
# 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
|
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
|
||||||
wilco_ec_debugfs-objs := debugfs.o
|
wilco_ec_debugfs-objs := debugfs.o
|
||||||
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_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;
|
ec->dev = dev;
|
||||||
mutex_init(&ec->mailbox_lock);
|
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;
|
||||||
ec->data_size = sizeof(struct wilco_ec_response) +
|
|
||||||
EC_MAILBOX_DATA_SIZE_EXTENDED;
|
|
||||||
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
|
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
|
||||||
if (!ec->data_buffer)
|
if (!ec->data_buffer)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev)
|
||||||
goto unregister_debugfs;
|
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;
|
return 0;
|
||||||
|
|
||||||
|
remove_sysfs:
|
||||||
|
wilco_ec_remove_sysfs(ec);
|
||||||
|
unregister_rtc:
|
||||||
|
platform_device_unregister(ec->rtc_pdev);
|
||||||
unregister_debugfs:
|
unregister_debugfs:
|
||||||
if (ec->debugfs_pdev)
|
if (ec->debugfs_pdev)
|
||||||
platform_device_unregister(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);
|
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);
|
platform_device_unregister(ec->rtc_pdev);
|
||||||
if (ec->debugfs_pdev)
|
if (ec->debugfs_pdev)
|
||||||
platform_device_unregister(ec->debugfs_pdev);
|
platform_device_unregister(ec->debugfs_pdev);
|
||||||
|
|
|
@ -16,14 +16,14 @@
|
||||||
|
|
||||||
#define DRV_NAME "wilco-ec-debugfs"
|
#define DRV_NAME "wilco-ec-debugfs"
|
||||||
|
|
||||||
/* The 256 raw bytes will take up more space when represented as a hex string */
|
/* The raw bytes will take up more space when represented as a hex string */
|
||||||
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4)
|
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
|
||||||
|
|
||||||
struct wilco_ec_debugfs {
|
struct wilco_ec_debugfs {
|
||||||
struct wilco_ec_device *ec;
|
struct wilco_ec_device *ec;
|
||||||
struct dentry *dir;
|
struct dentry *dir;
|
||||||
size_t response_size;
|
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];
|
u8 formatted_data[FORMATTED_BUFFER_SIZE];
|
||||||
};
|
};
|
||||||
static struct wilco_ec_debugfs *debug_info;
|
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_data = debug_info->raw_data;
|
||||||
msg.response_size = EC_MAILBOX_DATA_SIZE;
|
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);
|
ret = wilco_ec_mailbox(debug_info->ec, &msg);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
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;
|
struct wilco_ec_response *rs;
|
||||||
u8 checksum;
|
u8 checksum;
|
||||||
u8 flag;
|
u8 flag;
|
||||||
size_t size;
|
|
||||||
|
|
||||||
/* Write request header, then data */
|
/* Write request header, then data */
|
||||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
|
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;
|
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 */
|
/* Read back response */
|
||||||
rs = ec->data_buffer;
|
rs = ec->data_buffer;
|
||||||
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
|
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) {
|
if (checksum) {
|
||||||
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
|
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
|
||||||
return -EBADMSG;
|
return -EBADMSG;
|
||||||
|
@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
||||||
return -EBADMSG;
|
return -EBADMSG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rs->data_size != size) {
|
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
|
||||||
dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
|
dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
|
||||||
rs->data_size, size);
|
rs->data_size, EC_MAILBOX_DATA_SIZE);
|
||||||
return -EMSGSIZE;
|
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;
|
struct ec_response_get_next_event_v1 event_data;
|
||||||
int event_size;
|
int event_size;
|
||||||
u32 host_event_wake_mask;
|
u32 host_event_wake_mask;
|
||||||
|
u32 last_resume_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,12 +13,9 @@
|
||||||
|
|
||||||
/* Message flags for using the mailbox() interface */
|
/* Message flags for using the mailbox() interface */
|
||||||
#define WILCO_EC_FLAG_NO_RESPONSE BIT(0) /* EC does not respond */
|
#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 */
|
/* Normal commands have a maximum 32 bytes of data */
|
||||||
#define EC_MAILBOX_DATA_SIZE 32
|
#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.
|
* struct wilco_ec_device - Wilco Embedded Controller handle.
|
||||||
|
@ -32,6 +29,7 @@
|
||||||
* @data_size: Size of the data buffer used for EC communication.
|
* @data_size: Size of the data buffer used for EC communication.
|
||||||
* @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
|
* @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
|
||||||
* @rtc_pdev: The child platform_device used by the RTC 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 wilco_ec_device {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
@ -43,6 +41,7 @@ struct wilco_ec_device {
|
||||||
size_t data_size;
|
size_t data_size;
|
||||||
struct platform_device *debugfs_pdev;
|
struct platform_device *debugfs_pdev;
|
||||||
struct platform_device *rtc_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.
|
* 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_LEGACY: Legacy EC messages for standard EC behavior.
|
||||||
* @WILCO_EC_MSG_PROPERTY: Get/Set/Sync EC controlled NVRAM property.
|
* @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: Request telemetry data from the EC.
|
||||||
* @WILCO_EC_MSG_TELEMETRY_LONG: 256 bytes of telemetry data provided by the EC.
|
|
||||||
*/
|
*/
|
||||||
enum wilco_ec_msg_type {
|
enum wilco_ec_msg_type {
|
||||||
WILCO_EC_MSG_LEGACY = 0x00f0,
|
WILCO_EC_MSG_LEGACY = 0x00f0,
|
||||||
WILCO_EC_MSG_PROPERTY = 0x00f2,
|
WILCO_EC_MSG_PROPERTY = 0x00f2,
|
||||||
WILCO_EC_MSG_TELEMETRY_SHORT = 0x00f5,
|
WILCO_EC_MSG_TELEMETRY = 0x00f5,
|
||||||
WILCO_EC_MSG_TELEMETRY_LONG = 0x00f6,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,4 +120,87 @@ struct wilco_ec_message {
|
||||||
*/
|
*/
|
||||||
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg);
|
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 */
|
#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,
|
static int ec_command_get_gain(struct snd_soc_component *component,
|
||||||
struct ec_param_codec_i2s *param,
|
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 =
|
struct cros_ec_codec_data *codec_data =
|
||||||
snd_soc_component_get_drvdata(component);
|
snd_soc_component_get_drvdata(component);
|
||||||
struct cros_ec_device *ec_device = codec_data->ec_device;
|
struct cros_ec_device *ec_device = codec_data->ec_device;
|
||||||
u8 buffer[sizeof(struct cros_ec_command) +
|
u8 buffer[sizeof(struct cros_ec_command) +
|
||||||
max(sizeof(struct ec_param_codec_i2s),
|
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;
|
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
msg->version = 0;
|
msg->version = 0;
|
||||||
msg->command = EC_CMD_CODEC_I2S;
|
msg->command = EC_CMD_CODEC_I2S;
|
||||||
msg->outsize = sizeof(struct ec_param_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);
|
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)
|
u8 *left, u8 *right)
|
||||||
{
|
{
|
||||||
struct ec_param_codec_i2s param;
|
struct ec_param_codec_i2s param;
|
||||||
struct ec_response_codec_gain resp;
|
struct ec_codec_i2s_gain resp;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
param.cmd = EC_CODEC_GET_GAIN;
|
param.cmd = EC_CODEC_GET_GAIN;
|
||||||
|
|
Loading…
Reference in New Issue