1 /*
2  * Copyright (c) 2025 Croxel Inc.
3  * Copyright (c) 2025 CogniPilot Foundation
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <zephyr/drivers/gpio.h>
9 #include <zephyr/drivers/sensor.h>
10 #include <zephyr/drivers/sensor_clock.h>
11 #include <zephyr/sys/check.h>
12 #include <zephyr/rtio/rtio.h>
13 #include <zephyr/rtio/work.h>
14 
15 #include "paa3905.h"
16 #include "paa3905_reg.h"
17 #include "paa3905_stream.h"
18 #include "paa3905_decoder.h"
19 #include "paa3905_bus.h"
20 
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_REGISTER(PAA3905_STREAM, CONFIG_SENSOR_LOG_LEVEL);
23 
paa3905_chip_recovery_handler(struct rtio_iodev_sqe * iodev_sqe)24 static void paa3905_chip_recovery_handler(struct rtio_iodev_sqe *iodev_sqe)
25 {
26 	const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
27 	const struct device *dev = cfg->sensor;
28 	int err;
29 
30 	err = paa3905_recover(dev);
31 
32 	if (err) {
33 		rtio_iodev_sqe_err(iodev_sqe, err);
34 	} else {
35 		rtio_iodev_sqe_ok(iodev_sqe, 0);
36 	}
37 }
38 
start_drdy_backup_timer(const struct device * dev)39 static void start_drdy_backup_timer(const struct device *dev)
40 {
41 	struct paa3905_data *data = dev->data;
42 	const struct paa3905_config *cfg = dev->config;
43 
44 	k_timer_start(&data->stream.timer,
45 		      K_MSEC(cfg->backup_timer_period),
46 		      K_NO_WAIT);
47 }
48 
paa3905_complete_result(struct rtio * ctx,const struct rtio_sqe * sqe,void * arg)49 static void paa3905_complete_result(struct rtio *ctx,
50 				    const struct rtio_sqe *sqe,
51 				    void *arg)
52 {
53 	const struct device *dev = (const struct device *)arg;
54 	struct paa3905_data *data = dev->data;
55 	struct rtio_iodev_sqe *iodev_sqe = data->stream.iodev_sqe;
56 	struct paa3905_encoded_data *edata = sqe->userdata;
57 	struct rtio_cqe *cqe;
58 	int err;
59 
60 	edata->header.events.drdy = true &&
61 				    data->stream.settings.enabled.drdy;
62 	edata->header.events.motion = REG_MOTION_DETECTED(edata->motion) &&
63 				      data->stream.settings.enabled.motion;
64 	edata->header.channels = 0;
65 
66 	if ((data->stream.settings.enabled.drdy &&
67 	      data->stream.settings.opt.drdy == SENSOR_STREAM_DATA_INCLUDE) ||
68 	    (data->stream.settings.enabled.motion &&
69 	      data->stream.settings.opt.motion == SENSOR_STREAM_DATA_INCLUDE)) {
70 		edata->header.channels |= paa3905_encode_channel(SENSOR_CHAN_POS_DXYZ);
71 	}
72 
73 	if (data->stream.settings.enabled.drdy) {
74 		start_drdy_backup_timer(dev);
75 	}
76 
77 	/** Attempt chip recovery if erratic behavior is detected  */
78 	if (!REG_OBSERVATION_CHIP_OK(edata->observation)) {
79 
80 		LOG_WRN("CHIP OK register indicates issues. Attempting chip recovery");
81 		struct rtio_work_req *req = rtio_work_req_alloc();
82 
83 		CHECKIF(!req) {
84 			LOG_ERR("Failed to allocate RTIO work request");
85 			rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
86 			return;
87 		}
88 
89 		rtio_work_req_submit(req, iodev_sqe, paa3905_chip_recovery_handler);
90 	} else {
91 		rtio_iodev_sqe_ok(iodev_sqe, 0);
92 	}
93 
94 	/* Flush RTIO bus CQEs */
95 	do {
96 		cqe = rtio_cqe_consume(ctx);
97 		if (cqe != NULL) {
98 			err = cqe->result;
99 			rtio_cqe_release(ctx, cqe);
100 		}
101 	} while (cqe != NULL);
102 }
103 
paa3905_stream_get_data(const struct device * dev)104 static void paa3905_stream_get_data(const struct device *dev)
105 {
106 	struct paa3905_data *data = dev->data;
107 	uint64_t cycles;
108 	int err;
109 
110 	CHECKIF(!data->stream.iodev_sqe) {
111 		LOG_WRN("No RTIO submission with an INT GPIO event");
112 		return;
113 	}
114 
115 	struct paa3905_encoded_data *buf;
116 	uint32_t buf_len;
117 	uint32_t buf_len_required = sizeof(struct paa3905_encoded_data);
118 
119 	err = rtio_sqe_rx_buf(data->stream.iodev_sqe,
120 			      buf_len_required,
121 			      buf_len_required,
122 			      (uint8_t **)&buf,
123 			      &buf_len);
124 	__ASSERT(err == 0, "Failed to acquire buffer (len: %d) for encoded data: %d. "
125 			   "Please revisit RTIO queue sizing and look for "
126 			   "bottlenecks during sensor data processing",
127 			   buf_len_required, err);
128 
129 	/** Still throw an error even if asserts are off */
130 	if (err) {
131 		struct rtio_iodev_sqe *iodev_sqe = data->stream.iodev_sqe;
132 
133 		LOG_ERR("Failed to acquire buffer for encoded data: %d", err);
134 
135 		data->stream.iodev_sqe = NULL;
136 		rtio_iodev_sqe_err(iodev_sqe, err);
137 		return;
138 	}
139 
140 	struct rtio_sqe *write_sqe = rtio_sqe_acquire(data->rtio.ctx);
141 	struct rtio_sqe *read_sqe = rtio_sqe_acquire(data->rtio.ctx);
142 	struct rtio_sqe *cb_sqe = rtio_sqe_acquire(data->rtio.ctx);
143 	uint8_t val;
144 
145 	err = sensor_clock_get_cycles(&cycles);
146 	CHECKIF(err) {
147 		struct rtio_iodev_sqe *iodev_sqe = data->stream.iodev_sqe;
148 
149 		LOG_ERR("Failed to get timestamp: %d", err);
150 
151 		data->stream.iodev_sqe = NULL;
152 		rtio_iodev_sqe_err(iodev_sqe, err);
153 		return;
154 	}
155 	buf->header.timestamp = sensor_clock_cycles_to_ns(cycles);
156 
157 	CHECKIF(!write_sqe || !read_sqe || !cb_sqe) {
158 		LOG_ERR("Failed to acquire RTIO SQE's");
159 		rtio_iodev_sqe_err(data->stream.iodev_sqe, -ENOMEM);
160 		return;
161 	}
162 
163 	val = REG_BURST_READ | REG_SPI_READ_BIT;
164 
165 	rtio_sqe_prep_tiny_write(write_sqe,
166 				 data->rtio.iodev,
167 				 RTIO_PRIO_HIGH,
168 				 &val,
169 				 1,
170 				 NULL);
171 	write_sqe->flags |= RTIO_SQE_TRANSACTION;
172 
173 	rtio_sqe_prep_read(read_sqe,
174 			   data->rtio.iodev,
175 			   RTIO_PRIO_HIGH,
176 			   buf->buf,
177 			   sizeof(buf->buf),
178 			   NULL);
179 	read_sqe->flags |= RTIO_SQE_CHAINED;
180 
181 	rtio_sqe_prep_callback_no_cqe(cb_sqe,
182 				      paa3905_complete_result,
183 				      (void *)dev,
184 				      buf);
185 
186 	rtio_submit(data->rtio.ctx, 0);
187 }
188 
paa3905_gpio_callback(const struct device * gpio_dev,struct gpio_callback * cb,uint32_t pins)189 static void paa3905_gpio_callback(const struct device *gpio_dev,
190 				  struct gpio_callback *cb,
191 				  uint32_t pins)
192 {
193 	struct paa3905_stream *stream = CONTAINER_OF(cb,
194 						     struct paa3905_stream,
195 						     cb);
196 	const struct device *dev = stream->dev;
197 	const struct paa3905_config *cfg = dev->config;
198 	int err;
199 
200 	/* Disable interrupts */
201 	err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
202 					      GPIO_INT_MODE_DISABLED);
203 	if (err) {
204 		LOG_ERR("Failed to disable interrupt");
205 		return;
206 	}
207 
208 	paa3905_stream_get_data(dev);
209 }
210 
paa3905_stream_drdy_timeout(struct k_timer * timer)211 static void paa3905_stream_drdy_timeout(struct k_timer *timer)
212 {
213 	struct paa3905_stream *stream = CONTAINER_OF(timer,
214 						     struct paa3905_stream,
215 						     timer);
216 	const struct device *dev = stream->dev;
217 	const struct paa3905_config *cfg = dev->config;
218 	int err;
219 
220 	/* Disable interrupts */
221 	err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
222 					      GPIO_INT_MODE_DISABLED);
223 	if (err) {
224 		LOG_ERR("Failed to disable interrupt");
225 		return;
226 	}
227 
228 	paa3905_stream_get_data(dev);
229 }
230 
settings_changed(const struct paa3905_stream * a,const struct paa3905_stream * b)231 static inline bool settings_changed(const struct paa3905_stream *a,
232 				    const struct paa3905_stream *b)
233 {
234 	return (a->settings.enabled.drdy != b->settings.enabled.drdy) ||
235 	       (a->settings.opt.drdy != b->settings.opt.drdy) ||
236 	       (a->settings.enabled.motion != b->settings.enabled.motion) ||
237 	       (a->settings.opt.motion != b->settings.opt.motion);
238 }
239 
paa3905_stream_submit(const struct device * dev,struct rtio_iodev_sqe * iodev_sqe)240 void paa3905_stream_submit(const struct device *dev,
241 			   struct rtio_iodev_sqe *iodev_sqe)
242 {
243 	const struct sensor_read_config *read_config = iodev_sqe->sqe.iodev->data;
244 	struct paa3905_data *data = dev->data;
245 	const struct paa3905_config *cfg = dev->config;
246 	int err;
247 
248 	/** This separate struct is required due to the streaming API using a
249 	 * multi-shot RTIO submission: meaning, re-submitting itself after
250 	 * completion; hence, we don't have context to determine if this was
251 	 * the first submission that kicked things off. We're then, inferring
252 	 * this by comparing if the read-config has changed, and only restart
253 	 * in such case.
254 	 */
255 	struct paa3905_stream stream = {0};
256 
257 	for (size_t i = 0 ; i < read_config->count ; i++) {
258 		switch (read_config->channels[i].chan_type) {
259 		case SENSOR_TRIG_DATA_READY:
260 			stream.settings.enabled.drdy = true;
261 			stream.settings.opt.drdy = read_config->triggers[i].opt;
262 			break;
263 		case SENSOR_TRIG_MOTION:
264 			stream.settings.enabled.motion = true;
265 			stream.settings.opt.motion = read_config->triggers[i].opt;
266 			break;
267 		default:
268 			LOG_ERR("Unsupported trigger (%d)", read_config->triggers[i].trigger);
269 			rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
270 			return;
271 		}
272 	}
273 
274 	/* Store context for next submission (handled within callbacks) */
275 	data->stream.iodev_sqe = iodev_sqe;
276 
277 	if (settings_changed(&data->stream, &stream)) {
278 		uint8_t motion_data[6];
279 
280 		data->stream.settings = stream.settings;
281 
282 		/* Disable interrupts */
283 		err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
284 						      GPIO_INT_MODE_DISABLED);
285 		if (err) {
286 			LOG_ERR("Failed to disable interrupt");
287 			rtio_iodev_sqe_err(iodev_sqe, err);
288 			return;
289 		}
290 
291 		/* Read reg's 0x02-0x06 to clear motion data. */
292 		err = paa3905_bus_read(dev, REG_MOTION, motion_data, sizeof(motion_data));
293 		if (err) {
294 			LOG_ERR("Failed to read motion data");
295 			rtio_iodev_sqe_err(iodev_sqe, err);
296 			return;
297 		}
298 	}
299 
300 	/* Re-enable interrupts */
301 	err = gpio_pin_interrupt_configure_dt(&cfg->int_gpio,
302 						GPIO_INT_LEVEL_ACTIVE);
303 	if (err) {
304 		LOG_ERR("Failed to enable interrupt");
305 		rtio_iodev_sqe_err(iodev_sqe, err);
306 		return;
307 	}
308 
309 	/** Back-up timer allows us to keep checking in with the sensor in
310 	 * spite of not having any motion. This results in allowing sensor
311 	 * recovery if falling in erratic state.
312 	 */
313 	if (data->stream.settings.enabled.drdy) {
314 		start_drdy_backup_timer(dev);
315 	}
316 }
317 
paa3905_stream_init(const struct device * dev)318 int paa3905_stream_init(const struct device *dev)
319 {
320 	const struct paa3905_config *cfg = dev->config;
321 	struct paa3905_data *data = dev->data;
322 	int err;
323 
324 	/** Needed to get back the device handle from the callback context */
325 	data->stream.dev = dev;
326 
327 	if (!cfg->int_gpio.port) {
328 		LOG_ERR("Interrupt GPIO not supplied");
329 		return -ENODEV;
330 	}
331 
332 	if (!gpio_is_ready_dt(&cfg->int_gpio)) {
333 		LOG_ERR("Interrupt GPIO not ready");
334 		return -ENODEV;
335 	}
336 
337 	err = gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
338 	if (err) {
339 		LOG_ERR("Failed to configure interrupt GPIO");
340 		return -EIO;
341 	}
342 
343 	gpio_init_callback(&data->stream.cb,
344 			   paa3905_gpio_callback,
345 			   BIT(cfg->int_gpio.pin));
346 
347 	err = gpio_add_callback(cfg->int_gpio.port, &data->stream.cb);
348 	if (err) {
349 		LOG_ERR("Failed to add interrupt callback");
350 		return -EIO;
351 	}
352 
353 	k_timer_init(&data->stream.timer, paa3905_stream_drdy_timeout, NULL);
354 
355 	return err;
356 }
357