Commit d7d170a8 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'tag-chrome-platform-for-v5.3' of...

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
  ...
parents d06e4156 8c3166e1
What: /sys/kernel/debug/<cros-ec-device>/console_log
Date: September 2017
KernelVersion: 4.13
Description:
If the EC supports the CONSOLE_READ command type, this file
can be used to grab the EC logs. The kernel polls for the log
and keeps its own buffer but userspace should grab this and
write it out to some logs.
What: /sys/kernel/debug/<cros-ec-device>/panicinfo
Date: September 2017
KernelVersion: 4.13
Description:
This file dumps the EC panic information from the previous
reboot. This file will only exist if the PANIC_INFO command
type is supported by the EC.
What: /sys/kernel/debug/<cros-ec-device>/pdinfo
Date: June 2018
KernelVersion: 4.17
Description:
This file provides the port role, muxes and power debug
information for all the USB PD/type-C ports available. If
the are no ports available, this file will be just an empty
file.
What: /sys/kernel/debug/<cros-ec-device>/uptime
Date: June 2019
KernelVersion: 5.3
Description:
A u32 providing the time since EC booted in ms. This is
is used for synchronizing the AP host time with the EC
log. An error is returned if the command is not supported
by the EC or there is a communication problem.
What: /sys/kernel/debug/<cros-ec-device>/last_resume_result
Date: June 2019
KernelVersion: 5.3
Description:
Some ECs have a feature where they will track transitions to
the (Intel) processor's SLP_S0 line, in order to detect cases
where a system failed to go into S0ix. When the system resumes,
an EC with this feature will return a summary of SLP_S0
transitions that occurred. The last_resume_result file returns
the most recent response from the AP's resume message to the EC.
The bottom 31 bits contain a count of the number of SLP_S0
transitions that occurred since the suspend message was
received. Bit 31 is set if the EC attempted to wake the
system due to a timeout when watching for SLP_S0 transitions.
Callers can use this to detect a wake from the EC due to
S0ix timeouts. The result will be zero if no suspend
transitions have been attempted, or the EC does not support
this feature.
Output will be in the format: "0x%08x\n".
......@@ -23,11 +23,9 @@ Description:
For writing, bytes 0-1 indicate the message type, one of enum
wilco_ec_msg_type. Byte 2+ consist of the data passed in the
request, starting at MBOX[0]
At least three bytes are required for writing, two for the type
and at least a single byte of data. Only the first
EC_MAILBOX_DATA_SIZE bytes of MBOX will be used.
request, starting at MBOX[0]. At least three bytes are required
for writing, two for the type and at least a single byte of
data.
Example:
// Request EC info type 3 (EC firmware build date)
......@@ -40,7 +38,7 @@ Description:
$ cat /sys/kernel/debug/wilco_ec/raw
00 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 ..12/21/18.8...
Note that the first 32 bytes of the received MBOX[] will be
printed, even if some of the data is junk. It is up to you to
know how many of the first bytes of data are the actual
response.
Note that the first 16 bytes of the received MBOX[] will be
printed, even if some of the data is junk, and skipping bytes
17 to 32. It is up to you to know how many of the first bytes of
data are the actual response.
What: /sys/bus/platform/devices/GOOG000C\:00/boot_on_ac
Date: April 2019
KernelVersion: 5.3
Description:
Boot on AC is a policy which makes the device boot from S5
when AC power is connected. This is useful for users who
want to run their device headless or with a dock.
Input should be parseable by kstrtou8() to 0 or 1.
What: /sys/bus/platform/devices/GOOG000C\:00/build_date
Date: May 2019
KernelVersion: 5.3
Description:
Display Wilco Embedded Controller firmware build date.
Output will a MM/DD/YY string.
What: /sys/bus/platform/devices/GOOG000C\:00/build_revision
Date: May 2019
KernelVersion: 5.3
Description:
Display Wilco Embedded Controller build revision.
Output will a version string be similar to the example below:
d2592cae0
What: /sys/bus/platform/devices/GOOG000C\:00/model_number
Date: May 2019
KernelVersion: 5.3
Description:
Display Wilco Embedded Controller model number.
Output will a version string be similar to the example below:
08B6
What: /sys/bus/platform/devices/GOOG000C\:00/version
Date: May 2019
KernelVersion: 5.3
Description:
Display Wilco Embedded Controller firmware version.
The format of the string is x.y.z. Where x is major, y is minor
and z is the build number. For example: 95.00.06
......@@ -21,3 +21,12 @@ config IIO_CROS_EC_SENSORS
Accelerometers, Gyroscope and Magnetometer that are
presented by the ChromeOS EC Sensor hub.
Creates an IIO device for each functions.
config IIO_CROS_EC_SENSORS_LID_ANGLE
tristate "ChromeOS EC Sensor for lid angle"
depends on IIO_CROS_EC_SENSORS_CORE
help
Module to report the angle between lid and base for some
convertible devices.
This module is loaded when the EC can calculate the angle between the base
and the lid.
......@@ -5,3 +5,4 @@
obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros_ec_sensors_core.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o
// SPDX-License-Identifier: GPL-2.0
/*
* cros_ec_lid_angle - Driver for CrOS EC lid angle sensor.
*
* Copyright 2018 Google, Inc
*
* This driver uses the cros-ec interface to communicate with the Chrome OS
* EC about counter sensors. Counters are presented through
* iio sysfs.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iio/buffer.h>
#include <linux/iio/common/cros_ec_sensors_core.h>
#include <linux/iio/iio.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/kernel.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define DRV_NAME "cros-ec-lid-angle"
/*
* One channel for the lid angle, the other for timestamp.
*/
static const struct iio_chan_spec cros_ec_lid_angle_channels[] = {
{
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.scan_type.realbits = CROS_EC_SENSOR_BITS,
.scan_type.storagebits = CROS_EC_SENSOR_BITS,
.scan_type.sign = 'u',
.type = IIO_ANGL
},
IIO_CHAN_SOFT_TIMESTAMP(1)
};
/* State data for ec_sensors iio driver. */
struct cros_ec_lid_angle_state {
/* Shared by all sensors */
struct cros_ec_sensors_core_state core;
};
static int cros_ec_sensors_read_lid_angle(struct iio_dev *indio_dev,
unsigned long scan_mask, s16 *data)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret;
st->param.cmd = MOTIONSENSE_CMD_LID_ANGLE;
ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->lid_angle));
if (ret) {
dev_warn(&indio_dev->dev, "Unable to read lid angle\n");
return ret;
}
*data = st->resp->lid_angle.value;
return 0;
}
static int cros_ec_lid_angle_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct cros_ec_lid_angle_state *st = iio_priv(indio_dev);
s16 data;
int ret;
mutex_lock(&st->core.cmd_lock);
ret = cros_ec_sensors_read_lid_angle(indio_dev, 1, &data);
if (ret == 0) {
*val = data;
ret = IIO_VAL_INT;
}
mutex_unlock(&st->core.cmd_lock);
return ret;
}
static const struct iio_info cros_ec_lid_angle_info = {
.read_raw = &cros_ec_lid_angle_read,
};
static int cros_ec_lid_angle_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct iio_dev *indio_dev;
struct cros_ec_lid_angle_state *state;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
ret = cros_ec_sensors_core_init(pdev, indio_dev, false);
if (ret)
return ret;
indio_dev->info = &cros_ec_lid_angle_info;
state = iio_priv(indio_dev);
indio_dev->channels = cros_ec_lid_angle_channels;
indio_dev->num_channels = ARRAY_SIZE(cros_ec_lid_angle_channels);
state->core.read_ec_sensors_data = cros_ec_sensors_read_lid_angle;
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
cros_ec_sensors_capture, NULL);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
static const struct platform_device_id cros_ec_lid_angle_ids[] = {
{
.name = DRV_NAME,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids);
static struct platform_driver cros_ec_lid_angle_platform_driver = {
.driver = {
.name = DRV_NAME,
.pm = &cros_ec_sensors_pm_ops,
},
.probe = cros_ec_lid_angle_probe,
.id_table = cros_ec_lid_angle_ids,
};
module_platform_driver(cros_ec_lid_angle_platform_driver);
MODULE_DESCRIPTION("ChromeOS EC driver for reporting convertible lid angle.");
MODULE_LICENSE("GPL v2");
......@@ -237,7 +237,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
if (queued_during_suspend && !device_may_wakeup(ckdev->dev))
return NOTIFY_OK;
switch (ckdev->ec->event_data.event_type) {
switch (ckdev->ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK) {
case EC_MKBP_EVENT_KEY_MATRIX:
pm_wakeup_event(ckdev->dev, 0);
......
......@@ -102,12 +102,16 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
/* For now, report failure to transition to S0ix with a warning. */
if (ret >= 0 && ec_dev->host_sleep_v1 &&
(sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME))
(sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) {
ec_dev->last_resume_result =
buf.u.resp1.resume_response.sleep_transitions;
WARN_ONCE(buf.u.resp1.resume_response.sleep_transitions &
EC_HOST_RESUME_SLEEP_TIMEOUT,
"EC detected sleep transition timeout. Total slp_s0 transitions: %d",
buf.u.resp1.resume_response.sleep_transitions &
EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK);
}
return ret;
}
......
......@@ -72,6 +72,19 @@ config CROS_EC_RPMSG
To compile this driver as a module, choose M here: the
module will be called cros_ec_rpmsg.
config CROS_EC_ISHTP
tristate "ChromeOS Embedded Controller (ISHTP)"
depends on MFD_CROS_EC
depends on INTEL_ISH_HID
help
If you say Y here, you get support for talking to the ChromeOS EC
firmware running on Intel Integrated Sensor Hub (ISH), using the
ISH Transport protocol (ISH-TP). This uses a simple byte-level
protocol with a checksum.
To compile this driver as a module, choose M here: the
module will be called cros_ec_ishtp.
config CROS_EC_SPI
tristate "ChromeOS Embedded Controller (SPI)"
depends on MFD_CROS_EC && SPI
......@@ -83,28 +96,17 @@ config CROS_EC_SPI
'pre-amble' bytes before the response actually starts.
config CROS_EC_LPC
tristate "ChromeOS Embedded Controller (LPC)"
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
help
If you say Y here, you get support for talking to the ChromeOS EC
over an LPC bus. This uses a simple byte-level protocol with a
checksum. This is used for userspace access only. The kernel
typically has its own communication methods.
To compile this driver as a module, choose M here: the
module will be called cros_ec_lpc.
config CROS_EC_LPC_MEC
bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant"
depends on CROS_EC_LPC
default n
tristate "ChromeOS Embedded Controller (LPC)"
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
help
If you say Y here, a variant LPC protocol for the Microchip EC
will be used. Note that this variant is not backward compatible
with non-Microchip ECs.
If you say Y here, you get support for talking to the ChromeOS EC
over an LPC bus, including the LPC Microchip EC (MEC) variant.
This uses a simple byte-level protocol with a checksum. This is
used for userspace access only. The kernel typically has its own
communication methods.
If you have a ChromeOS Embedded Controller Microchip EC variant
choose Y here.
To compile this driver as a module, choose M here: the
module will be called cros_ec_lpcs.
config CROS_EC_PROTO
bool
......
......@@ -7,10 +7,10 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o
obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o
cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
......
......@@ -25,7 +25,8 @@
#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
/* struct cros_ec_debugfs - ChromeOS EC debugging information
/**
* struct cros_ec_debugfs - EC debugging information.
*
* @ec: EC device this debugfs information belongs to
* @dir: dentry for debugfs files
......@@ -241,7 +242,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file,
read_buf, p - read_buf);
}
const struct file_operations cros_ec_console_log_fops = {
static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct cros_ec_debugfs *debug_info = file->private_data;
struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
struct {
struct cros_ec_command cmd;
struct ec_response_uptime_info resp;
} __packed msg = {};
struct ec_response_uptime_info *resp;
char read_buf[32];
int ret;
resp = (struct ec_response_uptime_info *)&msg.resp;
msg.cmd.command = EC_CMD_GET_UPTIME_INFO;
msg.cmd.insize = sizeof(*resp);
ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd);
if (ret < 0)
return ret;
ret = scnprintf(read_buf, sizeof(read_buf), "%u\n",
resp->time_since_ec_boot_ms);
return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret);
}
static const struct file_operations cros_ec_console_log_fops = {
.owner = THIS_MODULE,
.open = cros_ec_console_log_open,
.read = cros_ec_console_log_read,
......@@ -250,13 +279,20 @@ const struct file_operations cros_ec_console_log_fops = {
.release = cros_ec_console_log_release,
};
const struct file_operations cros_ec_pdinfo_fops = {
static const struct file_operations cros_ec_pdinfo_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = cros_ec_pdinfo_read,
.llseek = default_llseek,
};
static const struct file_operations cros_ec_uptime_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = cros_ec_uptime_read,
.llseek = default_llseek,
};
static int ec_read_version_supported(struct cros_ec_dev *ec)
{
struct ec_params_get_cmd_versions_v1 *params;
......@@ -408,6 +444,12 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
&cros_ec_pdinfo_fops);
debugfs_create_file("uptime", 0444, debug_info->dir, debug_info,
&cros_ec_uptime_fops);
debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
&ec->ec_dev->last_resume_result);
ec->debug_info = debug_info;
dev_set_drvdata(&pd->dev, ec);
......
This diff is collapsed.
......@@ -547,7 +547,7 @@ static struct attribute *__lb_cmds_attrs[] = {
NULL,
};
struct attribute_group cros_ec_lightbar_attr_group = {
static struct attribute_group cros_ec_lightbar_attr_group = {
.name = "lightbar",
.attrs = __lb_cmds_attrs,
};
......@@ -600,7 +600,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd)
static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
{
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
if (userspace_control)
return 0;
......@@ -610,7 +610,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
{
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
if (userspace_control)
return 0;
......
......@@ -23,7 +23,7 @@
#include <linux/printk.h>
#include <linux/suspend.h>
#include "cros_ec_lpc_reg.h"
#include "cros_ec_lpc_mec.h"
#define DRV_NAME "cros_ec_lpcs"
#define ACPI_DRV_NAME "GOOG0004"
......@@ -31,6 +31,96 @@
/* True if ACPI device is present */
static bool cros_ec_lpc_acpi_device_found;
/**
* struct lpc_driver_ops - LPC driver operations
* @read: Copy length bytes from EC address offset into buffer dest. Returns
* the 8-bit checksum of all bytes read.
* @write: Copy length bytes from buffer msg into EC address offset. Returns
* the 8-bit checksum of all bytes written.
*/
struct lpc_driver_ops {
u8 (*read)(unsigned int offset, unsigned int length, u8 *dest);
u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg);
};
static struct lpc_driver_ops cros_ec_lpc_ops = { };
/*
* A generic instance of the read function of struct lpc_driver_ops, used for
* the LPC EC.
*/
static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
u8 *dest)
{
int sum = 0;
int i;
for (i = 0; i < length; ++i) {
dest[i] = inb(offset + i);
sum += dest[i];
}
/* Return checksum of all bytes read */
return sum;
}
/*
* A generic instance of the write function of struct lpc_driver_ops, used for
* the LPC EC.
*/
static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
const u8 *msg)
{
int sum = 0;
int i;
for (i = 0; i < length; ++i) {
outb(msg[i], offset + i);
sum += msg[i];
}
/* Return checksum of all bytes written */
return sum;
}
/*
* An instance of the read function of struct lpc_driver_ops, used for the
* MEC variant of LPC EC.
*/
static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
u8 *dest)
{
int in_range = cros_ec_lpc_mec_in_range(offset, length);
if (in_range < 0)
return 0;
return in_range ?
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
offset - EC_HOST_CMD_REGION0,
length, dest) :
cros_ec_lpc_read_bytes(offset, length, dest);
}
/*
* An instance of the write function of struct lpc_driver_ops, used for the
* MEC variant of LPC EC.
*/
static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
const u8 *msg)
{
int in_range = cros_ec_lpc_mec_in_range(offset, length);
if (in_range < 0)
return 0;
return in_range ?
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
offset - EC_HOST_CMD_REGION0,
length, (u8 *)msg) :
cros_ec_lpc_write_bytes(offset, length, msg);
}
static int ec_response_timed_out(void)
{
unsigned long one_second = jiffies + HZ;
......@@ -38,7 +128,7 @@ static int ec_response_timed_out(void)
usleep_range(200, 300);
do {
if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) &
if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) &
EC_LPC_STATUS_BUSY_MASK))
return 0;
usleep_range(100, 200);
......@@ -58,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
ret = cros_ec_prepare_tx(ec, msg);
/* Write buffer */
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
/* Here we go */
sum = EC_COMMAND_PROTOCOL_3;
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
if (ec_response_timed_out()) {
dev_warn(ec->dev, "EC responsed timed out\n");
......@@ -71,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
}
/* Check result */
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
ret = cros_ec_check_result(ec, msg);
if (ret)
goto done;
/* Read back response */
dout = (u8 *)&response;
sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
dout);
sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
dout);
msg->result = response.result;
......@@ -92,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
}
/* Read response and process checksum */
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
sizeof(response), response.data_len,
msg->data);
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
sizeof(response), response.data_len,
msg->data);
if (sum) {
dev_err(ec->dev,
......@@ -134,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
sum = msg->command + args.flags + args.command_version + args.data_size;
/* Copy data and update checksum */
sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
msg->data);
sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
msg->data);
/* Finalize checksum and write args */
args.checksum = sum;
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
(u8 *)&args);
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
(u8 *)&args);
/* Here we go */
sum = msg->command;
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
if (ec_response_timed_out()) {
dev_warn(ec->dev, "EC responsed timed out\n");
......@@ -153,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
}
/* Check result */
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
ret = cros_ec_check_result(ec, msg);
if (ret)
goto done;
/* Read back args */
cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
(u8 *)&args);
cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
if (args.data_size > msg->insize) {
dev_err(ec->dev,
......@@ -174,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
sum = msg->command + args.flags + args.command_version + args.data_size;
/* Read response and update checksum */
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
msg->data);
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
msg->data);
/* Verify checksum */
if (args.checksum != sum) {
......@@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
/* fixed length */
if (bytes) {
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
return bytes;
}
/* string */
for (; i < EC_MEMMAP_SIZE; i++, s++) {
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s);
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s);
cnt++;
if (!*s)
break;
......@@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
return -EBUSY;
}
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
/*
* Read the mapped ID twice, the first one is assuming the
* EC is a Microchip Embedded Controller (MEC) variant, if the
* protocol fails, fallback to the non MEC variant and try to
* read again the ID.
*/
cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes;
cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes;
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
if (buf[0] != 'E' || buf[1] != 'C') {
dev_err(dev, "EC ID not detected\n");
return -ENODEV;
/* Re-assign read/write operations for the non MEC variant */
cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes;
cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes;
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2,
buf);
if (buf[0] != 'E' || buf[1] != 'C') {
dev_err(dev, "EC ID not detected\n");
return -ENODEV;
}
}
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
......@@ -405,7 +509,7 @@ static int cros_ec_lpc_resume(struct device *dev)
}
#endif
const struct dev_pm_ops cros_ec_lpc_pm_ops = {
static const struct dev_pm_ops cros_ec_lpc_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume)
};
......@@ -446,13 +550,14 @@ static int __init cros_ec_lpc_init(void)
return -ENODEV;
}
cros_ec_lpc_reg_init();
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
/* Register the driver */
ret = platform_driver_register(&cros_ec_lpc_driver);
if (ret) {
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
cros_ec_lpc_reg_destroy();
cros_ec_lpc_mec_destroy();
return ret;
}
......@@ -462,7 +567,7 @@ static int __init cros_ec_lpc_init(void)
if (ret) {
pr_err(DRV_NAME ": can't register device: %d\n", ret);
platform_driver_unregister(&cros_ec_lpc_driver);
cros_ec_lpc_reg_destroy();
cros_ec_lpc_mec_destroy();
}
}
......@@ -474,7 +579,7 @@ static void __exit cros_ec_lpc_exit(void)
if (!cros_ec_lpc_acpi_device_found)
platform_device_unregister(&cros_ec_lpc_device);
platform_driver_unregister(&cros_ec_lpc_driver);
cros_ec_lpc_reg_destroy();
cros_ec_lpc_mec_destroy();
}
module_init(cros_ec_lpc_init);
......
......@@ -17,12 +17,10 @@
static struct mutex io_mutex;
static u16 mec_emi_base, mec_emi_end;
/*
* cros_ec_lpc_mec_emi_write_address
*
* Initialize EMI read / write at a given address.
/**
* cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
*
* @addr: Starting read / write address
* @addr: Starting read / write address
* @access_type: Type of access, typically 32-bit auto-increment
*/
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
......@@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
return 0;
}
/*
* cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port
/**
* cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port.
*
* @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request
* @offset: Base read / write address
* @length: Number of bytes to read / write
* @buf: Destination / source buffer
*
* @return 8-bit checksum of all bytes read / written
* Return: 8-bit checksum of all bytes read / written
*/
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
unsigned int offset, unsigned int length,
......
// 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 */
/* SPDX-License-Identifier: GPL-2.0 */
/*
* LPC interface for ChromeOS Embedded Controller
*
* Copyright (C) 2016 Google, Inc
*/
#ifndef __CROS_EC_LPC_REG_H
#define __CROS_EC_LPC_REG_H
/**
* cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address.
* Returns 8-bit checksum of all bytes read.
*
* @offset: Base read address
* @length: Number of bytes to read
* @dest: Destination buffer
*/
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest);
/**
* cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address.
* Returns 8-bit checksum of all bytes written.
*
* @offset: Base write address
* @length: Number of bytes to write
* @msg: Write data buffer
*/
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg);
/**
* cros_ec_lpc_reg_init
*
* Initialize register I/O.
*/
void cros_ec_lpc_reg_init(void);
/**
* cros_ec_lpc_reg_destroy
*
* Cleanup reg I/O.
*/
void cros_ec_lpc_reg_destroy(void);
#endif /* __CROS_EC_LPC_REG_H */
......@@ -12,7 +12,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <uapi/linux/sched/types.h>
/* The header byte, which follows the preamble */
#define EC_MSG_HEADER 0xec
......@@ -67,12 +67,14 @@
* is sent when we want to turn on CS at the start of a transaction.
* @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that
* is sent when we want to turn off CS at the end of a transaction.
* @high_pri_worker: Used to schedule high priority work.
*/
struct cros_ec_spi {
struct spi_device *spi;
s64 last_transfer_ns;
unsigned int start_of_msg_delay;
unsigned int end_of_msg_delay;
struct kthread_worker *high_pri_worker;
};
typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
......@@ -89,7 +91,7 @@ typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
*/
struct cros_ec_xfer_work_params {
struct work_struct work;
struct kthread_work work;
cros_ec_xfer_fn_t fn;
struct cros_ec_device *ec_dev;
struct cros_ec_command *ec_msg;
......@@ -632,7 +634,7 @@ static int do_cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev,
return ret;
}
static void cros_ec_xfer_high_pri_work(struct work_struct *work)
static void cros_ec_xfer_high_pri_work(struct kthread_work *work)
{
struct cros_ec_xfer_work_params *params;
......@@ -644,12 +646,14 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
struct cros_ec_command *ec_msg,
cros_ec_xfer_fn_t fn)
{
struct cros_ec_xfer_work_params params;
INIT_WORK_ONSTACK(&params.work, cros_ec_xfer_high_pri_work);
params.ec_dev = ec_dev;
params.ec_msg = ec_msg;
params.fn = fn;
struct cros_ec_spi *ec_spi = ec_dev->priv;
struct cros_ec_xfer_work_params params = {
.work = KTHREAD_WORK_INIT(params.work,
cros_ec_xfer_high_pri_work),
.ec_dev = ec_dev,
.ec_msg = ec_msg,
.fn = fn,
};
/*
* This looks a bit ridiculous. Why do the work on a
......@@ -660,9 +664,8 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
* context switched out for too long and the EC giving up on
* the transfer.
*/
queue_work(system_highpri_wq, &params.work);
flush_work(&params.work);
destroy_work_on_stack(&params.work);
kthread_queue_work(ec_spi->high_pri_worker, &params.work);
kthread_flush_work(&params.work);
return params.ret;
}
......@@ -694,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
ec_spi->end_of_msg_delay = val;
}
static void cros_ec_spi_high_pri_release(void *worker)
{
kthread_destroy_worker(worker);
}
static int cros_ec_spi_devm_high_pri_alloc(struct device *dev,
struct cros_ec_spi *ec_spi)
{
struct sched_param sched_priority = {
.sched_priority = MAX_RT_PRIO - 1,
};
int err;
ec_spi->high_pri_worker =
kthread_create_worker(0, "cros_ec_spi_high_pri");
if (IS_ERR(ec_spi->high_pri_worker)) {
err = PTR_ERR(ec_spi->high_pri_worker);
dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err);
return err;
}
err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release,
ec_spi->high_pri_worker);
if (err)
return err;
err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task,
SCHED_FIFO, &sched_priority);
if (err)
dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err);
return err;
}
static int cros_ec_spi_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
......@@ -703,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi)
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
spi->rt = true;
err = spi_setup(spi);
if (err < 0)
return err;
......@@ -732,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
ec_spi->last_transfer_ns = ktime_get_ns();
err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi);
if (err)
return err;
err = cros_ec_register(ec_dev);
if (err) {
dev_err(dev, "cannot register EC\n");
......@@ -777,7 +819,7 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id);
static struct spi_driver cros_ec_driver_spi = {
.driver = {
.name = "cros-ec-spi",
.of_match_table = of_match_ptr(cros_ec_spi_of_match),
.of_match_table = cros_ec_spi_of_match,
.pm = &cros_ec_spi_pm_ops,
},
.probe = cros_ec_spi_probe,
......
......@@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
return a->mode;
}
struct attribute_group cros_ec_attr_group = {
static struct attribute_group cros_ec_attr_group = {
.attrs = __ec_attrs,
.is_visible = cros_ec_ctrl_visible,
};
......
......@@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = {
NULL
};
struct attribute_group cros_ec_vbc_attr_group = {
static struct attribute_group cros_ec_vbc_attr_group = {
.name = "vbc",
.bin_attrs = cros_ec_vbc_bin_attrs,
};
......
# SPDX-License-Identifier: GPL-2.0-only
config WILCO_EC
tristate "ChromeOS Wilco Embedded Controller"
depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC
depends on ACPI && X86 && CROS_EC_LPC
help
If you say Y here, you get support for talking to the ChromeOS
Wilco EC over an eSPI bus. This uses a simple byte-level protocol
......@@ -19,3 +19,19 @@ config WILCO_EC_DEBUGFS
manipulation and allow for testing arbitrary commands. This
interface is intended for debug only and will not be present
on production devices.
config WILCO_EC_EVENTS
tristate "Enable event forwarding from EC to userspace"
depends on WILCO_EC
help
If you say Y here, you get support for the EC to send events
(such as power state changes) to userspace. The EC sends the events
over ACPI, and a driver queues up the events to be read by a
userspace daemon from /dev/wilco_event using read() and poll().
config WILCO_EC_TELEMETRY
tristate "Enable querying telemetry data from EC"
depends on WILCO_EC
help
If you say Y here, you get support to query EC telemetry data from
/dev/wilco_telem0 using write() and then read().
# SPDX-License-Identifier: GPL-2.0
wilco_ec-objs := core.o mailbox.o
wilco_ec-objs := core.o mailbox.o properties.o sysfs.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
wilco_ec_debugfs-objs := debugfs.o
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
wilco_ec_events-objs := event.o
obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o
wilco_ec_telem-objs := telemetry.o
obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o
......@@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
ec->dev = dev;
mutex_init(&ec->mailbox_lock);
/* Largest data buffer size requirement is extended data response */
ec->data_size = sizeof(struct wilco_ec_response) +
EC_MAILBOX_DATA_SIZE_EXTENDED;
ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
if (!ec->data_buffer)
return -ENOMEM;
......@@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev)
goto unregister_debugfs;
}
ret = wilco_ec_add_sysfs(ec);
if (ret < 0) {
dev_err(dev, "Failed to create sysfs entries: %d", ret);
goto unregister_rtc;
}
/* Register child device that will be found by the telemetry driver. */
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
PLATFORM_DEVID_AUTO,
ec, sizeof(*ec));
if (IS_ERR(ec->telem_pdev)) {
dev_err(dev, "Failed to create telemetry platform device\n");
ret = PTR_ERR(ec->telem_pdev);
goto remove_sysfs;
}
return 0;
remove_sysfs:
wilco_ec_remove_sysfs(ec);
unregister_rtc:
platform_device_unregister(ec->rtc_pdev);
unregister_debugfs:
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
......@@ -102,6 +120,8 @@ static int wilco_ec_remove(struct platform_device *pdev)
{
struct wilco_ec_device *ec = platform_get_drvdata(pdev);
wilco_ec_remove_sysfs(ec);
platform_device_unregister(ec->telem_pdev);
platform_device_unregister(ec->rtc_pdev);
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
......
......@@ -16,14 +16,14 @@
#define DRV_NAME "wilco-ec-debugfs"
/* The 256 raw bytes will take up more space when represented as a hex string */
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4)
/* The raw bytes will take up more space when represented as a hex string */
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
struct wilco_ec_debugfs {
struct wilco_ec_device *ec;
struct dentry *dir;
size_t response_size;
u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED];
u8 raw_data[EC_MAILBOX_DATA_SIZE];
u8 formatted_data[FORMATTED_BUFFER_SIZE];
};
static struct wilco_ec_debugfs *debug_info;
......@@ -124,12 +124,6 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf,
msg.response_data = debug_info->raw_data;
msg.response_size = EC_MAILBOX_DATA_SIZE;
/* Telemetry commands use extended response data */
if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) {
msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA;
msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED;
}
ret = wilco_ec_mailbox(debug_info->ec, &msg);
if (ret < 0)
return ret;
......
This diff is collapsed.
......@@ -119,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
struct wilco_ec_response *rs;
u8 checksum;
u8 flag;
size_t size;
/* Write request header, then data */
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
......@@ -148,21 +147,11 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
return -EIO;
}
/*
* The EC always returns either EC_MAILBOX_DATA_SIZE or
* EC_MAILBOX_DATA_SIZE_EXTENDED bytes of data, so we need to
* calculate the checksum on **all** of this data, even if we
* won't use all of it.
*/
if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA)
size = EC_MAILBOX_DATA_SIZE_EXTENDED;
else
size = EC_MAILBOX_DATA_SIZE;
/* Read back response */
rs = ec->data_buffer;
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
sizeof(*rs) + size, (u8 *)rs);
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
(u8 *)rs);
if (checksum) {
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
return -EBADMSG;
......@@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
return -EBADMSG;
}
if (rs->data_size != size) {
dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
rs->data_size, size);
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
rs->data_size, EC_MAILBOX_DATA_SIZE);
return -EMSGSIZE;
}
......
// 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);
// 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);
}
This diff is collapsed.
......@@ -155,6 +155,7 @@ struct cros_ec_device {
struct ec_response_get_next_event_v1 event_data;
int event_size;
u32 host_event_wake_mask;
u32 last_resume_result;
};
/**
......
This diff is collapsed.
......@@ -13,12 +13,9 @@
/* Message flags for using the mailbox() interface */
#define WILCO_EC_FLAG_NO_RESPONSE BIT(0) /* EC does not respond */
#define WILCO_EC_FLAG_EXTENDED_DATA BIT(1) /* EC returns 256 data bytes */
/* Normal commands have a maximum 32 bytes of data */
#define EC_MAILBOX_DATA_SIZE 32
/* Extended commands have 256 bytes of response data */
#define EC_MAILBOX_DATA_SIZE_EXTENDED 256
/**
* struct wilco_ec_device - Wilco Embedded Controller handle.
......@@ -32,6 +29,7 @@
* @data_size: Size of the data buffer used for EC communication.
* @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
* @rtc_pdev: The child platform_device used by the RTC sub-driver.
* @telem_pdev: The child platform_device used by the telemetry sub-driver.
*/
struct wilco_ec_device {
struct device *dev;
......@@ -43,6 +41,7 @@ struct wilco_ec_device {
size_t data_size;
struct platform_device *debugfs_pdev;
struct platform_device *rtc_pdev;
struct platform_device *telem_pdev;
};
/**
......@@ -85,14 +84,12 @@ struct wilco_ec_response {
* enum wilco_ec_msg_type - Message type to select a set of command codes.
* @WILCO_EC_MSG_LEGACY: Legacy EC messages for standard EC behavior.
* @WILCO_EC_MSG_PROPERTY: Get/Set/Sync EC controlled NVRAM property.
* @WILCO_EC_MSG_TELEMETRY_SHORT: 32 bytes of telemetry data provided by the EC.
* @WILCO_EC_MSG_TELEMETRY_LONG: 256 bytes of telemetry data provided by the EC.
* @WILCO_EC_MSG_TELEMETRY: Request telemetry data from the EC.
*/
enum wilco_ec_msg_type {
WILCO_EC_MSG_LEGACY = 0x00f0,
WILCO_EC_MSG_PROPERTY = 0x00f2,
WILCO_EC_MSG_TELEMETRY_SHORT = 0x00f5,
WILCO_EC_MSG_TELEMETRY_LONG = 0x00f6,
WILCO_EC_MSG_TELEMETRY = 0x00f5,
};
/**
......@@ -123,4 +120,87 @@ struct wilco_ec_message {
*/
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg);
/*
* A Property is typically a data item that is stored to NVRAM
* by the EC. Each of these data items has an index associated
* with it, known as the Property ID (PID). Properties may have
* variable lengths, up to a max of WILCO_EC_PROPERTY_MAX_SIZE
* bytes. Properties can be simple integers, or they may be more
* complex binary data.
*/
#define WILCO_EC_PROPERTY_MAX_SIZE 4
/**
* struct ec_property_set_msg - Message to get or set a property.
* @property_id: Which property to get or set.
* @length: Number of bytes of |data| that are used.
* @data: Actual property data.
*/
struct wilco_ec_property_msg {
u32 property_id;
int length;
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
};
/**
* wilco_ec_get_property() - Retrieve a property from the EC.
* @ec: Embedded Controller device.
* @prop_msg: Message for request and response.
*
* The property_id field of |prop_msg| should be filled before calling this
* function. The result will be stored in the data and length fields.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_get_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg);
/**
* wilco_ec_set_property() - Store a property on the EC.
* @ec: Embedded Controller device.
* @prop_msg: Message for request and response.
*
* The property_id, length, and data fields of |prop_msg| should be
* filled before calling this function.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_set_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg);
/**
* wilco_ec_get_byte_property() - Retrieve a byte-size property from the EC.
* @ec: Embedded Controller device.
* @property_id: Which property to retrieve.
* @val: The result value, will be filled by this function.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
u8 *val);
/**
* wilco_ec_get_byte_property() - Store a byte-size property on the EC.
* @ec: Embedded Controller device.
* @property_id: Which property to store.
* @val: Value to store.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
u8 val);
/**
* wilco_ec_add_sysfs() - Create sysfs entries
* @ec: Wilco EC device
*
* wilco_ec_remove_sysfs() needs to be called afterwards
* to perform the necessary cleanup.
*
* Return: 0 on success or negative error code on failure.
*/
int wilco_ec_add_sysfs(struct wilco_ec_device *ec);
void wilco_ec_remove_sysfs(struct wilco_ec_device *ec);
#endif /* WILCO_EC_H */
......@@ -38,21 +38,21 @@ static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0);
static int ec_command_get_gain(struct snd_soc_component *component,
struct ec_param_codec_i2s *param,
struct ec_response_codec_gain *resp)
struct ec_codec_i2s_gain *resp)
{
struct cros_ec_codec_data *codec_data =
snd_soc_component_get_drvdata(component);
struct cros_ec_device *ec_device = codec_data->ec_device;
u8 buffer[sizeof(struct cros_ec_command) +
max(sizeof(struct ec_param_codec_i2s),
sizeof(struct ec_response_codec_gain))];
sizeof(struct ec_codec_i2s_gain))];
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
int ret;
msg->version = 0;
msg->command = EC_CMD_CODEC_I2S;
msg->outsize = sizeof(struct ec_param_codec_i2s);
msg->insize = sizeof(struct ec_response_codec_gain);
msg->insize = sizeof(struct ec_codec_i2s_gain);
memcpy(msg->data, param, msg->outsize);
......@@ -226,7 +226,7 @@ static int get_ec_mic_gain(struct snd_soc_component *component,
u8 *left, u8 *right)
{
struct ec_param_codec_i2s param;
struct ec_response_codec_gain resp;
struct ec_codec_i2s_gain resp;
int ret;
param.cmd = EC_CODEC_GET_GAIN;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment