Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Chrome OS Embedded Controller managed sensors library
#
config IIO_CROS_EC_SENSORS_CORE
tristate "ChromeOS EC Sensors Core"
depends on SYSFS && CROS_EC_SENSORHUB
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
Base module for the ChromeOS EC Sensors module.
Contains core functions used by other IIO CrosEC sensor
drivers.
Define common attributes and sysfs interrupt handler.
config IIO_CROS_EC_SENSORS
tristate "ChromeOS EC Contiguous Sensors"
depends on IIO_CROS_EC_SENSORS_CORE
help
Module to handle 3d contiguous sensors like
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.
config IIO_CROS_EC_ACTIVITY
tristate "ChromeOS EC Activity Sensors"
depends on IIO_CROS_EC_SENSORS_CORE
help
Module to handle activity events presented by the ChromeOS EC sensor hub.
Activities can be a proximity detector (on body/off body detection)
or a significant motion detector.
Creates an IIO device to manage all activities.
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for sensors seen through the ChromeOS EC sensor hub.
#
cros-ec-sensors-core-objs += cros_ec_sensors_core.o cros_ec_sensors_trace.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros-ec-sensors-core.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o
obj-$(CONFIG_IIO_CROS_EC_ACTIVITY) += cros_ec_activity.o
@@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0
/*
* cros_ec_activity - Driver for activities/gesture recognition.
*
* Copyright 2025 Google, Inc
*
* This driver uses the cros-ec interface to communicate with the ChromeOS
* EC about activity data.
*/
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/iio/common/cros_ec_sensors_core.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger_consumer.h>
#define DRV_NAME "cros-ec-activity"
/* state data for ec_sensors iio driver. */
struct cros_ec_sensors_state {
/* Shared by all sensors */
struct cros_ec_sensors_core_state core;
struct iio_chan_spec *channels;
int body_detection_channel_index;
int sig_motion_channel_index;
};
static const struct iio_event_spec cros_ec_activity_single_shot[] = {
{
.type = IIO_EV_TYPE_CHANGE,
/* significant motion trigger when we get out of still. */
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
},
};
static const struct iio_event_spec cros_ec_body_detect_events[] = {
{
.type = IIO_EV_TYPE_CHANGE,
.dir = IIO_EV_DIR_EITHER,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
},
};
static int cros_ec_activity_sensors_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
int ret;
if (chan->type != IIO_PROXIMITY || mask != IIO_CHAN_INFO_RAW)
return -EINVAL;
guard(mutex)(&st->core.cmd_lock);
st->core.param.cmd = MOTIONSENSE_CMD_GET_ACTIVITY;
st->core.param.get_activity.activity =
MOTIONSENSE_ACTIVITY_BODY_DETECTION;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret)
return ret;
/*
* EC actually report if a body is near (1) or far (0).
* Units for proximity sensor after scale is in meter,
* so invert the result to return 0m when near and 1m when far.
*/
*val = !st->core.resp->get_activity.state;
return IIO_VAL_INT;
}
static int cros_ec_activity_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
int ret;
if (chan->type != IIO_ACTIVITY && chan->type != IIO_PROXIMITY)
return -EINVAL;
guard(mutex)(&st->core.cmd_lock);
st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret)
return ret;
switch (chan->type) {
case IIO_PROXIMITY:
return !!(st->core.resp->list_activities.enabled &
(1 << MOTIONSENSE_ACTIVITY_BODY_DETECTION));
case IIO_ACTIVITY:
if (chan->channel2 == IIO_MOD_STILL) {
return !!(st->core.resp->list_activities.enabled &
(1 << MOTIONSENSE_ACTIVITY_SIG_MOTION));
}
dev_warn(&indio_dev->dev, "Unknown activity: %d\n",
chan->channel2);
return -EINVAL;
default:
dev_warn(&indio_dev->dev, "Unknown channel type: %d\n",
chan->type);
return -EINVAL;
}
}
static int cros_ec_activity_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
bool state)
{
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
guard(mutex)(&st->core.cmd_lock);
st->core.param.cmd = MOTIONSENSE_CMD_SET_ACTIVITY;
switch (chan->type) {
case IIO_PROXIMITY:
st->core.param.set_activity.activity =
MOTIONSENSE_ACTIVITY_BODY_DETECTION;
break;
case IIO_ACTIVITY:
if (chan->channel2 == IIO_MOD_STILL) {
st->core.param.set_activity.activity =
MOTIONSENSE_ACTIVITY_SIG_MOTION;
break;
}
dev_warn(&indio_dev->dev, "Unknown activity: %d\n",
chan->channel2);
return -EINVAL;
default:
dev_warn(&indio_dev->dev, "Unknown channel type: %d\n",
chan->type);
return -EINVAL;
}
st->core.param.set_activity.enable = state;
return cros_ec_motion_send_host_cmd(&st->core, 0);
}
static int cros_ec_activity_push_data(struct iio_dev *indio_dev,
s16 *data, s64 timestamp)
{
struct ec_response_activity_data *activity_data =
(struct ec_response_activity_data *)data;
enum motionsensor_activity activity = activity_data->activity;
u8 state = activity_data->state;
const struct cros_ec_sensors_state *st = iio_priv(indio_dev);
const struct iio_chan_spec *chan;
enum iio_event_direction dir;
int index;
switch (activity) {
case MOTIONSENSE_ACTIVITY_BODY_DETECTION:
index = st->body_detection_channel_index;
dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
break;
case MOTIONSENSE_ACTIVITY_SIG_MOTION:
index = st->sig_motion_channel_index;
dir = IIO_EV_DIR_FALLING;
break;
default:
dev_warn(&indio_dev->dev, "Unknown activity: %d\n", activity);
return 0;
}
chan = &st->channels[index];
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(chan->type, index, chan->event_spec[0].type, dir),
timestamp);
return 0;
}
static irqreturn_t cros_ec_activity_capture(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
/*
* This callback would be called when a software trigger is
* used. But when this virtual sensor is present, it is guaranteed
* the sensor hub is advanced enough to not need a software trigger.
*/
dev_warn(&indio_dev->dev, "%s: Not Expected\n", __func__);
return IRQ_NONE;
}
static const struct iio_info ec_sensors_info = {
.read_raw = &cros_ec_activity_sensors_read_raw,
.read_event_config = cros_ec_activity_read_event_config,
.write_event_config = cros_ec_activity_write_event_config,
};
static int cros_ec_sensors_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_device *ec_device = dev_get_drvdata(dev->parent);
struct iio_dev *indio_dev;
struct cros_ec_sensors_state *st;
struct iio_chan_spec *channel;
unsigned long activities;
int i, index, ret, nb_activities;
if (!ec_device) {
dev_warn(dev, "No CROS EC device found.\n");
return -EINVAL;
}
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
ret = cros_ec_sensors_core_init(pdev, indio_dev, true,
cros_ec_activity_capture);
if (ret)
return ret;
indio_dev->info = &ec_sensors_info;
st = iio_priv(indio_dev);
st->core.type = st->core.resp->info.type;
st->core.read_ec_sensors_data = cros_ec_sensors_read_cmd;
st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret)
return ret;
activities = st->core.resp->list_activities.enabled |
st->core.resp->list_activities.disabled;
if (!activities)
return -ENODEV;
/* Allocate a channel per activity and one for timestamp */
nb_activities = hweight_long(activities) + 1;
st->channels = devm_kcalloc(dev, nb_activities,
sizeof(*st->channels), GFP_KERNEL);
if (!st->channels)
return -ENOMEM;
channel = &st->channels[0];
index = 0;
for_each_set_bit(i, &activities, BITS_PER_LONG) {
/* List all available triggers */
if (i == MOTIONSENSE_ACTIVITY_BODY_DETECTION) {
channel->type = IIO_PROXIMITY;
channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
channel->event_spec = cros_ec_body_detect_events;
channel->num_event_specs =
ARRAY_SIZE(cros_ec_body_detect_events);
st->body_detection_channel_index = index;
} else {
channel->type = IIO_ACTIVITY;
channel->modified = 1;
channel->event_spec = cros_ec_activity_single_shot;
channel->num_event_specs =
ARRAY_SIZE(cros_ec_activity_single_shot);
if (i == MOTIONSENSE_ACTIVITY_SIG_MOTION) {
channel->channel2 = IIO_MOD_STILL;
st->sig_motion_channel_index = index;
} else {
dev_warn(dev, "Unknown activity: %d\n", i);
continue;
}
}
channel->ext_info = cros_ec_sensors_limited_info;
channel->scan_index = index++;
channel++;
}
*channel = IIO_CHAN_SOFT_TIMESTAMP(index);
indio_dev->channels = st->channels;
indio_dev->num_channels = index + 1;
return cros_ec_sensors_core_register(dev, indio_dev,
cros_ec_activity_push_data);
}
static struct platform_driver cros_ec_sensors_platform_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = cros_ec_sensors_probe,
};
module_platform_driver(cros_ec_sensors_platform_driver);
MODULE_DESCRIPTION("ChromeOS EC activity sensors driver");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,137 @@
// 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/module.h>
#include <linux/platform_data/cros_ec_commands.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, NULL);
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 cros_ec_sensors_core_register(dev, indio_dev, NULL);
}
static const struct platform_device_id cros_ec_lid_angle_ids[] = {
{
.name = DRV_NAME,
},
{ }
};
MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids);
static struct platform_driver cros_ec_lid_angle_platform_driver = {
.driver = {
.name = DRV_NAME,
},
.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");
@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0
/*
* cros_ec_sensors - Driver for Chrome OS Embedded Controller sensors.
*
* Copyright (C) 2016 Google, Inc
*
* This driver uses the cros-ec interface to communicate with the Chrome OS
* EC about sensors data. Data access is presented through iio sysfs.
*/
#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_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define CROS_EC_SENSORS_MAX_CHANNELS 4
/* State data for ec_sensors iio driver. */
struct cros_ec_sensors_state {
/* Shared by all sensors */
struct cros_ec_sensors_core_state core;
struct iio_chan_spec channels[CROS_EC_SENSORS_MAX_CHANNELS];
};
static int cros_ec_sensors_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
s16 data = 0;
s64 val64;
int i;
int ret;
int idx = chan->scan_index;
mutex_lock(&st->core.cmd_lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = st->core.read_ec_sensors_data(indio_dev, 1 << idx, &data);
if (ret < 0)
break;
ret = IIO_VAL_INT;
*val = data;
break;
case IIO_CHAN_INFO_CALIBBIAS:
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
st->core.param.sensor_offset.flags = 0;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret < 0)
break;
/* Save values */
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
st->core.calib[i].offset =
st->core.resp->sensor_offset.offset[i];
ret = IIO_VAL_INT;
*val = st->core.calib[idx].offset;
break;
case IIO_CHAN_INFO_CALIBSCALE:
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE;
st->core.param.sensor_offset.flags = 0;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret == -EPROTO || ret == -EOPNOTSUPP) {
/* Reading calibscale is not supported on older EC. */
*val = 1;
*val2 = 0;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
} else if (ret) {
break;
}
/* Save values */
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
st->core.calib[i].scale =
st->core.resp->sensor_scale.scale[i];
*val = st->core.calib[idx].scale >> 15;
*val2 = ((st->core.calib[idx].scale & 0x7FFF) * 1000000LL) /
MOTION_SENSE_DEFAULT_SCALE;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
case IIO_CHAN_INFO_SCALE:
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret < 0)
break;
val64 = st->core.resp->sensor_range.ret;
switch (st->core.type) {
case MOTIONSENSE_TYPE_ACCEL:
/*
* EC returns data in g, IIO expects m/s^2.
* Do not use IIO_G_TO_M_S_2 to avoid precision loss.
*/
*val = div_s64(val64 * 980665, 10);
*val2 = 10000 << (CROS_EC_SENSOR_BITS - 1);
ret = IIO_VAL_FRACTIONAL;
break;
case MOTIONSENSE_TYPE_GYRO:
/*
* EC returns data in dps, iio expects rad/s.
* Do not use IIO_DEGREE_TO_RAD to avoid precision
* loss. Round to the nearest integer.
*/
*val = 0;
*val2 = div_s64(val64 * 3141592653ULL,
180 << (CROS_EC_SENSOR_BITS - 1));
ret = IIO_VAL_INT_PLUS_NANO;
break;
case MOTIONSENSE_TYPE_MAG:
/*
* EC returns data in 16LSB / uT,
* iio expects Gauss
*/
*val = val64;
*val2 = 100 << (CROS_EC_SENSOR_BITS - 1);
ret = IIO_VAL_FRACTIONAL;
break;
default:
ret = -EINVAL;
}
break;
default:
ret = cros_ec_sensors_core_read(&st->core, chan, val, val2,
mask);
break;
}
mutex_unlock(&st->core.cmd_lock);
return ret;
}
static int cros_ec_sensors_write(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
int i;
int ret;
int idx = chan->scan_index;
mutex_lock(&st->core.cmd_lock);
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
st->core.calib[idx].offset = val;
/* Send to EC for each axis, even if not complete */
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
st->core.param.sensor_offset.flags =
MOTION_SENSE_SET_OFFSET;
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
st->core.param.sensor_offset.offset[i] =
st->core.calib[i].offset;
st->core.param.sensor_offset.temp =
EC_MOTION_SENSE_INVALID_CALIB_TEMP;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
break;
case IIO_CHAN_INFO_CALIBSCALE:
st->core.calib[idx].scale = val;
/* Send to EC for each axis, even if not complete */
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE;
st->core.param.sensor_offset.flags =
MOTION_SENSE_SET_OFFSET;
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
st->core.param.sensor_scale.scale[i] =
st->core.calib[i].scale;
st->core.param.sensor_scale.temp =
EC_MOTION_SENSE_INVALID_CALIB_TEMP;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
break;
case IIO_CHAN_INFO_SCALE:
if (st->core.type == MOTIONSENSE_TYPE_MAG) {
ret = -EINVAL;
break;
}
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
st->core.param.sensor_range.data = val;
/* Always roundup, so caller gets at least what it asks for. */
st->core.param.sensor_range.roundup = 1;
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
if (ret == 0) {
st->core.range_updated = true;
st->core.curr_range = val;
}
break;
default:
ret = cros_ec_sensors_core_write(
&st->core, chan, val, val2, mask);
break;
}
mutex_unlock(&st->core.cmd_lock);
return ret;
}
static const struct iio_info ec_sensors_info = {
.read_raw = &cros_ec_sensors_read,
.write_raw = &cros_ec_sensors_write,
.read_avail = &cros_ec_sensors_core_read_avail,
};
static int cros_ec_sensors_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct iio_dev *indio_dev;
struct cros_ec_sensors_state *state;
struct iio_chan_spec *channel;
int ret, i;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
ret = cros_ec_sensors_core_init(pdev, indio_dev, true,
cros_ec_sensors_capture);
if (ret)
return ret;
indio_dev->info = &ec_sensors_info;
state = iio_priv(indio_dev);
for (channel = state->channels, i = CROS_EC_SENSOR_X;
i < CROS_EC_SENSOR_MAX_AXIS; i++, channel++) {
/* Common part */
channel->info_mask_separate =
BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_CALIBBIAS) |
BIT(IIO_CHAN_INFO_CALIBSCALE);
channel->info_mask_shared_by_all =
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_SAMP_FREQ);
channel->info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_SAMP_FREQ);
channel->scan_type.realbits = CROS_EC_SENSOR_BITS;
channel->scan_type.storagebits = CROS_EC_SENSOR_BITS;
channel->scan_index = i;
channel->ext_info = cros_ec_sensors_ext_info;
channel->modified = 1;
channel->channel2 = IIO_MOD_X + i;
channel->scan_type.sign = 's';
/* Sensor specific */
switch (state->core.type) {
case MOTIONSENSE_TYPE_ACCEL:
channel->type = IIO_ACCEL;
break;
case MOTIONSENSE_TYPE_GYRO:
channel->type = IIO_ANGL_VEL;
break;
case MOTIONSENSE_TYPE_MAG:
channel->type = IIO_MAGN;
break;
default:
dev_err(&pdev->dev, "Unknown motion sensor\n");
return -EINVAL;
}
}
*channel = IIO_CHAN_SOFT_TIMESTAMP(CROS_EC_SENSOR_MAX_AXIS);
indio_dev->channels = state->channels;
indio_dev->num_channels = CROS_EC_SENSORS_MAX_CHANNELS;
/* There is only enough room for accel and gyro in the io space */
if ((state->core.ec->cmd_readmem != NULL) &&
(state->core.type != MOTIONSENSE_TYPE_MAG))
state->core.read_ec_sensors_data = cros_ec_sensors_read_lpc;
else
state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd;
return cros_ec_sensors_core_register(dev, indio_dev,
cros_ec_sensors_push_data);
}
static const struct platform_device_id cros_ec_sensors_ids[] = {
{
.name = "cros-ec-accel",
},
{
.name = "cros-ec-gyro",
},
{
.name = "cros-ec-mag",
},
{ }
};
MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids);
static struct platform_driver cros_ec_sensors_platform_driver = {
.driver = {
.name = "cros-ec-sensors",
.pm = &cros_ec_sensors_pm_ops,
},
.probe = cros_ec_sensors_probe,
.id_table = cros_ec_sensors_ids,
};
module_platform_driver(cros_ec_sensors_platform_driver);
MODULE_DESCRIPTION("ChromeOS EC 3-axis sensors driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,886 @@
// SPDX-License-Identifier: GPL-2.0
/*
* cros_ec_sensors_core - Common function for Chrome OS EC sensor driver.
*
* Copyright (C) 2016 Google, Inc
*/
#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/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.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 "cros_ec_sensors_trace.h"
/*
* Hard coded to the first device to support sensor fifo. The EC has a 2048
* byte fifo and will trigger an interrupt when fifo is 2/3 full.
*/
#define CROS_EC_FIFO_SIZE (2048 * 2 / 3)
static int cros_ec_get_host_cmd_version_mask(struct cros_ec_device *ec_dev,
u16 cmd_offset, u16 cmd, u32 *mask)
{
DEFINE_RAW_FLEX(struct cros_ec_command, buf, data,
MAX(sizeof(struct ec_response_get_cmd_versions),
sizeof(struct ec_params_get_cmd_versions)));
int ret;
buf->command = EC_CMD_GET_CMD_VERSIONS + cmd_offset;
buf->insize = sizeof(struct ec_response_get_cmd_versions);
buf->outsize = sizeof(struct ec_params_get_cmd_versions);
((struct ec_params_get_cmd_versions *)buf->data)->cmd = cmd;
ret = cros_ec_cmd_xfer_status(ec_dev, buf);
if (ret >= 0)
*mask = ((struct ec_response_get_cmd_versions *)buf->data)->version_mask;
return ret;
}
static void get_default_min_max_freq(enum motionsensor_type type,
u32 *min_freq,
u32 *max_freq,
u32 *max_fifo_events)
{
/*
* We don't know fifo size, set to size previously used by older
* hardware.
*/
*max_fifo_events = CROS_EC_FIFO_SIZE;
switch (type) {
case MOTIONSENSE_TYPE_ACCEL:
*min_freq = 12500;
*max_freq = 100000;
break;
case MOTIONSENSE_TYPE_GYRO:
*min_freq = 25000;
*max_freq = 100000;
break;
case MOTIONSENSE_TYPE_MAG:
*min_freq = 5000;
*max_freq = 25000;
break;
case MOTIONSENSE_TYPE_PROX:
case MOTIONSENSE_TYPE_LIGHT:
*min_freq = 100;
*max_freq = 50000;
break;
case MOTIONSENSE_TYPE_BARO:
*min_freq = 250;
*max_freq = 20000;
break;
case MOTIONSENSE_TYPE_ACTIVITY:
default:
*min_freq = 0;
*max_freq = 0;
break;
}
}
static ssize_t cros_ec_sensor_set_report_latency(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int integer, fract, ret;
int latency;
ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract);
if (ret)
return ret;
/* EC rate is in ms. */
latency = integer * 1000 + fract / 1000;
mutex_lock(&st->cmd_lock);
st->param.cmd = MOTIONSENSE_CMD_EC_RATE;
st->param.ec_rate.data = min(U16_MAX, latency);
ret = cros_ec_motion_send_host_cmd(st, 0);
if (ret < 0) {
mutex_unlock(&st->cmd_lock);
return ret;
}
/*
* Flush samples currently in the FIFO, especially when the new latency
* is shorter than the old one: new timeout value is only considered when
* there is a new sample available. It can take a while for a slow
* sensor.
*/
st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH;
ret = cros_ec_motion_send_host_cmd(st, 0);
mutex_unlock(&st->cmd_lock);
if (ret < 0)
return ret;
return len;
}
static ssize_t cros_ec_sensor_get_report_latency(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int latency, ret;
mutex_lock(&st->cmd_lock);
st->param.cmd = MOTIONSENSE_CMD_EC_RATE;
st->param.ec_rate.data = EC_MOTION_SENSE_NO_VALUE;
ret = cros_ec_motion_send_host_cmd(st, 0);
latency = st->resp->ec_rate.ret;
mutex_unlock(&st->cmd_lock);
if (ret < 0)
return ret;
return sprintf(buf, "%d.%06u\n",
latency / 1000,
(latency % 1000) * 1000);
}
static IIO_DEVICE_ATTR(hwfifo_timeout, 0644,
cros_ec_sensor_get_report_latency,
cros_ec_sensor_set_report_latency, 0);
static ssize_t hwfifo_watermark_max_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
return sprintf(buf, "%d\n", st->fifo_max_event_count);
}
static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0);
static const struct iio_dev_attr *cros_ec_sensor_fifo_attributes[] = {
&iio_dev_attr_hwfifo_timeout,
&iio_dev_attr_hwfifo_watermark_max,
NULL,
};
int cros_ec_sensors_push_data(struct iio_dev *indio_dev,
s16 *data,
s64 timestamp)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
s16 *out;
s64 delta;
unsigned int i;
/*
* Ignore samples if the buffer is not set: it is needed if the ODR is
* set but the buffer is not enabled yet.
*/
if (!iio_device_try_claim_buffer_mode(indio_dev))
return 0;
out = (s16 *)st->samples;
iio_for_each_active_channel(indio_dev, i) {
*out = data[i];
out++;
}
if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME)
delta = iio_get_time_ns(indio_dev) - cros_ec_get_time_ns();
else
delta = 0;
iio_push_to_buffers_with_timestamp(indio_dev, st->samples,
timestamp + delta);
iio_device_release_buffer_mode(indio_dev);
return 0;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_push_data);
static void cros_ec_sensors_core_clean(void *arg)
{
struct platform_device *pdev = (struct platform_device *)arg;
struct cros_ec_sensorhub *sensor_hub =
dev_get_drvdata(pdev->dev.parent);
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
u8 sensor_num = st->param.info.sensor_num;
cros_ec_sensorhub_unregister_push_data(sensor_hub, sensor_num);
}
/**
* cros_ec_sensors_core_init() - basic initialization of the core structure
* @pdev: platform device created for the sensor
* @indio_dev: iio device structure of the device
* @physical_device: true if the device refers to a physical device
* @trigger_capture: function pointer to call buffer is triggered,
* for backward compatibility.
*
* Return: 0 on success, -errno on failure.
*/
int cros_ec_sensors_core_init(struct platform_device *pdev,
struct iio_dev *indio_dev,
bool physical_device,
cros_ec_sensors_capture_t trigger_capture)
{
struct device *dev = &pdev->dev;
struct cros_ec_sensors_core_state *state = iio_priv(indio_dev);
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);
u32 ver_mask, temp;
int frequencies[ARRAY_SIZE(state->frequencies) / 2] = { 0 };
int ret, i;
platform_set_drvdata(pdev, indio_dev);
state->ec = ec->ec_dev;
state->msg = devm_kzalloc(&pdev->dev, sizeof(*state->msg) +
max((u16)sizeof(struct ec_params_motion_sense),
state->ec->max_response), GFP_KERNEL);
if (!state->msg)
return -ENOMEM;
state->resp = (struct ec_response_motion_sense *)state->msg->data;
mutex_init(&state->cmd_lock);
ret = cros_ec_get_host_cmd_version_mask(state->ec,
ec->cmd_offset,
EC_CMD_MOTION_SENSE_CMD,
&ver_mask);
if (ret < 0)
return ret;
/* Set up the host command structure. */
state->msg->version = fls(ver_mask) - 1;
state->msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
state->msg->outsize = sizeof(struct ec_params_motion_sense);
indio_dev->name = pdev->name;
if (physical_device) {
enum motionsensor_location loc;
state->param.cmd = MOTIONSENSE_CMD_INFO;
state->param.info.sensor_num = sensor_platform->sensor_num;
ret = cros_ec_motion_send_host_cmd(state, 0);
if (ret) {
dev_warn(dev, "Can not access sensor info\n");
return ret;
}
state->type = state->resp->info.type;
loc = state->resp->info.location;
if (loc == MOTIONSENSE_LOC_BASE)
indio_dev->label = "accel-base";
else if (loc == MOTIONSENSE_LOC_LID)
indio_dev->label = "accel-display";
else if (loc == MOTIONSENSE_LOC_CAMERA)
indio_dev->label = "accel-camera";
/* Set sign vector, only used for backward compatibility. */
memset(state->sign, 1, CROS_EC_SENSOR_MAX_AXIS);
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
state->calib[i].scale = MOTION_SENSE_DEFAULT_SCALE;
/* 0 is a correct value used to stop the device */
if (state->msg->version < 3) {
get_default_min_max_freq(state->resp->info.type,
&frequencies[1],
&frequencies[2],
&state->fifo_max_event_count);
} else {
if (state->resp->info_3.max_frequency == 0) {
get_default_min_max_freq(state->resp->info.type,
&frequencies[1],
&frequencies[2],
&temp);
} else {
frequencies[1] = state->resp->info_3.min_frequency;
frequencies[2] = state->resp->info_3.max_frequency;
}
state->fifo_max_event_count = state->resp->info_3.fifo_max_event_count;
}
for (i = 0; i < ARRAY_SIZE(frequencies); i++) {
state->frequencies[2 * i] = frequencies[i] / 1000;
state->frequencies[2 * i + 1] =
(frequencies[i] % 1000) * 1000;
}
if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) {
/*
* Create a software buffer, feed by the EC FIFO.
* We can not use trigger here, as events are generated
* as soon as sample_frequency is set.
*/
ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, NULL,
cros_ec_sensor_fifo_attributes);
if (ret)
return ret;
/* Timestamp coming from FIFO are in ns since boot. */
ret = iio_device_set_clock(indio_dev, CLOCK_BOOTTIME);
if (ret)
return ret;
} else {
/*
* The only way to get samples in buffer is to set a
* software trigger (systrig, hrtimer).
*/
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
NULL, trigger_capture, NULL);
if (ret)
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_init);
/**
* cros_ec_sensors_core_register() - Register callback to FIFO and IIO when
* sensor is ready.
* It must be called at the end of the sensor probe routine.
* @dev: device created for the sensor
* @indio_dev: iio device structure of the device
* @push_data: function to call when cros_ec_sensorhub receives
* a sample for that sensor.
*
* Return: 0 on success, -errno on failure.
*/
int cros_ec_sensors_core_register(struct device *dev,
struct iio_dev *indio_dev,
cros_ec_sensorhub_push_data_cb_t push_data)
{
struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev);
struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent);
struct platform_device *pdev = to_platform_device(dev);
struct cros_ec_dev *ec = sensor_hub->ec;
int ret;
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return ret;
if (!push_data ||
!cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO))
return 0;
ret = cros_ec_sensorhub_register_push_data(
sensor_hub, sensor_platform->sensor_num,
indio_dev, push_data);
if (ret)
return ret;
return devm_add_action_or_reset(
dev, cros_ec_sensors_core_clean, pdev);
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_register);
/**
* cros_ec_motion_send_host_cmd() - send motion sense host command
* @state: pointer to state information for device
* @opt_length: optional length to reduce the response size, useful on the data
* path. Otherwise, the maximal allowed response size is used
*
* When called, the sub-command is assumed to be set in param->cmd.
*
* Return: 0 on success, -errno on failure.
*/
int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *state,
u16 opt_length)
{
struct ec_response_motion_sense *resp = (struct ec_response_motion_sense *)state->msg->data;
int ret;
if (opt_length)
state->msg->insize = min(opt_length, state->ec->max_response);
else
state->msg->insize = state->ec->max_response;
memcpy(state->msg->data, &state->param, sizeof(state->param));
ret = cros_ec_cmd_xfer_status(state->ec, state->msg);
trace_cros_ec_motion_host_cmd(&state->param, resp, ret);
if (ret < 0)
return ret;
if (ret && state->resp != resp)
memcpy(state->resp, resp, ret);
return 0;
}
EXPORT_SYMBOL_GPL(cros_ec_motion_send_host_cmd);
static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev,
uintptr_t private, const struct iio_chan_spec *chan,
const char *buf, size_t len)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret, i;
bool calibrate;
ret = kstrtobool(buf, &calibrate);
if (ret < 0)
return ret;
mutex_lock(&st->cmd_lock);
st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB;
st->param.perform_calib.enable = calibrate;
ret = cros_ec_motion_send_host_cmd(st, 0);
if (ret != 0) {
dev_warn(&indio_dev->dev, "Unable to calibrate sensor: %d\n",
ret);
} else {
/* Save values */
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
st->calib[i].offset = st->resp->perform_calib.offset[i];
}
mutex_unlock(&st->cmd_lock);
return ret ? ret : len;
}
static ssize_t cros_ec_sensors_id(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan, char *buf)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
return snprintf(buf, PAGE_SIZE, "%d\n", st->param.info.sensor_num);
}
const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[] = {
{
.name = "calibrate",
.shared = IIO_SHARED_BY_ALL,
.write = cros_ec_sensors_calibrate
},
{
.name = "id",
.shared = IIO_SHARED_BY_ALL,
.read = cros_ec_sensors_id
},
{ }
};
EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info);
const struct iio_chan_spec_ext_info cros_ec_sensors_limited_info[] = {
{
.name = "id",
.shared = IIO_SHARED_BY_ALL,
.read = cros_ec_sensors_id
},
{ }
};
EXPORT_SYMBOL_GPL(cros_ec_sensors_limited_info);
/**
* cros_ec_sensors_idx_to_reg - convert index into offset in shared memory
* @st: pointer to state information for device
* @idx: sensor index (should be element of enum sensor_index)
*
* Return: address to read at
*/
static unsigned int cros_ec_sensors_idx_to_reg(
struct cros_ec_sensors_core_state *st,
unsigned int idx)
{
/*
* When using LPC interface, only space for 2 Accel and one Gyro.
* First halfword of MOTIONSENSE_TYPE_ACCEL is used by angle.
*/
if (st->type == MOTIONSENSE_TYPE_ACCEL)
return EC_MEMMAP_ACC_DATA + sizeof(u16) *
(1 + idx + st->param.info.sensor_num *
CROS_EC_SENSOR_MAX_AXIS);
return EC_MEMMAP_GYRO_DATA + sizeof(u16) * idx;
}
static int cros_ec_sensors_cmd_read_u8(struct cros_ec_device *ec,
unsigned int offset, u8 *dest)
{
return ec->cmd_readmem(ec, offset, 1, dest);
}
static int cros_ec_sensors_cmd_read_u16(struct cros_ec_device *ec,
unsigned int offset, u16 *dest)
{
__le16 tmp;
int ret = ec->cmd_readmem(ec, offset, 2, &tmp);
if (ret >= 0)
*dest = le16_to_cpu(tmp);
return ret;
}
/**
* cros_ec_sensors_read_until_not_busy() - read until is not busy
*
* @st: pointer to state information for device
*
* Read from EC status byte until it reads not busy.
* Return: 8-bit status if ok, -errno on failure.
*/
static int cros_ec_sensors_read_until_not_busy(
struct cros_ec_sensors_core_state *st)
{
struct cros_ec_device *ec = st->ec;
u8 status;
int ret, attempts = 0;
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status);
if (ret < 0)
return ret;
while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) {
/* Give up after enough attempts, return error. */
if (attempts++ >= 50)
return -EIO;
/* Small delay every so often. */
if (attempts % 5 == 0)
msleep(25);
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS,
&status);
if (ret < 0)
return ret;
}
return status;
}
/**
* cros_ec_sensors_read_data_unsafe() - read acceleration data from EC shared memory
* @indio_dev: pointer to IIO device
* @scan_mask: bitmap of the sensor indices to scan
* @data: location to store data
*
* This is the unsafe function for reading the EC data. It does not guarantee
* that the EC will not modify the data as it is being read in.
*
* Return: 0 on success, -errno on failure.
*/
static int cros_ec_sensors_read_data_unsafe(struct iio_dev *indio_dev,
unsigned long scan_mask, s16 *data)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
struct cros_ec_device *ec = st->ec;
unsigned int i;
int ret;
/* Read all sensors enabled in scan_mask. Each value is 2 bytes. */
for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) {
ret = cros_ec_sensors_cmd_read_u16(ec,
cros_ec_sensors_idx_to_reg(st, i),
data);
if (ret < 0)
return ret;
*data *= st->sign[i];
data++;
}
return 0;
}
/**
* cros_ec_sensors_read_lpc() - read acceleration data from EC shared memory.
* @indio_dev: pointer to IIO device.
* @scan_mask: bitmap of the sensor indices to scan.
* @data: location to store data.
*
* Note: this is the safe function for reading the EC data. It guarantees
* that the data sampled was not modified by the EC while being read.
*
* Return: 0 on success, -errno on failure.
*/
int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev,
unsigned long scan_mask, s16 *data)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
struct cros_ec_device *ec = st->ec;
u8 samp_id = 0xff, status = 0;
int ret, attempts = 0;
/*
* Continually read all data from EC until the status byte after
* all reads reflects that the EC is not busy and the sample id
* matches the sample id from before all reads. This guarantees
* that data read in was not modified by the EC while reading.
*/
while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT |
EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) {
/* If we have tried to read too many times, return error. */
if (attempts++ >= 5)
return -EIO;
/* Read status byte until EC is not busy. */
ret = cros_ec_sensors_read_until_not_busy(st);
if (ret < 0)
return ret;
/*
* Store the current sample id so that we can compare to the
* sample id after reading the data.
*/
samp_id = ret & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK;
/* Read all EC data, format it, and store it into data. */
ret = cros_ec_sensors_read_data_unsafe(indio_dev, scan_mask,
data);
if (ret < 0)
return ret;
/* Read status byte. */
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS,
&status);
if (ret < 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_read_lpc);
/**
* cros_ec_sensors_read_cmd() - retrieve data using the EC command protocol
* @indio_dev: pointer to IIO device
* @scan_mask: bitmap of the sensor indices to scan
* @data: location to store data
*
* Return: 0 on success, -errno on failure.
*/
int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev,
unsigned long scan_mask, s16 *data)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret;
unsigned int i;
/* Read all sensor data through a command. */
st->param.cmd = MOTIONSENSE_CMD_DATA;
ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->data));
if (ret != 0) {
dev_warn(&indio_dev->dev, "Unable to read sensor data\n");
return ret;
}
for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) {
*data = st->resp->data.data[i];
data++;
}
return 0;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_read_cmd);
/**
* cros_ec_sensors_capture() - the trigger handler function
* @irq: the interrupt number.
* @p: a pointer to the poll function.
*
* On a trigger event occurring, if the pollfunc is attached then this
* handler is called as a threaded interrupt (and hence may sleep). It
* is responsible for grabbing data from the device and pushing it into
* the associated buffer.
*
* Return: IRQ_HANDLED
*/
irqreturn_t cros_ec_sensors_capture(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret;
mutex_lock(&st->cmd_lock);
/* Clear capture data. */
memset(st->samples, 0, indio_dev->scan_bytes);
/* Read data based on which channels are enabled in scan mask. */
ret = st->read_ec_sensors_data(indio_dev,
*(indio_dev->active_scan_mask),
(s16 *)st->samples);
if (ret < 0)
goto done;
iio_push_to_buffers_with_timestamp(indio_dev, st->samples,
iio_get_time_ns(indio_dev));
done:
/*
* Tell the core we are done with this trigger and ready for the
* next one.
*/
iio_trigger_notify_done(indio_dev->trig);
mutex_unlock(&st->cmd_lock);
return IRQ_HANDLED;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_capture);
/**
* cros_ec_sensors_core_read() - function to request a value from the sensor
* @st: pointer to state information for device
* @chan: channel specification structure table
* @val: will contain one element making up the returned value
* @val2: will contain another element making up the returned value
* @mask: specifies which values to be requested
*
* Return: the type of value returned by the device
*/
int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
int ret, frequency;
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR;
st->param.sensor_odr.data =
EC_MOTION_SENSE_NO_VALUE;
ret = cros_ec_motion_send_host_cmd(st, 0);
if (ret)
break;
frequency = st->resp->sensor_odr.ret;
*val = frequency / 1000;
*val2 = (frequency % 1000) * 1000;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read);
/**
* cros_ec_sensors_core_read_avail() - get available values
* @indio_dev: pointer to state information for device
* @chan: channel specification structure table
* @vals: list of available values
* @type: type of data returned
* @length: number of data returned in the array
* @mask: specifies which values to be requested
*
* Return: an error code, IIO_AVAIL_RANGE or IIO_AVAIL_LIST
*/
int cros_ec_sensors_core_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals,
int *type,
int *length,
long mask)
{
struct cros_ec_sensors_core_state *state = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*length = ARRAY_SIZE(state->frequencies);
*vals = (const int *)&state->frequencies;
*type = IIO_VAL_INT_PLUS_MICRO;
return IIO_AVAIL_LIST;
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read_avail);
/**
* cros_ec_sensors_core_write() - function to write a value to the sensor
* @st: pointer to state information for device
* @chan: channel specification structure table
* @val: first part of value to write
* @val2: second part of value to write
* @mask: specifies which values to write
*
* Return: the type of value returned by the device
*/
int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
int ret, frequency;
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
frequency = val * 1000 + val2 / 1000;
st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR;
st->param.sensor_odr.data = frequency;
/* Always roundup, so caller gets at least what it asks for. */
st->param.sensor_odr.roundup = 1;
ret = cros_ec_motion_send_host_cmd(st, 0);
if (ret)
break;
/* Flush the FIFO when a sensor is stopped.
* If the FIFO has just been emptied, pending samples will be
* stuck until new samples are available. It will not happen
* when all the sensors are stopped.
*/
if (frequency == 0) {
st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH;
ret = cros_ec_motion_send_host_cmd(st, 0);
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write);
static int __maybe_unused cros_ec_sensors_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret = 0;
if (st->range_updated) {
mutex_lock(&st->cmd_lock);
st->param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
st->param.sensor_range.data = st->curr_range;
st->param.sensor_range.roundup = 1;
ret = cros_ec_motion_send_host_cmd(st, 0);
mutex_unlock(&st->cmd_lock);
}
return ret;
}
SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume);
EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops);
MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0
// Trace events for the ChromeOS Embedded Controller
//
// Copyright 2025 Google LLC.
#define TRACE_SYMBOL(a) {a, #a}
// Generate the list using the following script:
// sed -n 's/^.*\(MOTIONSENSE_CMD.*\) = .*,$/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h
#define MOTIONSENSE_CMDS \
TRACE_SYMBOL(MOTIONSENSE_CMD_DUMP), \
TRACE_SYMBOL(MOTIONSENSE_CMD_INFO), \
TRACE_SYMBOL(MOTIONSENSE_CMD_EC_RATE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_ODR), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_RANGE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_KB_WAKE_ANGLE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_DATA), \
TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INFO), \
TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_FLUSH), \
TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_READ), \
TRACE_SYMBOL(MOTIONSENSE_CMD_PERFORM_CALIB), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_OFFSET), \
TRACE_SYMBOL(MOTIONSENSE_CMD_LIST_ACTIVITIES), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SET_ACTIVITY), \
TRACE_SYMBOL(MOTIONSENSE_CMD_LID_ANGLE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INT_ENABLE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SPOOF), \
TRACE_SYMBOL(MOTIONSENSE_CMD_TABLET_MODE_LID_ANGLE), \
TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_SCALE)
#define CREATE_TRACE_POINTS
#include "cros_ec_sensors_trace.h"
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Trace events for the ChromeOS Embedded Controller
*
* Copyright 2025 Google LLC.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM cros_ec
#if !defined(_CROS_EC_SENSORS_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _CROS_EC_SENSORS_TRACE_H_
#include <linux/bits.h>
#include <linux/types.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/tracepoint.h>
TRACE_EVENT(cros_ec_motion_host_cmd,
TP_PROTO(struct ec_params_motion_sense *param,
struct ec_response_motion_sense *resp,
int retval),
TP_ARGS(param, resp, retval),
TP_STRUCT__entry(__field(uint8_t, cmd)
__field(uint8_t, sensor_id)
__field(uint32_t, data)
__field(int, retval)
__field(int32_t, ret)
),
TP_fast_assign(__entry->cmd = param->cmd;
__entry->sensor_id = param->sensor_odr.sensor_num;
__entry->data = param->sensor_odr.data;
__entry->retval = retval;
__entry->ret = retval > 0 ? resp->sensor_odr.ret : -1;
),
TP_printk("%s, id: %d, data: %u, result: %u, return: %d",
__print_symbolic(__entry->cmd, MOTIONSENSE_CMDS),
__entry->sensor_id,
__entry->data,
__entry->retval,
__entry->ret)
);
#endif /* _CROS_EC_SENSORS_TRACE_H_ */
/* this part must be outside header guard */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../drivers/iio/common/cros_ec_sensors
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE cros_ec_sensors_trace
#include <trace/define_trace.h>