Commit 63de3747 authored by Linus Torvalds's avatar Linus Torvalds

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

Merge tag 'tag-chrome-platform-for-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux

Pull chrome platform changes from Benson Leung:
 "CrOS EC / MFD / IIO:

   - Contains tag-ib-chrome-mfd-iio-input-5.5, which is the first part
     of a series from Gwendal to refactor sensor code between MFD, CrOS
     EC, iio and input in order to add a new sensorhub driver and FIFO
     processing

  Wilco EC:

   - Add support for Dell's USB PowerShare policy control, keyboard
     backlight LED driver, and a new test_event file.

   - Fixes use after free in wilco_ec's telemetry driver.

  Misc:

   - bugfix in cros_usbpd_logger (missing destroy workqueue)"

* tag 'tag-chrome-platform-for-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux:
  platform/chrome: wilco_ec: fix use after free issue
  platform/chrome: cros_ec: Add Kconfig default for cros-ec-sensorhub
  Revert "Input: cros_ec_keyb: mask out extra flags in event_type"
  Revert "Input: cros_ec_keyb - add back missing mask for event_type"
  platform/chrome: cros_ec: handle MKBP more events flag
  platform/chrome: cros_ec: Do not attempt to register a non-positive IRQ number
  platform/chrome: cros-ec: Record event timestamp in the hard irq
  mfd / platform / iio: cros_ec: Register sensor through sensorhub
  iio / platform: cros_ec: Add cros-ec-sensorhub driver
  mfd / platform: cros_ec: Add sensor_count and make check_features public
  platform/chrome: cros_ec: Put docs with the code
  platform/chrome: cros_usbpd_logger: add missed destroy_workqueue in remove
  platform/chrome: cros_ec: Fix Kconfig indentation
  platform/chrome: wilco_ec: Add keyboard backlight LED support
  platform/chrome: wilco_ec: Add charging config driver
  platform/chrome: wilco_ec: Add Dell's USB PowerShare Policy control
  platform/chrome: wilco_ec: Add debugfs test_event file
parents 388c645a 856a0a6e
...@@ -31,6 +31,23 @@ Description: ...@@ -31,6 +31,23 @@ Description:
Output will a version string be similar to the example below: Output will a version string be similar to the example below:
08B6 08B6
What: /sys/bus/platform/devices/GOOG000C\:00/usb_charge
Date: October 2019
KernelVersion: 5.5
Description:
Control the USB PowerShare Policy. USB PowerShare is a policy
which affects charging via the special USB PowerShare port
(marked with a small lightning bolt or battery icon) when in
low power states:
- In S0, the port will always provide power.
- In S0ix, if usb_charge is enabled, then power will be
supplied to the port when on AC or if battery is > 50%.
Else no power is supplied.
- In S5, if usb_charge is enabled, then power will be supplied
to the port when on AC. Else no power is supplied.
Input should be either "0" or "1".
What: /sys/bus/platform/devices/GOOG000C\:00/version What: /sys/bus/platform/devices/GOOG000C\:00/version
Date: May 2019 Date: May 2019
KernelVersion: 5.3 KernelVersion: 5.3
......
...@@ -163,16 +163,10 @@ static const struct iio_chan_spec cros_ec_accel_legacy_channels[] = { ...@@ -163,16 +163,10 @@ static const struct iio_chan_spec cros_ec_accel_legacy_channels[] = {
static int cros_ec_accel_legacy_probe(struct platform_device *pdev) static int cros_ec_accel_legacy_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct cros_ec_dev *ec = dev_get_drvdata(dev->parent);
struct iio_dev *indio_dev; struct iio_dev *indio_dev;
struct cros_ec_sensors_core_state *state; struct cros_ec_sensors_core_state *state;
int ret; int ret;
if (!ec || !ec->ec_dev) {
dev_warn(&pdev->dev, "No EC device found.\n");
return -EINVAL;
}
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state));
if (!indio_dev) if (!indio_dev)
return -ENOMEM; return -ENOMEM;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# #
config IIO_CROS_EC_SENSORS_CORE config IIO_CROS_EC_SENSORS_CORE
tristate "ChromeOS EC Sensors Core" tristate "ChromeOS EC Sensors Core"
depends on SYSFS && CROS_EC depends on SYSFS && CROS_EC_SENSORHUB
select IIO_BUFFER select IIO_BUFFER
select IIO_TRIGGERED_BUFFER select IIO_TRIGGERED_BUFFER
help help
......
...@@ -222,17 +222,11 @@ static const struct iio_info ec_sensors_info = { ...@@ -222,17 +222,11 @@ static const struct iio_info ec_sensors_info = {
static int cros_ec_sensors_probe(struct platform_device *pdev) static int cros_ec_sensors_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
struct iio_dev *indio_dev; struct iio_dev *indio_dev;
struct cros_ec_sensors_state *state; struct cros_ec_sensors_state *state;
struct iio_chan_spec *channel; struct iio_chan_spec *channel;
int ret, i; int ret, i;
if (!ec_dev || !ec_dev->ec_dev) {
dev_warn(&pdev->dev, "No CROS EC device found.\n");
return -EINVAL;
}
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state));
if (!indio_dev) if (!indio_dev)
return -ENOMEM; return -ENOMEM;
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_data/cros_ec_sensorhub.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
static char *cros_ec_loc[] = { static char *cros_ec_loc[] = {
...@@ -88,7 +89,8 @@ int cros_ec_sensors_core_init(struct platform_device *pdev, ...@@ -88,7 +89,8 @@ int cros_ec_sensors_core_init(struct platform_device *pdev,
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); struct cros_ec_sensors_core_state *state = iio_priv(indio_dev);
struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent); struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent);
struct cros_ec_dev *ec = sensor_hub->ec;
struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev);
u32 ver_mask; u32 ver_mask;
int ret, i; int ret, i;
......
...@@ -169,17 +169,11 @@ static const struct iio_info cros_ec_light_prox_info = { ...@@ -169,17 +169,11 @@ static const struct iio_info cros_ec_light_prox_info = {
static int cros_ec_light_prox_probe(struct platform_device *pdev) static int cros_ec_light_prox_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
struct iio_dev *indio_dev; struct iio_dev *indio_dev;
struct cros_ec_light_prox_state *state; struct cros_ec_light_prox_state *state;
struct iio_chan_spec *channel; struct iio_chan_spec *channel;
int ret; int ret;
if (!ec_dev || !ec_dev->ec_dev) {
dev_warn(dev, "No CROS EC device found.\n");
return -EINVAL;
}
indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev) if (!indio_dev)
return -ENOMEM; return -ENOMEM;
......
...@@ -226,8 +226,6 @@ static int cros_ec_keyb_work(struct notifier_block *nb, ...@@ -226,8 +226,6 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
{ {
struct cros_ec_keyb *ckdev = container_of(nb, struct cros_ec_keyb, struct cros_ec_keyb *ckdev = container_of(nb, struct cros_ec_keyb,
notifier); notifier);
uint8_t mkbp_event_type = ckdev->ec->event_data.event_type &
EC_MKBP_EVENT_TYPE_MASK;
u32 val; u32 val;
unsigned int ev_type; unsigned int ev_type;
...@@ -239,7 +237,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb, ...@@ -239,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 (mkbp_event_type) { switch (ckdev->ec->event_data.event_type) {
case EC_MKBP_EVENT_KEY_MATRIX: case EC_MKBP_EVENT_KEY_MATRIX:
pm_wakeup_event(ckdev->dev, 0); pm_wakeup_event(ckdev->dev, 0);
...@@ -266,7 +264,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb, ...@@ -266,7 +264,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
case EC_MKBP_EVENT_SWITCH: case EC_MKBP_EVENT_SWITCH:
pm_wakeup_event(ckdev->dev, 0); pm_wakeup_event(ckdev->dev, 0);
if (mkbp_event_type == EC_MKBP_EVENT_BUTTON) { if (ckdev->ec->event_data.event_type == EC_MKBP_EVENT_BUTTON) {
val = get_unaligned_le32( val = get_unaligned_le32(
&ckdev->ec->event_data.data.buttons); &ckdev->ec->event_data.data.buttons);
ev_type = EV_KEY; ev_type = EV_KEY;
......
...@@ -78,6 +78,10 @@ static const struct mfd_cell cros_ec_rtc_cells[] = { ...@@ -78,6 +78,10 @@ static const struct mfd_cell cros_ec_rtc_cells[] = {
{ .name = "cros-ec-rtc", }, { .name = "cros-ec-rtc", },
}; };
static const struct mfd_cell cros_ec_sensorhub_cells[] = {
{ .name = "cros-ec-sensorhub", },
};
static const struct mfd_cell cros_usbpd_charger_cells[] = { static const struct mfd_cell cros_usbpd_charger_cells[] = {
{ .name = "cros-usbpd-charger", }, { .name = "cros-usbpd-charger", },
{ .name = "cros-usbpd-logger", }, { .name = "cros-usbpd-logger", },
...@@ -112,229 +116,11 @@ static const struct mfd_cell cros_ec_vbc_cells[] = { ...@@ -112,229 +116,11 @@ static const struct mfd_cell cros_ec_vbc_cells[] = {
{ .name = "cros-ec-vbc", } { .name = "cros-ec-vbc", }
}; };
static int cros_ec_check_features(struct cros_ec_dev *ec, int feature)
{
struct cros_ec_command *msg;
int ret;
if (ec->features[0] == -1U && ec->features[1] == -1U) {
/* features bitmap not read yet */
msg = kzalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset;
msg->insize = sizeof(ec->features);
ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
if (ret < 0) {
dev_warn(ec->dev, "cannot get EC features: %d/%d\n",
ret, msg->result);
memset(ec->features, 0, sizeof(ec->features));
} else {
memcpy(ec->features, msg->data, sizeof(ec->features));
}
dev_dbg(ec->dev, "EC features %08x %08x\n",
ec->features[0], ec->features[1]);
kfree(msg);
}
return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature);
}
static void cros_ec_class_release(struct device *dev) static void cros_ec_class_release(struct device *dev)
{ {
kfree(to_cros_ec_dev(dev)); kfree(to_cros_ec_dev(dev));
} }
static void cros_ec_sensors_register(struct cros_ec_dev *ec)
{
/*
* Issue a command to get the number of sensor reported.
* Build an array of sensors driver and register them all.
*/
int ret, i, id, sensor_num;
struct mfd_cell *sensor_cells;
struct cros_ec_sensor_platform *sensor_platforms;
int sensor_type[MOTIONSENSE_TYPE_MAX];
struct ec_params_motion_sense *params;
struct ec_response_motion_sense *resp;
struct cros_ec_command *msg;
msg = kzalloc(sizeof(struct cros_ec_command) +
max(sizeof(*params), sizeof(*resp)), GFP_KERNEL);
if (msg == NULL)
return;
msg->version = 2;
msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
msg->outsize = sizeof(*params);
msg->insize = sizeof(*resp);
params = (struct ec_params_motion_sense *)msg->data;
params->cmd = MOTIONSENSE_CMD_DUMP;
ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
if (ret < 0) {
dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n",
ret, msg->result);
goto error;
}
resp = (struct ec_response_motion_sense *)msg->data;
sensor_num = resp->dump.sensor_count;
/*
* Allocate 2 extra sensors if lid angle sensor and/or FIFO are needed.
*/
sensor_cells = kcalloc(sensor_num + 2, sizeof(struct mfd_cell),
GFP_KERNEL);
if (sensor_cells == NULL)
goto error;
sensor_platforms = kcalloc(sensor_num,
sizeof(struct cros_ec_sensor_platform),
GFP_KERNEL);
if (sensor_platforms == NULL)
goto error_platforms;
memset(sensor_type, 0, sizeof(sensor_type));
id = 0;
for (i = 0; i < sensor_num; i++) {
params->cmd = MOTIONSENSE_CMD_INFO;
params->info.sensor_num = i;
ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
if (ret < 0) {
dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n",
i, ret, msg->result);
continue;
}
switch (resp->info.type) {
case MOTIONSENSE_TYPE_ACCEL:
sensor_cells[id].name = "cros-ec-accel";
break;
case MOTIONSENSE_TYPE_BARO:
sensor_cells[id].name = "cros-ec-baro";
break;
case MOTIONSENSE_TYPE_GYRO:
sensor_cells[id].name = "cros-ec-gyro";
break;
case MOTIONSENSE_TYPE_MAG:
sensor_cells[id].name = "cros-ec-mag";
break;
case MOTIONSENSE_TYPE_PROX:
sensor_cells[id].name = "cros-ec-prox";
break;
case MOTIONSENSE_TYPE_LIGHT:
sensor_cells[id].name = "cros-ec-light";
break;
case MOTIONSENSE_TYPE_ACTIVITY:
sensor_cells[id].name = "cros-ec-activity";
break;
default:
dev_warn(ec->dev, "unknown type %d\n", resp->info.type);
continue;
}
sensor_platforms[id].sensor_num = i;
sensor_cells[id].id = sensor_type[resp->info.type];
sensor_cells[id].platform_data = &sensor_platforms[id];
sensor_cells[id].pdata_size =
sizeof(struct cros_ec_sensor_platform);
sensor_type[resp->info.type]++;
id++;
}
if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2)
ec->has_kb_wake_angle = true;
if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) {
sensor_cells[id].name = "cros-ec-ring";
id++;
}
if (cros_ec_check_features(ec,
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) {
sensor_cells[id].name = "cros-ec-lid-angle";
id++;
}
ret = mfd_add_devices(ec->dev, 0, sensor_cells, id,
NULL, 0, NULL);
if (ret)
dev_err(ec->dev, "failed to add EC sensors\n");
kfree(sensor_platforms);
error_platforms:
kfree(sensor_cells);
error:
kfree(msg);
}
static struct cros_ec_sensor_platform sensor_platforms[] = {
{ .sensor_num = 0 },
{ .sensor_num = 1 }
};
static const struct mfd_cell cros_ec_accel_legacy_cells[] = {
{
.name = "cros-ec-accel-legacy",
.platform_data = &sensor_platforms[0],
.pdata_size = sizeof(struct cros_ec_sensor_platform),
},
{
.name = "cros-ec-accel-legacy",
.platform_data = &sensor_platforms[1],
.pdata_size = sizeof(struct cros_ec_sensor_platform),
}
};
static void cros_ec_accel_legacy_register(struct cros_ec_dev *ec)
{
struct cros_ec_device *ec_dev = ec->ec_dev;
u8 status;
int ret;
/*
* ECs that need legacy support are the main EC, directly connected to
* the AP.
*/
if (ec->cmd_offset != 0)
return;
/*
* Check if EC supports direct memory reads and if EC has
* accelerometers.
*/
if (ec_dev->cmd_readmem) {
ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS, 1,
&status);
if (ret < 0) {
dev_warn(ec->dev, "EC direct read error.\n");
return;
}
/* Check if EC has accelerometers. */
if (!(status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) {
dev_info(ec->dev, "EC does not have accelerometers.\n");
return;
}
}
/*
* The device may still support accelerometers:
* it would be an older ARM based device that do not suppor the
* EC_CMD_GET_FEATURES command.
*
* Register 2 accelerometers, we will fail in the IIO driver if there
* are no sensors.
*/
ret = mfd_add_hotplug_devices(ec->dev, cros_ec_accel_legacy_cells,
ARRAY_SIZE(cros_ec_accel_legacy_cells));
if (ret)
dev_err(ec_dev->dev, "failed to add EC sensors\n");
}
static int ec_device_probe(struct platform_device *pdev) static int ec_device_probe(struct platform_device *pdev)
{ {
int retval = -ENOMEM; int retval = -ENOMEM;
...@@ -390,11 +176,14 @@ static int ec_device_probe(struct platform_device *pdev) ...@@ -390,11 +176,14 @@ static int ec_device_probe(struct platform_device *pdev)
goto failed; goto failed;
/* check whether this EC is a sensor hub. */ /* check whether this EC is a sensor hub. */
if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) if (cros_ec_get_sensor_count(ec) > 0) {
cros_ec_sensors_register(ec); retval = mfd_add_hotplug_devices(ec->dev,
else cros_ec_sensorhub_cells,
/* Workaroud for older EC firmware */ ARRAY_SIZE(cros_ec_sensorhub_cells));
cros_ec_accel_legacy_register(ec); if (retval)
dev_err(ec->dev, "failed to add %s subdevice: %d\n",
cros_ec_sensorhub_cells->name, retval);
}
/* /*
* The following subdevices can be detected by sending the * The following subdevices can be detected by sending the
......
...@@ -190,6 +190,19 @@ config CROS_EC_DEBUGFS ...@@ -190,6 +190,19 @@ config CROS_EC_DEBUGFS
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_debugfs. module will be called cros_ec_debugfs.
config CROS_EC_SENSORHUB
tristate "ChromeOS EC MEMS Sensor Hub"
depends on MFD_CROS_EC_DEV
default MFD_CROS_EC_DEV
help
Allow loading IIO sensors. This driver is loaded by MFD and will in
turn query the EC and register the sensors.
It also spreads the sensor data coming from the EC to the IIO sensor
object.
To compile this driver as a module, choose M here: the
module will be called cros_ec_sensorhub.
config CROS_EC_SYSFS config CROS_EC_SYSFS
tristate "ChromeOS EC control and information through sysfs" tristate "ChromeOS EC control and information through sysfs"
depends on MFD_CROS_EC_DEV && SYSFS depends on MFD_CROS_EC_DEV && SYSFS
......
...@@ -19,6 +19,7 @@ obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o ...@@ -19,6 +19,7 @@ obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o
obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o
obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o
obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o
obj-$(CONFIG_CROS_EC_SENSORHUB) += cros_ec_sensorhub.o
obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o
obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o
......
...@@ -31,13 +31,32 @@ static struct cros_ec_platform pd_p = { ...@@ -31,13 +31,32 @@ static struct cros_ec_platform pd_p = {
.cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX),
}; };
static irqreturn_t ec_irq_thread(int irq, void *data) static irqreturn_t ec_irq_handler(int irq, void *data)
{ {
struct cros_ec_device *ec_dev = data; struct cros_ec_device *ec_dev = data;
bool wake_event = true;
ec_dev->last_event_time = cros_ec_get_time_ns();
return IRQ_WAKE_THREAD;
}
/**
* cros_ec_handle_event() - process and forward pending events on EC
* @ec_dev: Device with events to process.
*
* Call this function in a loop when the kernel is notified that the EC has
* pending events.
*
* Return: true if more events are still pending and this function should be
* called again.
*/
bool cros_ec_handle_event(struct cros_ec_device *ec_dev)
{
bool wake_event;
bool ec_has_more_events;
int ret; int ret;
ret = cros_ec_get_next_event(ec_dev, &wake_event); ret = cros_ec_get_next_event(ec_dev, &wake_event, &ec_has_more_events);
/* /*
* Signal only if wake host events or any interrupt if * Signal only if wake host events or any interrupt if
...@@ -50,6 +69,20 @@ static irqreturn_t ec_irq_thread(int irq, void *data) ...@@ -50,6 +69,20 @@ static irqreturn_t ec_irq_thread(int irq, void *data)
if (ret > 0) if (ret > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier, blocking_notifier_call_chain(&ec_dev->event_notifier,
0, ec_dev); 0, ec_dev);
return ec_has_more_events;
}
EXPORT_SYMBOL(cros_ec_handle_event);
static irqreturn_t ec_irq_thread(int irq, void *data)
{
struct cros_ec_device *ec_dev = data;
bool ec_has_more_events;
do {
ec_has_more_events = cros_ec_handle_event(ec_dev);
} while (ec_has_more_events);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -104,6 +137,15 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) ...@@ -104,6 +137,15 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
return ret; return ret;
} }
/**
* cros_ec_register() - Register a new ChromeOS EC, using the provided info.
* @ec_dev: Device to register.
*
* Before calling this, allocate a pointer to a new device and then fill
* in all the fields up to the --private-- marker.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_register(struct cros_ec_device *ec_dev) int cros_ec_register(struct cros_ec_device *ec_dev)
{ {
struct device *dev = ec_dev->dev; struct device *dev = ec_dev->dev;
...@@ -131,9 +173,11 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -131,9 +173,11 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
return err; return err;
} }
if (ec_dev->irq) { if (ec_dev->irq > 0) {
err = devm_request_threaded_irq(dev, ec_dev->irq, NULL, err = devm_request_threaded_irq(dev, ec_dev->irq,
ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, ec_irq_handler,
ec_irq_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"chromeos-ec", ec_dev); "chromeos-ec", ec_dev);
if (err) { if (err) {
dev_err(dev, "Failed to request IRQ %d: %d", dev_err(dev, "Failed to request IRQ %d: %d",
...@@ -198,6 +242,14 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -198,6 +242,14 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
} }
EXPORT_SYMBOL(cros_ec_register); EXPORT_SYMBOL(cros_ec_register);
/**
* cros_ec_unregister() - Remove a ChromeOS EC.
* @ec_dev: Device to unregister.
*
* Call this to deregister a ChromeOS EC, then clean up any private data.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_unregister(struct cros_ec_device *ec_dev) int cros_ec_unregister(struct cros_ec_device *ec_dev)
{ {
if (ec_dev->pd) if (ec_dev->pd)
...@@ -209,6 +261,14 @@ int cros_ec_unregister(struct cros_ec_device *ec_dev) ...@@ -209,6 +261,14 @@ int cros_ec_unregister(struct cros_ec_device *ec_dev)
EXPORT_SYMBOL(cros_ec_unregister); EXPORT_SYMBOL(cros_ec_unregister);
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
/**
* cros_ec_suspend() - Handle a suspend operation for the ChromeOS EC device.
* @ec_dev: Device to suspend.
*
* This can be called by drivers to handle a suspend event.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_suspend(struct cros_ec_device *ec_dev) int cros_ec_suspend(struct cros_ec_device *ec_dev)
{ {
struct device *dev = ec_dev->dev; struct device *dev = ec_dev->dev;
...@@ -238,11 +298,19 @@ EXPORT_SYMBOL(cros_ec_suspend); ...@@ -238,11 +298,19 @@ EXPORT_SYMBOL(cros_ec_suspend);
static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev)
{ {
while (ec_dev->mkbp_event_supported && while (ec_dev->mkbp_event_supported &&
cros_ec_get_next_event(ec_dev, NULL) > 0) cros_ec_get_next_event(ec_dev, NULL, NULL) > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier, blocking_notifier_call_chain(&ec_dev->event_notifier,
1, ec_dev); 1, ec_dev);
} }
/**
* cros_ec_resume() - Handle a resume operation for the ChromeOS EC device.
* @ec_dev: Device to resume.
*
* This can be called by drivers to handle a resume event.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_resume(struct cros_ec_device *ec_dev) int cros_ec_resume(struct cros_ec_device *ec_dev)
{ {
int ret; int ret;
......
...@@ -136,11 +136,11 @@ static void ish_evt_handler(struct work_struct *work) ...@@ -136,11 +136,11 @@ static void ish_evt_handler(struct work_struct *work)
struct ishtp_cl_data *client_data = struct ishtp_cl_data *client_data =
container_of(work, struct ishtp_cl_data, work_ec_evt); container_of(work, struct ishtp_cl_data, work_ec_evt);
struct cros_ec_device *ec_dev = client_data->ec_dev; struct cros_ec_device *ec_dev = client_data->ec_dev;
bool ec_has_more_events;
if (cros_ec_get_next_event(ec_dev, NULL) > 0) { do {
blocking_notifier_call_chain(&ec_dev->event_notifier, ec_has_more_events = cros_ec_handle_event(ec_dev);
0, ec_dev); } while (ec_has_more_events);
}
} }
/** /**
...@@ -200,13 +200,14 @@ static int ish_send(struct ishtp_cl_data *client_data, ...@@ -200,13 +200,14 @@ static int ish_send(struct ishtp_cl_data *client_data,
* process_recv() - Received and parse incoming packet * process_recv() - Received and parse incoming packet
* @cros_ish_cl: Client instance to get stats * @cros_ish_cl: Client instance to get stats
* @rb_in_proc: Host interface message buffer * @rb_in_proc: Host interface message buffer
* @timestamp: Timestamp of when parent callback started
* *
* Parse the incoming packet. If it is a response packet then it will * 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 * 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. * response. If it is an event packet then it will schedule event work.
*/ */
static void process_recv(struct ishtp_cl *cros_ish_cl, static void process_recv(struct ishtp_cl *cros_ish_cl,
struct ishtp_cl_rb *rb_in_proc) struct ishtp_cl_rb *rb_in_proc, ktime_t timestamp)
{ {
size_t data_len = rb_in_proc->buf_idx; size_t data_len = rb_in_proc->buf_idx;
struct ishtp_cl_data *client_data = struct ishtp_cl_data *client_data =
...@@ -295,6 +296,11 @@ static void process_recv(struct ishtp_cl *cros_ish_cl, ...@@ -295,6 +296,11 @@ static void process_recv(struct ishtp_cl *cros_ish_cl,
break; break;
case CROS_MKBP_EVENT: case CROS_MKBP_EVENT:
/*
* Set timestamp from beginning of function since we actually
* got an incoming MKBP event
*/
client_data->ec_dev->last_event_time = timestamp;
/* The event system doesn't send any data in buffer */ /* The event system doesn't send any data in buffer */
schedule_work(&client_data->work_ec_evt); schedule_work(&client_data->work_ec_evt);
...@@ -322,10 +328,17 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device) ...@@ -322,10 +328,17 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device)
{ {
struct ishtp_cl_rb *rb_in_proc; struct ishtp_cl_rb *rb_in_proc;
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
ktime_t timestamp;
/*
* Take timestamp as close to hardware interrupt as possible for sensor
* timestamps.
*/
timestamp = cros_ec_get_time_ns();
while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) { while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
/* Decide what to do with received data */ /* Decide what to do with received data */
process_recv(cros_ish_cl, rb_in_proc); process_recv(cros_ish_cl, rb_in_proc, timestamp);
} }
} }
......
...@@ -312,11 +312,20 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, ...@@ -312,11 +312,20 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data) static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data)
{ {
struct cros_ec_device *ec_dev = data; struct cros_ec_device *ec_dev = data;
bool ec_has_more_events;
int ret;
ec_dev->last_event_time = cros_ec_get_time_ns();
if (ec_dev->mkbp_event_supported && if (ec_dev->mkbp_event_supported)
cros_ec_get_next_event(ec_dev, NULL) > 0) do {
blocking_notifier_call_chain(&ec_dev->event_notifier, 0, ret = cros_ec_get_next_event(ec_dev, NULL,
&ec_has_more_events);
if (ret > 0)
blocking_notifier_call_chain(
&ec_dev->event_notifier, 0,
ec_dev); ec_dev);
} while (ec_has_more_events);
if (value == ACPI_NOTIFY_DEVICE_WAKE) if (value == ACPI_NOTIFY_DEVICE_WAKE)
pm_system_wakeup(); pm_system_wakeup();
......
This diff is collapsed.
...@@ -143,22 +143,11 @@ cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work) ...@@ -143,22 +143,11 @@ cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work)
struct cros_ec_rpmsg, struct cros_ec_rpmsg,
host_event_work); host_event_work);
struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev); struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev);
bool wake_event = true; bool ec_has_more_events;
int ret;
ret = cros_ec_get_next_event(ec_dev, &wake_event);
/*
* Signal only if wake host events or any interrupt if
* cros_ec_get_next_event() returned an error (default value for
* wake_event is true)
*/
if (wake_event && device_may_wakeup(ec_dev->dev))
pm_wakeup_event(ec_dev->dev, 0);
if (ret > 0) do {
blocking_notifier_call_chain(&ec_dev->event_notifier, ec_has_more_events = cros_ec_handle_event(ec_dev);
0, ec_dev); } while (ec_has_more_events);
} }
static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data, static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
......
// SPDX-License-Identifier: GPL-2.0
/*
* Sensor HUB driver that discovers sensors behind a ChromeOS Embedded
* Controller.
*
* Copyright 2019 Google LLC
*/
#include <linux/init.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mfd/cros_ec.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_data/cros_ec_sensorhub.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#define DRV_NAME "cros-ec-sensorhub"
static void cros_ec_sensorhub_free_sensor(void *arg)
{
struct platform_device *pdev = arg;
platform_device_unregister(pdev);
}
static int cros_ec_sensorhub_allocate_sensor(struct device *parent,
char *sensor_name,
int sensor_num)
{
struct cros_ec_sensor_platform sensor_platforms = {
.sensor_num = sensor_num,
};
struct platform_device *pdev;
pdev = platform_device_register_data(parent, sensor_name,
PLATFORM_DEVID_AUTO,
&sensor_platforms,
sizeof(sensor_platforms));
if (IS_ERR(pdev))
return PTR_ERR(pdev);
return devm_add_action_or_reset(parent,
cros_ec_sensorhub_free_sensor,
pdev);
}
static int cros_ec_sensorhub_register(struct device *dev,
struct cros_ec_sensorhub *sensorhub)
{
int sensor_type[MOTIONSENSE_TYPE_MAX] = { 0 };
struct cros_ec_dev *ec = sensorhub->ec;
struct ec_params_motion_sense *params;
struct ec_response_motion_sense *resp;
struct cros_ec_command *msg;
int ret, i, sensor_num;
char *name;
sensor_num = cros_ec_get_sensor_count(ec);
if (sensor_num < 0) {
dev_err(dev,
"Unable to retrieve sensor information (err:%d)\n",
sensor_num);
return sensor_num;
}
if (sensor_num == 0) {
dev_err(dev, "Zero sensors reported.\n");
return -EINVAL;
}
/* Prepare a message to send INFO command to each sensor. */
msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)),
GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->version = 1;
msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
msg->outsize = sizeof(*params);
msg->insize = sizeof(*resp);
params = (struct ec_params_motion_sense *)msg->data;
resp = (struct ec_response_motion_sense *)msg->data;
for (i = 0; i < sensor_num; i++) {
params->cmd = MOTIONSENSE_CMD_INFO;
params->info.sensor_num = i;
ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
if (ret < 0) {
dev_warn(dev, "no info for EC sensor %d : %d/%d\n",
i, ret, msg->result);
continue;
}
switch (resp->info.type) {
case MOTIONSENSE_TYPE_ACCEL:
name = "cros-ec-accel";
break;
case MOTIONSENSE_TYPE_BARO:
name = "cros-ec-baro";
break;
case MOTIONSENSE_TYPE_GYRO:
name = "cros-ec-gyro";
break;
case MOTIONSENSE_TYPE_MAG:
name = "cros-ec-mag";
break;
case MOTIONSENSE_TYPE_PROX:
name = "cros-ec-prox";
break;
case MOTIONSENSE_TYPE_LIGHT:
name = "cros-ec-light";
break;
case MOTIONSENSE_TYPE_ACTIVITY:
name = "cros-ec-activity";
break;
default:
dev_warn(dev, "unknown type %d\n", resp->info.type);
continue;
}
ret = cros_ec_sensorhub_allocate_sensor(dev, name, i);
if (ret)
goto error;
sensor_type[resp->info.type]++;
}
if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2)
ec->has_kb_wake_angle = true;
if (cros_ec_check_features(ec,
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) {
ret = cros_ec_sensorhub_allocate_sensor(dev,
"cros-ec-lid-angle",
0);
if (ret)
goto error;
}
kfree(msg);
return 0;
error:
kfree(msg);
return ret;
}
static int cros_ec_sensorhub_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_sensorhub *data;
int ret;
int i;
data = devm_kzalloc(dev, sizeof(struct cros_ec_sensorhub), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->ec = dev_get_drvdata(dev->parent);
dev_set_drvdata(dev, data);
/* Check whether this EC is a sensor hub. */
if (cros_ec_check_features(data->ec, EC_FEATURE_MOTION_SENSE)) {
ret = cros_ec_sensorhub_register(dev, data);
if (ret)
return ret;
} else {
/*
* If the device has sensors but does not claim to
* be a sensor hub, we are in legacy mode.
*/
for (i = 0; i < 2; i++) {
ret = cros_ec_sensorhub_allocate_sensor(dev,
"cros-ec-accel-legacy", i);
if (ret)
return ret;
}
}
return 0;
}
static struct platform_driver cros_ec_sensorhub_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = cros_ec_sensorhub_probe,
};
module_platform_driver(cros_ec_sensorhub_driver);
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
MODULE_DESCRIPTION("ChromeOS EC MEMS Sensor Hub Driver");
MODULE_LICENSE("GPL");
...@@ -224,6 +224,7 @@ static int cros_usbpd_logger_remove(struct platform_device *pd) ...@@ -224,6 +224,7 @@ static int cros_usbpd_logger_remove(struct platform_device *pd)
struct logger_data *logger = platform_get_drvdata(pd); struct logger_data *logger = platform_get_drvdata(pd);
cancel_delayed_work_sync(&logger->log_work); cancel_delayed_work_sync(&logger->log_work);
destroy_workqueue(logger->log_workqueue);
return 0; return 0;
} }
......
# 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 depends on ACPI && X86 && CROS_EC_LPC && LEDS_CLASS
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
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
wilco_ec-objs := core.o mailbox.o properties.o sysfs.o wilco_ec-objs := core.o keyboard_leds.o mailbox.o \
properties.o sysfs.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o 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
......
...@@ -5,10 +5,6 @@ ...@@ -5,10 +5,6 @@
* Copyright 2018 Google LLC * Copyright 2018 Google LLC
* *
* This is the entry point for the drivers that control the Wilco EC. * This is the entry point for the drivers that control the Wilco EC.
* This driver is responsible for several tasks:
* - Initialize the register interface that is used by wilco_ec_mailbox()
* - Create a platform device which is picked up by the debugfs driver
* - Create a platform device which is picked up by the RTC driver
*/ */
#include <linux/acpi.h> #include <linux/acpi.h>
...@@ -87,12 +83,31 @@ static int wilco_ec_probe(struct platform_device *pdev) ...@@ -87,12 +83,31 @@ static int wilco_ec_probe(struct platform_device *pdev)
goto unregister_debugfs; goto unregister_debugfs;
} }
/* Set up the keyboard backlight LEDs. */
ret = wilco_keyboard_leds_init(ec);
if (ret < 0) {
dev_err(dev,
"Failed to initialize keyboard LEDs: %d\n",
ret);
goto unregister_rtc;
}
ret = wilco_ec_add_sysfs(ec); ret = wilco_ec_add_sysfs(ec);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "Failed to create sysfs entries: %d", ret); dev_err(dev, "Failed to create sysfs entries: %d", ret);
goto unregister_rtc; goto unregister_rtc;
} }
/* Register child device to be found by charger config driver. */
ec->charger_pdev = platform_device_register_data(dev, "wilco-charger",
PLATFORM_DEVID_AUTO,
NULL, 0);
if (IS_ERR(ec->charger_pdev)) {
dev_err(dev, "Failed to create charger platform device\n");
ret = PTR_ERR(ec->charger_pdev);
goto remove_sysfs;
}
/* Register child device that will be found by the telemetry driver. */ /* Register child device that will be found by the telemetry driver. */
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem", ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
PLATFORM_DEVID_AUTO, PLATFORM_DEVID_AUTO,
...@@ -100,11 +115,13 @@ static int wilco_ec_probe(struct platform_device *pdev) ...@@ -100,11 +115,13 @@ static int wilco_ec_probe(struct platform_device *pdev)
if (IS_ERR(ec->telem_pdev)) { if (IS_ERR(ec->telem_pdev)) {
dev_err(dev, "Failed to create telemetry platform device\n"); dev_err(dev, "Failed to create telemetry platform device\n");
ret = PTR_ERR(ec->telem_pdev); ret = PTR_ERR(ec->telem_pdev);
goto remove_sysfs; goto unregister_charge_config;
} }
return 0; return 0;
unregister_charge_config:
platform_device_unregister(ec->charger_pdev);
remove_sysfs: remove_sysfs:
wilco_ec_remove_sysfs(ec); wilco_ec_remove_sysfs(ec);
unregister_rtc: unregister_rtc:
...@@ -120,6 +137,7 @@ static int wilco_ec_remove(struct platform_device *pdev) ...@@ -120,6 +137,7 @@ 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);
platform_device_unregister(ec->charger_pdev);
wilco_ec_remove_sysfs(ec); wilco_ec_remove_sysfs(ec);
platform_device_unregister(ec->telem_pdev); platform_device_unregister(ec->telem_pdev);
platform_device_unregister(ec->rtc_pdev); platform_device_unregister(ec->rtc_pdev);
......
...@@ -160,29 +160,29 @@ static const struct file_operations fops_raw = { ...@@ -160,29 +160,29 @@ static const struct file_operations fops_raw = {
#define CMD_KB_CHROME 0x88 #define CMD_KB_CHROME 0x88
#define SUB_CMD_H1_GPIO 0x0A #define SUB_CMD_H1_GPIO 0x0A
#define SUB_CMD_TEST_EVENT 0x0B
struct h1_gpio_status_request { struct ec_request {
u8 cmd; /* Always CMD_KB_CHROME */ u8 cmd; /* Always CMD_KB_CHROME */
u8 reserved; u8 reserved;
u8 sub_cmd; /* Always SUB_CMD_H1_GPIO */ u8 sub_cmd;
} __packed; } __packed;
struct hi_gpio_status_response { struct ec_response {
u8 status; /* 0 if allowed */ u8 status; /* 0 if allowed */
u8 val; /* BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL */ u8 val;
} __packed; } __packed;
static int h1_gpio_get(void *arg, u64 *val) static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val)
{ {
struct wilco_ec_device *ec = arg; struct ec_request rq;
struct h1_gpio_status_request rq; struct ec_response rs;
struct hi_gpio_status_response rs;
struct wilco_ec_message msg; struct wilco_ec_message msg;
int ret; int ret;
memset(&rq, 0, sizeof(rq)); memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_KB_CHROME; rq.cmd = CMD_KB_CHROME;
rq.sub_cmd = SUB_CMD_H1_GPIO; rq.sub_cmd = sub_cmd;
memset(&msg, 0, sizeof(msg)); memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY; msg.type = WILCO_EC_MSG_LEGACY;
...@@ -196,13 +196,38 @@ static int h1_gpio_get(void *arg, u64 *val) ...@@ -196,13 +196,38 @@ static int h1_gpio_get(void *arg, u64 *val)
if (rs.status) if (rs.status)
return -EIO; return -EIO;
*val = rs.val; *out_val = rs.val;
return 0; return 0;
} }
/**
* h1_gpio_get() - Gets h1 gpio status.
* @arg: The wilco EC device.
* @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL
*/
static int h1_gpio_get(void *arg, u64 *val)
{
return send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val);
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n"); DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n");
/**
* test_event_set() - Sends command to EC to cause an EC test event.
* @arg: The wilco EC device.
* @val: unused.
*/
static int test_event_set(void *arg, u64 val)
{
u8 ret;
return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret);
}
/* Format is unused since it is only required for get method which is NULL */
DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n");
/** /**
* wilco_ec_debugfs_probe() - Create the debugfs node * wilco_ec_debugfs_probe() - Create the debugfs node
* @pdev: The platform device, probably created in core.c * @pdev: The platform device, probably created in core.c
...@@ -226,6 +251,8 @@ static int wilco_ec_debugfs_probe(struct platform_device *pdev) ...@@ -226,6 +251,8 @@ static int wilco_ec_debugfs_probe(struct platform_device *pdev)
debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw); debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);
debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec, debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec,
&fops_h1_gpio); &fops_h1_gpio);
debugfs_create_file("test_event", 0200, debug_info->dir, ec,
&fops_test_event);
return 0; return 0;
} }
......
// SPDX-License-Identifier: GPL-2.0
/*
* Keyboard backlight LED driver for the Wilco Embedded Controller
*
* Copyright 2019 Google LLC
*
* Since the EC will never change the backlight level of its own accord,
* we don't need to implement a brightness_get() method.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/slab.h>
#define WILCO_EC_COMMAND_KBBL 0x75
#define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */
#define WILCO_KBBL_DEFAULT_BRIGHTNESS 0
struct wilco_keyboard_leds {
struct wilco_ec_device *ec;
struct led_classdev keyboard;
};
enum wilco_kbbl_subcommand {
WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00,
WILCO_KBBL_SUBCMD_GET_STATE = 0x01,
WILCO_KBBL_SUBCMD_SET_STATE = 0x02,
};
/**
* struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control.
* @command: Always WILCO_EC_COMMAND_KBBL.
* @status: Set by EC to 0 on success, 0xFF on failure.
* @subcmd: One of enum wilco_kbbl_subcommand.
* @reserved3: Should be 0.
* @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM.
* @reserved5to8: Should be 0.
* @percent: Brightness in 0-100. Only meaningful in PWM mode.
* @reserved10to15: Should be 0.
*/
struct wilco_keyboard_leds_msg {
u8 command;
u8 status;
u8 subcmd;
u8 reserved3;
u8 mode;
u8 reserved5to8[4];
u8 percent;
u8 reserved10to15[6];
} __packed;
/* Send a request, get a response, and check that the response is good. */
static int send_kbbl_msg(struct wilco_ec_device *ec,
struct wilco_keyboard_leds_msg *request,
struct wilco_keyboard_leds_msg *response)
{
struct wilco_ec_message msg;
int ret;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = request;
msg.request_size = sizeof(*request);
msg.response_data = response;
msg.response_size = sizeof(*response);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0) {
dev_err(ec->dev,
"Failed sending keyboard LEDs command: %d", ret);
return ret;
}
if (response->status) {
dev_err(ec->dev,
"EC reported failure sending keyboard LEDs command: %d",
response->status);
return -EIO;
}
return 0;
}
static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE;
request.mode = WILCO_KBBL_MODE_FLAG_PWM;
request.percent = brightness;
return send_kbbl_msg(ec, &request, &response);
}
static int kbbl_exist(struct wilco_ec_device *ec, bool *exists)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
int ret;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES;
ret = send_kbbl_msg(ec, &request, &response);
if (ret < 0)
return ret;
*exists = response.status != 0xFF;
return 0;
}
/**
* kbbl_init() - Initialize the state of the keyboard backlight.
* @ec: EC device to talk to.
*
* Gets the current brightness, ensuring that the BIOS already initialized the
* backlight to PWM mode. If not in PWM mode, then the current brightness is
* meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS.
*
* Return: Final brightness of the keyboard, or negative error code on failure.
*/
static int kbbl_init(struct wilco_ec_device *ec)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
int ret;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE;
ret = send_kbbl_msg(ec, &request, &response);
if (ret < 0)
return ret;
if (response.mode & WILCO_KBBL_MODE_FLAG_PWM)
return response.percent;
ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS);
if (ret < 0)
return ret;
return WILCO_KBBL_DEFAULT_BRIGHTNESS;
}
static int wilco_keyboard_leds_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct wilco_keyboard_leds *wkl =
container_of(cdev, struct wilco_keyboard_leds, keyboard);
return set_kbbl(wkl->ec, brightness);
}
int wilco_keyboard_leds_init(struct wilco_ec_device *ec)
{
struct wilco_keyboard_leds *wkl;
bool leds_exist;
int ret;
ret = kbbl_exist(ec, &leds_exist);
if (ret < 0) {
dev_err(ec->dev,
"Failed checking keyboard LEDs support: %d", ret);
return ret;
}
if (!leds_exist)
return 0;
wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL);
if (!wkl)
return -ENOMEM;
wkl->ec = ec;
wkl->keyboard.name = "platform::kbd_backlight";
wkl->keyboard.max_brightness = 100;
wkl->keyboard.flags = LED_CORE_SUSPENDRESUME;
wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set;
ret = kbbl_init(ec);
if (ret < 0)
return ret;
wkl->keyboard.brightness = ret;
return devm_led_classdev_register(ec->dev, &wkl->keyboard);
}
...@@ -23,6 +23,26 @@ struct boot_on_ac_request { ...@@ -23,6 +23,26 @@ struct boot_on_ac_request {
u8 reserved7; u8 reserved7;
} __packed; } __packed;
#define CMD_USB_CHARGE 0x39
enum usb_charge_op {
USB_CHARGE_GET = 0,
USB_CHARGE_SET = 1,
};
struct usb_charge_request {
u8 cmd; /* Always CMD_USB_CHARGE */
u8 reserved;
u8 op; /* One of enum usb_charge_op */
u8 val; /* When setting, either 0 or 1 */
} __packed;
struct usb_charge_response {
u8 reserved;
u8 status; /* Set by EC to 0 on success, other value on failure */
u8 val; /* When getting, set by EC to either 0 or 1 */
} __packed;
#define CMD_EC_INFO 0x38 #define CMD_EC_INFO 0x38
enum get_ec_info_op { enum get_ec_info_op {
CMD_GET_EC_LABEL = 0, CMD_GET_EC_LABEL = 0,
...@@ -131,12 +151,83 @@ static ssize_t model_number_show(struct device *dev, ...@@ -131,12 +151,83 @@ static ssize_t model_number_show(struct device *dev,
static DEVICE_ATTR_RO(model_number); static DEVICE_ATTR_RO(model_number);
static int send_usb_charge(struct wilco_ec_device *ec,
struct usb_charge_request *rq,
struct usb_charge_response *rs)
{
struct wilco_ec_message msg;
int ret;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = rq;
msg.request_size = sizeof(*rq);
msg.response_data = rs;
msg.response_size = sizeof(*rs);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0)
return ret;
if (rs->status)
return -EIO;
return 0;
}
static ssize_t usb_charge_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct usb_charge_request rq;
struct usb_charge_response rs;
int ret;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_USB_CHARGE;
rq.op = USB_CHARGE_GET;
ret = send_usb_charge(ec, &rq, &rs);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", rs.val);
}
static ssize_t usb_charge_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct usb_charge_request rq;
struct usb_charge_response rs;
int ret;
u8 val;
ret = kstrtou8(buf, 10, &val);
if (ret < 0)
return ret;
if (val > 1)
return -EINVAL;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_USB_CHARGE;
rq.op = USB_CHARGE_SET;
rq.val = val;
ret = send_usb_charge(ec, &rq, &rs);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(usb_charge);
static struct attribute *wilco_dev_attrs[] = { static struct attribute *wilco_dev_attrs[] = {
&dev_attr_boot_on_ac.attr, &dev_attr_boot_on_ac.attr,
&dev_attr_build_date.attr, &dev_attr_build_date.attr,
&dev_attr_build_revision.attr, &dev_attr_build_revision.attr,
&dev_attr_model_number.attr, &dev_attr_model_number.attr,
&dev_attr_usb_charge.attr,
&dev_attr_version.attr, &dev_attr_version.attr,
NULL, NULL,
}; };
......
...@@ -406,8 +406,8 @@ static int telem_device_remove(struct platform_device *pdev) ...@@ -406,8 +406,8 @@ static int telem_device_remove(struct platform_device *pdev)
struct telem_device_data *dev_data = platform_get_drvdata(pdev); struct telem_device_data *dev_data = platform_get_drvdata(pdev);
cdev_device_del(&dev_data->cdev, &dev_data->dev); cdev_device_del(&dev_data->cdev, &dev_data->dev);
put_device(&dev_data->dev);
ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
put_device(&dev_data->dev);
return 0; return 0;
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/mfd/cros_ec.h>
#include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_commands.h>
#define CROS_EC_DEV_NAME "cros_ec" #define CROS_EC_DEV_NAME "cros_ec"
...@@ -115,12 +116,16 @@ struct cros_ec_command { ...@@ -115,12 +116,16 @@ struct cros_ec_command {
* code. * code.
* @pkt_xfer: Send packet to EC and get response. * @pkt_xfer: Send packet to EC and get response.
* @lock: One transaction at a time. * @lock: One transaction at a time.
* @mkbp_event_supported: True if this EC supports the MKBP event protocol. * @mkbp_event_supported: 0 if MKBP not supported. Otherwise its value is
* the maximum supported version of the MKBP host event
* command + 1.
* @host_sleep_v1: True if this EC supports the sleep v1 command. * @host_sleep_v1: True if this EC supports the sleep v1 command.
* @event_notifier: Interrupt event notifier for transport devices. * @event_notifier: Interrupt event notifier for transport devices.
* @event_data: Raw payload transferred with the MKBP event. * @event_data: Raw payload transferred with the MKBP event.
* @event_size: Size in bytes of the event data. * @event_size: Size in bytes of the event data.
* @host_event_wake_mask: Mask of host events that cause wake from suspend. * @host_event_wake_mask: Mask of host events that cause wake from suspend.
* @last_event_time: exact time from the hard irq when we got notified of
* a new event.
* @ec: The platform_device used by the mfd driver to interface with the * @ec: The platform_device used by the mfd driver to interface with the
* main EC. * main EC.
* @pd: The platform_device used by the mfd driver to interface with the * @pd: The platform_device used by the mfd driver to interface with the
...@@ -153,7 +158,7 @@ struct cros_ec_device { ...@@ -153,7 +158,7 @@ struct cros_ec_device {
int (*pkt_xfer)(struct cros_ec_device *ec, int (*pkt_xfer)(struct cros_ec_device *ec,
struct cros_ec_command *msg); struct cros_ec_command *msg);
struct mutex lock; struct mutex lock;
bool mkbp_event_supported; u8 mkbp_event_supported;
bool host_sleep_v1; bool host_sleep_v1;
struct blocking_notifier_head event_notifier; struct blocking_notifier_head event_notifier;
...@@ -161,20 +166,13 @@ struct cros_ec_device { ...@@ -161,20 +166,13 @@ struct cros_ec_device {
int event_size; int event_size;
u32 host_event_wake_mask; u32 host_event_wake_mask;
u32 last_resume_result; u32 last_resume_result;
ktime_t last_event_time;
/* The platform devices used by the mfd driver */ /* The platform devices used by the mfd driver */
struct platform_device *ec; struct platform_device *ec;
struct platform_device *pd; struct platform_device *pd;
}; };
/**
* struct cros_ec_sensor_platform - ChromeOS EC sensor platform information.
* @sensor_num: Id of the sensor, as reported by the EC.
*/
struct cros_ec_sensor_platform {
u8 sensor_num;
};
/** /**
* struct cros_ec_platform - ChromeOS EC platform information. * struct cros_ec_platform - ChromeOS EC platform information.
* @ec_name: Name of EC device (e.g. 'cros-ec', 'cros-pd', ...) * @ec_name: Name of EC device (e.g. 'cros-ec', 'cros-pd', ...)
...@@ -187,133 +185,51 @@ struct cros_ec_platform { ...@@ -187,133 +185,51 @@ struct cros_ec_platform {
u16 cmd_offset; u16 cmd_offset;
}; };
/**
* cros_ec_suspend() - Handle a suspend operation for the ChromeOS EC device.
* @ec_dev: Device to suspend.
*
* This can be called by drivers to handle a suspend event.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_suspend(struct cros_ec_device *ec_dev); int cros_ec_suspend(struct cros_ec_device *ec_dev);
/**
* cros_ec_resume() - Handle a resume operation for the ChromeOS EC device.
* @ec_dev: Device to resume.
*
* This can be called by drivers to handle a resume event.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_resume(struct cros_ec_device *ec_dev); int cros_ec_resume(struct cros_ec_device *ec_dev);
/**
* cros_ec_prepare_tx() - Prepare an outgoing message in the output buffer.
* @ec_dev: Device to register.
* @msg: Message to write.
*
* This is intended to be used by all ChromeOS EC drivers, but at present
* only SPI uses it. Once LPC uses the same protocol it can start using it.
* I2C could use it now, with a refactor of the existing code.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg); struct cros_ec_command *msg);
/**
* cros_ec_check_result() - Check ec_msg->result.
* @ec_dev: EC device.
* @msg: Message to check.
*
* This is used by ChromeOS EC drivers to check the ec_msg->result for
* errors and to warn about them.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_check_result(struct cros_ec_device *ec_dev, int cros_ec_check_result(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg); struct cros_ec_command *msg);
/**
* cros_ec_cmd_xfer() - Send a command to the ChromeOS EC.
* @ec_dev: EC device.
* @msg: Message to write.
*
* Call this to send a command to the ChromeOS EC. This should be used
* instead of calling the EC's cmd_xfer() callback directly.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg); struct cros_ec_command *msg);
/**
* cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC.
* @ec_dev: EC device.
* @msg: Message to write.
*
* This function is identical to cros_ec_cmd_xfer, except it returns success
* status only if both the command was transmitted successfully and the EC
* replied with success status. It's not necessary to check msg->result when
* using this function.
*
* Return: The number of bytes transferred on success or negative error code.
*/
int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev,
struct cros_ec_command *msg); struct cros_ec_command *msg);
/**
* cros_ec_register() - Register a new ChromeOS EC, using the provided info.
* @ec_dev: Device to register.
*
* Before calling this, allocate a pointer to a new device and then fill
* in all the fields up to the --private-- marker.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_register(struct cros_ec_device *ec_dev); int cros_ec_register(struct cros_ec_device *ec_dev);
/**
* cros_ec_unregister() - Remove a ChromeOS EC.
* @ec_dev: Device to unregister.
*
* Call this to deregister a ChromeOS EC, then clean up any private data.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_unregister(struct cros_ec_device *ec_dev); int cros_ec_unregister(struct cros_ec_device *ec_dev);
/**
* cros_ec_query_all() - Query the protocol version supported by the
* ChromeOS EC.
* @ec_dev: Device to register.
*
* Return: 0 on success or negative error code.
*/
int cros_ec_query_all(struct cros_ec_device *ec_dev); int cros_ec_query_all(struct cros_ec_device *ec_dev);
/** int cros_ec_get_next_event(struct cros_ec_device *ec_dev,
* cros_ec_get_next_event() - Fetch next event from the ChromeOS EC. bool *wake_event,
* @ec_dev: Device to fetch event from. bool *has_more_events);
* @wake_event: Pointer to a bool set to true upon return if the event might be
* treated as a wake event. Ignored if null. u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev);
*
* Return: negative error code on errors; 0 for no data; or else number of int cros_ec_check_features(struct cros_ec_dev *ec, int feature);
* bytes received (i.e., an event was retrieved successfully). Event types are
* written out to @ec_dev->event_data.event_type on success. int cros_ec_get_sensor_count(struct cros_ec_dev *ec);
*/
int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event); bool cros_ec_handle_event(struct cros_ec_device *ec_dev);
/** /**
* cros_ec_get_host_event() - Return a mask of event set by the ChromeOS EC. * cros_ec_get_time_ns() - Return time in ns.
* @ec_dev: Device to fetch event from.
* *
* When MKBP is supported, when the EC raises an interrupt, we collect the * This is the function used to record the time for last_event_time in struct
* events raised and call the functions in the ec notifier. This function * cros_ec_device during the hard irq.
* is a helper to know which events are raised.
* *
* Return: 0 on error or non-zero bitmask of one or more EC_HOST_EVENT_*. * Return: ktime_t format since boot.
*/ */
u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev); static inline ktime_t cros_ec_get_time_ns(void)
{
return ktime_get_boottime_ns();
}
#endif /* __LINUX_CROS_EC_PROTO_H */ #endif /* __LINUX_CROS_EC_PROTO_H */
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Chrome OS EC MEMS Sensor Hub driver.
*
* Copyright 2019 Google LLC
*/
#ifndef __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H
#define __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H
#include <linux/platform_data/cros_ec_commands.h>
/**
* struct cros_ec_sensor_platform - ChromeOS EC sensor platform information.
* @sensor_num: Id of the sensor, as reported by the EC.
*/
struct cros_ec_sensor_platform {
u8 sensor_num;
};
/**
* struct cros_ec_sensorhub - Sensor Hub device data.
*
* @ec: Embedded Controller where the hub is located.
*/
struct cros_ec_sensorhub {
struct cros_ec_dev *ec;
};
#endif /* __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H */
...@@ -29,6 +29,7 @@ ...@@ -29,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.
* @charger_pdev: Child platform_device used by the charger config sub-driver.
* @telem_pdev: The child platform_device used by the telemetry sub-driver. * @telem_pdev: The child platform_device used by the telemetry sub-driver.
*/ */
struct wilco_ec_device { struct wilco_ec_device {
...@@ -41,6 +42,7 @@ struct wilco_ec_device { ...@@ -41,6 +42,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 *charger_pdev;
struct platform_device *telem_pdev; struct platform_device *telem_pdev;
}; };
...@@ -120,6 +122,19 @@ struct wilco_ec_message { ...@@ -120,6 +122,19 @@ 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);
/**
* wilco_keyboard_leds_init() - Set up the keyboard backlight LEDs.
* @ec: EC device to query.
*
* After this call, the keyboard backlight will be exposed through a an LED
* device at /sys/class/leds.
*
* This may sleep because it uses wilco_ec_mailbox().
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_keyboard_leds_init(struct wilco_ec_device *ec);
/* /*
* A Property is typically a data item that is stored to NVRAM * A Property is typically a data item that is stored to NVRAM
* by the EC. Each of these data items has an index associated * by the EC. Each of these data items has an index associated
......
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