Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

iio: cros_ec_sensors: add cros_ec_activity driver

ChromeOS EC can report activity information derived from the
accelerometer:
- Reports on-body/off-body as a proximity event.
- Reports significant motion as an activity event.

This new sensor is a virtual sensor, included only when the EC firmware
is compiled with the appropriate module.

Signed-off-by: Gwendal Grignou <gwendal@google.com>
Reviewed-by: Tzung-Bi Shih <tzungbi@kernel.org>
Link: https://patch.msgid.link/20250604053903.1376465-1-gwendal@google.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

authored by

Gwendal Grignou and committed by
Jonathan Cameron
c430955d f0fdb01d

+352 -2
+9
drivers/iio/common/cros_ec_sensors/Kconfig
··· 30 30 convertible devices. 31 31 This module is loaded when the EC can calculate the angle between the base 32 32 and the lid. 33 + 34 + config IIO_CROS_EC_ACTIVITY 35 + tristate "ChromeOS EC Activity Sensors" 36 + depends on IIO_CROS_EC_SENSORS_CORE 37 + help 38 + Module to handle activity events presented by the ChromeOS EC sensor hub. 39 + Activities can be a proximity detector (on body/off body detection) 40 + or a significant motion detector. 41 + Creates an IIO device to manage all activities.
+1
drivers/iio/common/cros_ec_sensors/Makefile
··· 7 7 obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros-ec-sensors-core.o 8 8 obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o 9 9 obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o 10 + obj-$(CONFIG_IIO_CROS_EC_ACTIVITY) += cros_ec_activity.o
+307
drivers/iio/common/cros_ec_sensors/cros_ec_activity.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * cros_ec_activity - Driver for activities/gesture recognition. 4 + * 5 + * Copyright 2025 Google, Inc 6 + * 7 + * This driver uses the cros-ec interface to communicate with the ChromeOS 8 + * EC about activity data. 9 + */ 10 + 11 + #include <linux/bits.h> 12 + #include <linux/cleanup.h> 13 + #include <linux/kernel.h> 14 + #include <linux/module.h> 15 + #include <linux/mutex.h> 16 + #include <linux/platform_device.h> 17 + #include <linux/types.h> 18 + 19 + #include <linux/platform_data/cros_ec_commands.h> 20 + #include <linux/platform_data/cros_ec_proto.h> 21 + 22 + #include <linux/iio/common/cros_ec_sensors_core.h> 23 + #include <linux/iio/events.h> 24 + #include <linux/iio/iio.h> 25 + #include <linux/iio/trigger_consumer.h> 26 + 27 + #define DRV_NAME "cros-ec-activity" 28 + 29 + /* state data for ec_sensors iio driver. */ 30 + struct cros_ec_sensors_state { 31 + /* Shared by all sensors */ 32 + struct cros_ec_sensors_core_state core; 33 + 34 + struct iio_chan_spec *channels; 35 + 36 + int body_detection_channel_index; 37 + int sig_motion_channel_index; 38 + }; 39 + 40 + static const struct iio_event_spec cros_ec_activity_single_shot[] = { 41 + { 42 + .type = IIO_EV_TYPE_CHANGE, 43 + /* significant motion trigger when we get out of still. */ 44 + .dir = IIO_EV_DIR_FALLING, 45 + .mask_separate = BIT(IIO_EV_INFO_ENABLE), 46 + }, 47 + }; 48 + 49 + static const struct iio_event_spec cros_ec_body_detect_events[] = { 50 + { 51 + .type = IIO_EV_TYPE_CHANGE, 52 + .dir = IIO_EV_DIR_EITHER, 53 + .mask_separate = BIT(IIO_EV_INFO_ENABLE), 54 + }, 55 + }; 56 + 57 + static int cros_ec_activity_sensors_read_raw(struct iio_dev *indio_dev, 58 + struct iio_chan_spec const *chan, 59 + int *val, int *val2, long mask) 60 + { 61 + struct cros_ec_sensors_state *st = iio_priv(indio_dev); 62 + int ret; 63 + 64 + if (chan->type != IIO_PROXIMITY || mask != IIO_CHAN_INFO_RAW) 65 + return -EINVAL; 66 + 67 + guard(mutex)(&st->core.cmd_lock); 68 + st->core.param.cmd = MOTIONSENSE_CMD_GET_ACTIVITY; 69 + st->core.param.get_activity.activity = 70 + MOTIONSENSE_ACTIVITY_BODY_DETECTION; 71 + ret = cros_ec_motion_send_host_cmd(&st->core, 0); 72 + if (ret) 73 + return ret; 74 + 75 + /* 76 + * EC actually report if a body is near (1) or far (0). 77 + * Units for proximity sensor after scale is in meter, 78 + * so invert the result to return 0m when near and 1m when far. 79 + */ 80 + *val = !st->core.resp->get_activity.state; 81 + return IIO_VAL_INT; 82 + } 83 + 84 + static int cros_ec_activity_read_event_config(struct iio_dev *indio_dev, 85 + const struct iio_chan_spec *chan, 86 + enum iio_event_type type, 87 + enum iio_event_direction dir) 88 + { 89 + struct cros_ec_sensors_state *st = iio_priv(indio_dev); 90 + int ret; 91 + 92 + if (chan->type != IIO_ACTIVITY && chan->type != IIO_PROXIMITY) 93 + return -EINVAL; 94 + 95 + guard(mutex)(&st->core.cmd_lock); 96 + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; 97 + ret = cros_ec_motion_send_host_cmd(&st->core, 0); 98 + if (ret) 99 + return ret; 100 + 101 + switch (chan->type) { 102 + case IIO_PROXIMITY: 103 + return !!(st->core.resp->list_activities.enabled & 104 + (1 << MOTIONSENSE_ACTIVITY_BODY_DETECTION)); 105 + case IIO_ACTIVITY: 106 + if (chan->channel2 == IIO_MOD_STILL) { 107 + return !!(st->core.resp->list_activities.enabled & 108 + (1 << MOTIONSENSE_ACTIVITY_SIG_MOTION)); 109 + } 110 + 111 + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", 112 + chan->channel2); 113 + return -EINVAL; 114 + default: 115 + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", 116 + chan->type); 117 + return -EINVAL; 118 + } 119 + } 120 + 121 + static int cros_ec_activity_write_event_config(struct iio_dev *indio_dev, 122 + const struct iio_chan_spec *chan, 123 + enum iio_event_type type, 124 + enum iio_event_direction dir, 125 + bool state) 126 + { 127 + struct cros_ec_sensors_state *st = iio_priv(indio_dev); 128 + 129 + guard(mutex)(&st->core.cmd_lock); 130 + st->core.param.cmd = MOTIONSENSE_CMD_SET_ACTIVITY; 131 + switch (chan->type) { 132 + case IIO_PROXIMITY: 133 + st->core.param.set_activity.activity = 134 + MOTIONSENSE_ACTIVITY_BODY_DETECTION; 135 + break; 136 + case IIO_ACTIVITY: 137 + if (chan->channel2 == IIO_MOD_STILL) { 138 + st->core.param.set_activity.activity = 139 + MOTIONSENSE_ACTIVITY_SIG_MOTION; 140 + break; 141 + } 142 + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", 143 + chan->channel2); 144 + return -EINVAL; 145 + default: 146 + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", 147 + chan->type); 148 + return -EINVAL; 149 + } 150 + st->core.param.set_activity.enable = state; 151 + return cros_ec_motion_send_host_cmd(&st->core, 0); 152 + } 153 + 154 + static int cros_ec_activity_push_data(struct iio_dev *indio_dev, 155 + s16 *data, s64 timestamp) 156 + { 157 + struct ec_response_activity_data *activity_data = 158 + (struct ec_response_activity_data *)data; 159 + enum motionsensor_activity activity = activity_data->activity; 160 + u8 state = activity_data->state; 161 + const struct cros_ec_sensors_state *st = iio_priv(indio_dev); 162 + const struct iio_chan_spec *chan; 163 + enum iio_event_direction dir; 164 + int index; 165 + 166 + switch (activity) { 167 + case MOTIONSENSE_ACTIVITY_BODY_DETECTION: 168 + index = st->body_detection_channel_index; 169 + dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; 170 + break; 171 + case MOTIONSENSE_ACTIVITY_SIG_MOTION: 172 + index = st->sig_motion_channel_index; 173 + dir = IIO_EV_DIR_FALLING; 174 + break; 175 + default: 176 + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", activity); 177 + return 0; 178 + } 179 + chan = &st->channels[index]; 180 + iio_push_event(indio_dev, 181 + IIO_UNMOD_EVENT_CODE(chan->type, index, chan->event_spec[0].type, dir), 182 + timestamp); 183 + return 0; 184 + } 185 + 186 + static irqreturn_t cros_ec_activity_capture(int irq, void *p) 187 + { 188 + struct iio_poll_func *pf = p; 189 + struct iio_dev *indio_dev = pf->indio_dev; 190 + 191 + /* 192 + * This callback would be called when a software trigger is 193 + * used. But when this virtual sensor is present, it is guaranteed 194 + * the sensor hub is advanced enough to not need a software trigger. 195 + */ 196 + dev_warn(&indio_dev->dev, "%s: Not Expected\n", __func__); 197 + return IRQ_NONE; 198 + } 199 + 200 + static const struct iio_info ec_sensors_info = { 201 + .read_raw = &cros_ec_activity_sensors_read_raw, 202 + .read_event_config = cros_ec_activity_read_event_config, 203 + .write_event_config = cros_ec_activity_write_event_config, 204 + }; 205 + 206 + static int cros_ec_sensors_probe(struct platform_device *pdev) 207 + { 208 + struct device *dev = &pdev->dev; 209 + struct cros_ec_device *ec_device = dev_get_drvdata(dev->parent); 210 + struct iio_dev *indio_dev; 211 + struct cros_ec_sensors_state *st; 212 + struct iio_chan_spec *channel; 213 + unsigned long activities; 214 + int i, index, ret, nb_activities; 215 + 216 + if (!ec_device) { 217 + dev_warn(dev, "No CROS EC device found.\n"); 218 + return -EINVAL; 219 + } 220 + 221 + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); 222 + if (!indio_dev) 223 + return -ENOMEM; 224 + 225 + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, 226 + cros_ec_activity_capture); 227 + if (ret) 228 + return ret; 229 + 230 + indio_dev->info = &ec_sensors_info; 231 + st = iio_priv(indio_dev); 232 + st->core.type = st->core.resp->info.type; 233 + st->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; 234 + 235 + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; 236 + ret = cros_ec_motion_send_host_cmd(&st->core, 0); 237 + if (ret) 238 + return ret; 239 + 240 + activities = st->core.resp->list_activities.enabled | 241 + st->core.resp->list_activities.disabled; 242 + if (!activities) 243 + return -ENODEV; 244 + 245 + /* Allocate a channel per activity and one for timestamp */ 246 + nb_activities = hweight_long(activities) + 1; 247 + st->channels = devm_kcalloc(dev, nb_activities, 248 + sizeof(*st->channels), GFP_KERNEL); 249 + if (!st->channels) 250 + return -ENOMEM; 251 + 252 + channel = &st->channels[0]; 253 + index = 0; 254 + for_each_set_bit(i, &activities, BITS_PER_LONG) { 255 + /* List all available triggers */ 256 + if (i == MOTIONSENSE_ACTIVITY_BODY_DETECTION) { 257 + channel->type = IIO_PROXIMITY; 258 + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); 259 + channel->event_spec = cros_ec_body_detect_events; 260 + channel->num_event_specs = 261 + ARRAY_SIZE(cros_ec_body_detect_events); 262 + st->body_detection_channel_index = index; 263 + } else { 264 + channel->type = IIO_ACTIVITY; 265 + channel->modified = 1; 266 + channel->event_spec = cros_ec_activity_single_shot; 267 + channel->num_event_specs = 268 + ARRAY_SIZE(cros_ec_activity_single_shot); 269 + if (i == MOTIONSENSE_ACTIVITY_SIG_MOTION) { 270 + channel->channel2 = IIO_MOD_STILL; 271 + st->sig_motion_channel_index = index; 272 + } else { 273 + dev_warn(dev, "Unknown activity: %d\n", i); 274 + continue; 275 + } 276 + } 277 + channel->ext_info = cros_ec_sensors_limited_info; 278 + channel->scan_index = index++; 279 + channel++; 280 + } 281 + 282 + /* Timestamp */ 283 + channel->scan_index = index; 284 + channel->type = IIO_TIMESTAMP; 285 + channel->channel = -1; 286 + channel->scan_type.sign = 's'; 287 + channel->scan_type.realbits = 64; 288 + channel->scan_type.storagebits = 64; 289 + 290 + indio_dev->channels = st->channels; 291 + indio_dev->num_channels = index + 1; 292 + 293 + return cros_ec_sensors_core_register(dev, indio_dev, 294 + cros_ec_activity_push_data); 295 + } 296 + 297 + static struct platform_driver cros_ec_sensors_platform_driver = { 298 + .driver = { 299 + .name = DRV_NAME, 300 + }, 301 + .probe = cros_ec_sensors_probe, 302 + }; 303 + module_platform_driver(cros_ec_sensors_platform_driver); 304 + 305 + MODULE_DESCRIPTION("ChromeOS EC activity sensors driver"); 306 + MODULE_ALIAS("platform:" DRV_NAME); 307 + MODULE_LICENSE("GPL v2");
+10
drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c
··· 486 486 }; 487 487 EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info); 488 488 489 + const struct iio_chan_spec_ext_info cros_ec_sensors_limited_info[] = { 490 + { 491 + .name = "id", 492 + .shared = IIO_SHARED_BY_ALL, 493 + .read = cros_ec_sensors_id 494 + }, 495 + { } 496 + }; 497 + EXPORT_SYMBOL_GPL(cros_ec_sensors_limited_info); 498 + 489 499 /** 490 500 * cros_ec_sensors_idx_to_reg - convert index into offset in shared memory 491 501 * @st: pointer to state information for device
+1
include/linux/iio/common/cros_ec_sensors_core.h
··· 126 126 127 127 /* List of extended channel specification for all sensors. */ 128 128 extern const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[]; 129 + extern const struct iio_chan_spec_ext_info cros_ec_sensors_limited_info[]; 129 130 130 131 #endif /* __CROS_EC_SENSORS_CORE_H */
+24 -2
include/linux/platform_data/cros_ec_commands.h
··· 2388 2388 */ 2389 2389 MOTIONSENSE_CMD_SENSOR_SCALE = 18, 2390 2390 2391 + /* 2392 + * Activity management 2393 + * Retrieve current status of given activity. 2394 + */ 2395 + MOTIONSENSE_CMD_GET_ACTIVITY = 20, 2396 + 2391 2397 /* Number of motionsense sub-commands. */ 2392 2398 MOTIONSENSE_NUM_CMDS 2393 2399 }; ··· 2453 2447 MOTIONSENSE_ORIENTATION_UNKNOWN = 4, 2454 2448 }; 2455 2449 2450 + struct ec_response_activity_data { 2451 + uint8_t activity; /* motionsensor_activity */ 2452 + uint8_t state; 2453 + } __ec_todo_packed; 2454 + 2456 2455 struct ec_response_motion_sensor_data { 2457 2456 /* Flags for each sensor. */ 2458 2457 uint8_t flags; ··· 2471 2460 uint32_t timestamp; 2472 2461 }; 2473 2462 struct __ec_todo_unpacked { 2474 - uint8_t activity; /* motionsensor_activity */ 2475 - uint8_t state; 2463 + struct ec_response_activity_data activity_data; 2476 2464 int16_t add_info[2]; 2477 2465 }; 2478 2466 }; ··· 2504 2494 MOTIONSENSE_ACTIVITY_SIG_MOTION = 1, 2505 2495 MOTIONSENSE_ACTIVITY_DOUBLE_TAP = 2, 2506 2496 MOTIONSENSE_ACTIVITY_ORIENTATION = 3, 2497 + MOTIONSENSE_ACTIVITY_BODY_DETECTION = 4, 2507 2498 }; 2508 2499 2509 2500 struct ec_motion_sense_activity { ··· 2682 2671 uint32_t max_data_vector; 2683 2672 } fifo_read; 2684 2673 2674 + /* Used for MOTIONSENSE_CMD_SET_ACTIVITY */ 2685 2675 struct ec_motion_sense_activity set_activity; 2686 2676 2687 2677 /* Used for MOTIONSENSE_CMD_LID_ANGLE */ ··· 2728 2716 */ 2729 2717 int16_t hys_degree; 2730 2718 } tablet_mode_threshold; 2719 + 2720 + /* Used for MOTIONSENSE_CMD_GET_ACTIVITY */ 2721 + struct __ec_todo_unpacked { 2722 + uint8_t sensor_num; 2723 + uint8_t activity; /* enum motionsensor_activity */ 2724 + } get_activity; 2731 2725 }; 2732 2726 } __ec_todo_packed; 2733 2727 ··· 2851 2833 uint16_t hys_degree; 2852 2834 } tablet_mode_threshold; 2853 2835 2836 + /* USED for MOTIONSENSE_CMD_GET_ACTIVITY. */ 2837 + struct __ec_todo_unpacked { 2838 + uint8_t state; 2839 + } get_activity; 2854 2840 }; 2855 2841 } __ec_todo_packed; 2856 2842