File Index
Files and directories
Sign in
FILE

cros_ec_activity.c

File preview with syntax highlighting.

linux-6.19.14/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c 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. */
 30struct 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
 40static 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
 49static 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
 57static 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
 84static 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
121static 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
154static 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
186static 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
200static 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
206static 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
297static struct platform_driver cros_ec_sensors_platform_driver = {
298	.driver = {
299		.name	= DRV_NAME,
300	},
301	.probe		= cros_ec_sensors_probe,
302};
303module_platform_driver(cros_ec_sensors_platform_driver);
304
305MODULE_DESCRIPTION("ChromeOS EC activity sensors driver");
306MODULE_ALIAS("platform:" DRV_NAME);
307MODULE_LICENSE("GPL v2");
~/ Read-only file preview.