1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/drivers/i2c.h>
9 #include <zephyr/rtio/rtio.h>
10 #include <zephyr/drivers/i2c/rtio.h>
11 
12 #include <string.h>
13 
14 #define I2C_CONTROLLER_NODE              DT_ALIAS(i2c_controller)
15 #define I2C_CONTROLLER_TARGET_NODE       DT_ALIAS(i2c_controller_target)
16 #define I2C_CONTROLLER_DEVICE_GET        DEVICE_DT_GET(I2C_CONTROLLER_NODE)
17 #define I2C_CONTROLLER_TARGET_DEVICE_GET DEVICE_DT_GET(I2C_CONTROLLER_TARGET_NODE)
18 #define I2C_TARGET_ADDR                  0x0A
19 #define SAMPLE_TIMEOUT                   K_SECONDS(1)
20 
21 static const struct device *sample_i2c_controller = I2C_CONTROLLER_DEVICE_GET;
22 static const struct device *sample_i2c_controller_target = I2C_CONTROLLER_TARGET_DEVICE_GET;
23 
24 /* Data to write and buffer to store write in */
25 static uint8_t sample_write_data[] = {0x0A, 0x0B};
26 static uint8_t sample_write_buf[sizeof(sample_write_data)];
27 static uint32_t sample_write_buf_pos;
28 
29 /* Data to read and buffer to store read in */
30 static uint8_t sample_read_data[] = {0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5};
31 static uint32_t sample_read_data_pos;
32 static uint8_t sample_read_buf[sizeof(sample_read_data)];
33 
34 /*
35  * The user defines an RTIO context to which actions like writes and reads will be
36  * submitted, and the results of said actions will be retrieved.
37  *
38  * We will be using 3 submission queue events (SQEs); i2c write, i2c read,
39  * done callback, and 2 completion queue events (CQEs); i2c write result,
40  * i2c read result.
41  */
42 RTIO_DEFINE(sample_rtio, 3, 2);
43 
44 /*
45  * The user defines an RTIO IODEV which wraps the device which will perform the
46  * actions submitted to the RTIO context. In this sample, we are using an I2C
47  * controller device, so we use the I2C specific helper to define the RTIO IODEV.
48  */
49 I2C_IODEV_DEFINE(sample_rtio_iodev, I2C_CONTROLLER_NODE, I2C_TARGET_ADDR);
50 
51 /*
52  * For async write read operation we will be waiting for a callback from RTIO.
53  * We will wait on this sem which we will give from the callback.
54  */
55 static K_SEM_DEFINE(sample_write_read_sem, 0, 1);
56 
57 /*
58  * We register a simple I2C target which we will be targeting using RTIO. We
59  * store written data, and return sample_read_data when read.
60  */
sample_target_write_requested(struct i2c_target_config * target_config)61 static int sample_target_write_requested(struct i2c_target_config *target_config)
62 {
63 	sample_write_buf_pos = 0;
64 	return 0;
65 }
66 
sample_target_read_requested(struct i2c_target_config * target_config,uint8_t * val)67 static int sample_target_read_requested(struct i2c_target_config *target_config, uint8_t *val)
68 {
69 	sample_read_data_pos = 0;
70 	*val = sample_read_data[sample_read_data_pos];
71 	return 0;
72 }
73 
sample_target_write_received(struct i2c_target_config * target_config,uint8_t val)74 static int sample_target_write_received(struct i2c_target_config *target_config, uint8_t val)
75 {
76 	if (sample_write_buf_pos == sizeof(sample_write_buf)) {
77 		return -ENOMEM;
78 	}
79 
80 	sample_write_buf[sample_write_buf_pos] = val;
81 	sample_write_buf_pos++;
82 	return 0;
83 }
84 
sample_target_read_processed(struct i2c_target_config * target_config,uint8_t * val)85 static int sample_target_read_processed(struct i2c_target_config *target_config, uint8_t *val)
86 {
87 	sample_read_data_pos++;
88 
89 	if (sample_read_data_pos == sizeof(sample_read_data)) {
90 		return -ENOMEM;
91 	}
92 
93 	*val = sample_read_data[sample_read_data_pos];
94 	return 0;
95 }
96 
sample_target_buf_write_received(struct i2c_target_config * target_config,uint8_t * data,uint32_t size)97 static void sample_target_buf_write_received(struct i2c_target_config *target_config,
98 					     uint8_t *data,
99 					     uint32_t size)
100 {
101 	sample_write_buf_pos = MIN(size, ARRAY_SIZE(sample_write_buf));
102 	memcpy(sample_write_buf, data, sample_write_buf_pos);
103 }
104 
sample_target_buf_read_requested(struct i2c_target_config * target_config,uint8_t ** data,uint32_t * size)105 static int sample_target_buf_read_requested(struct i2c_target_config *target_config,
106 					    uint8_t **data,
107 					    uint32_t *size)
108 {
109 	*data = sample_read_data;
110 	*size = sizeof(sample_read_data);
111 	return 0;
112 }
113 
sample_target_stop(struct i2c_target_config * config)114 static int sample_target_stop(struct i2c_target_config *config)
115 {
116 	ARG_UNUSED(config);
117 	return 0;
118 }
119 
120 static const struct i2c_target_callbacks sample_target_callbacks = {
121 	.write_requested = sample_target_write_requested,
122 	.read_requested = sample_target_read_requested,
123 	.write_received = sample_target_write_received,
124 	.read_processed = sample_target_read_processed,
125 	.buf_write_received = sample_target_buf_write_received,
126 	.buf_read_requested = sample_target_buf_read_requested,
127 	.stop = sample_target_stop,
128 };
129 
130 static struct i2c_target_config sample_target_config = {
131 	.address = I2C_TARGET_ADDR,
132 	.callbacks = &sample_target_callbacks,
133 };
134 
sample_init_i2c_target(void)135 static int sample_init_i2c_target(void)
136 {
137 	return i2c_target_register(sample_i2c_controller_target, &sample_target_config);
138 }
139 
sample_reset_buffers(void)140 static void sample_reset_buffers(void)
141 {
142 	memset(sample_write_buf, 0, sizeof(sample_write_buf));
143 	memset(sample_read_buf, 0, sizeof(sample_read_buf));
144 }
145 
sample_standard_write_read(void)146 static int sample_standard_write_read(void)
147 {
148 	int ret;
149 	struct i2c_msg msgs[2];
150 
151 	msgs[0].buf = sample_write_data;
152 	msgs[0].len = sizeof(sample_write_data);
153 	msgs[0].flags = I2C_MSG_WRITE;
154 
155 	msgs[1].buf = sample_read_buf;
156 	msgs[1].len = sizeof(sample_read_buf);
157 	msgs[1].flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP;
158 
159 	ret = i2c_transfer(sample_i2c_controller,
160 			   msgs,
161 			   ARRAY_SIZE(msgs),
162 			   I2C_TARGET_ADDR);
163 	if (ret) {
164 		return -EIO;
165 	}
166 
167 	return 0;
168 }
169 
sample_validate_write_read(void)170 static int sample_validate_write_read(void)
171 {
172 	int ret;
173 
174 	if (sample_write_buf_pos != sizeof(sample_write_data)) {
175 		return -EIO;
176 	}
177 
178 	ret = memcmp(sample_write_buf, sample_write_data, sizeof(sample_write_data));
179 	if (ret) {
180 		return -EIO;
181 	}
182 
183 	ret = memcmp(sample_read_buf, sample_read_data, sizeof(sample_read_data));
184 	if (ret) {
185 		return -EIO;
186 	}
187 
188 	return 0;
189 }
190 
191 /* This is functionally identical to sample_standard_write_read() but uses RTIO */
sample_rtio_write_read(void)192 static int sample_rtio_write_read(void)
193 {
194 	struct rtio_sqe *wr_sqe, *rd_sqe;
195 	struct rtio_cqe *wr_rd_cqe;
196 	int ret;
197 
198 	/*
199 	 * We allocate one of the 3 submission queue events (SQEs) as defined by
200 	 * RTIO_DEFINE() and configure it to write sample_write_data to
201 	 * sample_rtio_iodev.
202 	 */
203 	wr_sqe = rtio_sqe_acquire(&sample_rtio);
204 	rtio_sqe_prep_write(wr_sqe,
205 			    &sample_rtio_iodev,
206 			    0,
207 			    sample_write_data,
208 			    sizeof(sample_write_data),
209 			    NULL);
210 
211 	/*
212 	 * This write SQE is followed by a read SQE, which is part of a single
213 	 * transaction. We configure this by setting the RTIO_SQE_TRANSACTION.
214 	 */
215 	wr_sqe->flags |= RTIO_SQE_TRANSACTION;
216 
217 	/*
218 	 * We then allocate an SQE and configure it to read
219 	 * sizeof(sample_read_buf) into sample_read_buf.
220 	 */
221 	rd_sqe = rtio_sqe_acquire(&sample_rtio);
222 	rtio_sqe_prep_read(rd_sqe,
223 			    &sample_rtio_iodev,
224 			    0,
225 			    sample_read_buf,
226 			    sizeof(sample_read_buf), NULL);
227 
228 	/*
229 	 * Since we are working with I2C messages, we need to specify the I2C
230 	 * message options. The I2C_READ and I2C_WRITE are implicit since we
231 	 * are preparing read and write SQEs, I2C_STOP and I2C_RESTART are not,
232 	 * so we add them to the read SQE.
233 	 */
234 	rd_sqe->iodev_flags = RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
235 
236 	/*
237 	 * With the two SQEs of the sample_rtio context prepared, we call
238 	 * rtio_submit() to have them executed. This call will execute all
239 	 * prepared SQEs.
240 	 *
241 	 * In this case, we wait for the two SQEs to be completed before
242 	 * continuing, similar to calling i2c_transfer().
243 	 */
244 	ret = rtio_submit(&sample_rtio, 2);
245 	if (ret) {
246 		return -EIO;
247 	}
248 
249 	/*
250 	 * Since the RTIO SQEs are executed in the background, we need to
251 	 * get the CQE after and check its result. Since we configured the
252 	 * write and read SQEs as a single transaction, only one CQE is
253 	 * generated which includes both of them. If we had chained them
254 	 * instead, one CQE would be created for each of them.
255 	 */
256 	wr_rd_cqe = rtio_cqe_consume(&sample_rtio);
257 	if (wr_rd_cqe->result) {
258 		return -EIO;
259 	}
260 
261 	/* Release the CQE after having checked its result. */
262 	rtio_cqe_release(&sample_rtio, wr_rd_cqe);
263 	return 0;
264 }
265 
rtio_write_read_done_callback(struct rtio * r,const struct rtio_sqe * sqe,void * arg0)266 static void rtio_write_read_done_callback(struct rtio *r, const struct rtio_sqe *sqe, void *arg0)
267 {
268 	struct k_sem *sem = arg0;
269 	struct rtio_cqe *wr_rd_cqe;
270 
271 	/* See sample_rtio_write_read() */
272 	wr_rd_cqe = rtio_cqe_consume(&sample_rtio);
273 	if (wr_rd_cqe->result) {
274 		/* Signal write and read SQEs completed with error */
275 		k_sem_reset(sem);
276 	}
277 
278 	/* See sample_rtio_write_read() */
279 	rtio_cqe_release(&sample_rtio, wr_rd_cqe);
280 
281 	/* Signal write and read SQEs completed with success */
282 	k_sem_give(sem);
283 }
284 
285 /*
286  * Aside from the blocking wait for the sample_write_read_sem, async RTIO
287  * can be performed entirely from within ISRs.
288  */
sample_rtio_write_read_async(void)289 static int sample_rtio_write_read_async(void)
290 {
291 	struct rtio_sqe *wr_sqe, *rd_sqe, *cb_sqe;
292 	int ret;
293 
294 	/* See sample_rtio_write_read() */
295 	wr_sqe = rtio_sqe_acquire(&sample_rtio);
296 	rtio_sqe_prep_write(wr_sqe,
297 			    &sample_rtio_iodev,
298 			    0,
299 			    sample_write_data,
300 			    sizeof(sample_write_data), NULL);
301 	wr_sqe->flags |= RTIO_SQE_TRANSACTION;
302 
303 	/* See sample_rtio_write_read() */
304 	rd_sqe = rtio_sqe_acquire(&sample_rtio);
305 	rtio_sqe_prep_read(rd_sqe,
306 			    &sample_rtio_iodev,
307 			    0,
308 			    sample_read_buf,
309 			    sizeof(sample_read_buf), NULL);
310 	rd_sqe->iodev_flags = RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
311 
312 	/*
313 	 * The next SQE is a callback which we will use to signal that the
314 	 * write and read SQEs have completed. It has to be executed only
315 	 * after the write and read SQEs, which we configure by setting the
316 	 * RTIO_SQE_CHAINED flag.
317 	 */
318 	rd_sqe->flags |= RTIO_SQE_CHAINED;
319 
320 	/*
321 	 * Prepare the callback SQE. The SQE allows us to pass an optional
322 	 * argument to the handler, which we will use to store a pointer to
323 	 * the binary semaphore we will be waiting on.
324 	 */
325 	cb_sqe = rtio_sqe_acquire(&sample_rtio);
326 	rtio_sqe_prep_callback_no_cqe(cb_sqe,
327 				      rtio_write_read_done_callback,
328 				      &sample_write_read_sem,
329 				      NULL);
330 
331 	/*
332 	 * Submit the SQEs for execution, without waiting for any of them
333 	 * to be completed. We use the callback to signal completion of all
334 	 * of them.
335 	 */
336 	ret = rtio_submit(&sample_rtio, 0);
337 	if (ret) {
338 		return -EIO;
339 	}
340 
341 	/*
342 	 * We wait for the callback which signals RTIO transfer has completed.
343 	 *
344 	 * We will be checking the CQE result from within the callback, which
345 	 * is entirely safe given RTIO is designed to work from ISR context.
346 	 * If the result is ok, we give the sem, if its not ok, we reset the
347 	 * sem (which makes k_sem_take() return an error).
348 	 */
349 	ret = k_sem_take(&sample_write_read_sem, SAMPLE_TIMEOUT);
350 	if (ret) {
351 		return -EIO;
352 	}
353 
354 	return 0;
355 }
356 
main(void)357 int main(void)
358 {
359 	int ret;
360 
361 	printk("%s %s\n", "init_i2c_target", "running");
362 	ret = sample_init_i2c_target();
363 	if (ret) {
364 		printk("%s %s\n", "init_i2c_target", "failed");
365 		return 0;
366 	}
367 
368 	sample_reset_buffers();
369 
370 	printk("%s %s\n", "standard_write_read", "running");
371 	ret = sample_standard_write_read();
372 	if (ret) {
373 		printk("%s %s\n", "standard_write_read", "failed");
374 		return 0;
375 	}
376 
377 	ret = sample_validate_write_read();
378 	if (ret) {
379 		printk("%s %s\n", "standard_write_read", "corrupted");
380 		return 0;
381 	}
382 
383 	sample_reset_buffers();
384 
385 	printk("%s %s\n", "rtio_write_read", "running");
386 	ret = sample_rtio_write_read();
387 	if (ret) {
388 		printk("%s %s\n", "rtio_write_read", "failed");
389 		return 0;
390 	}
391 
392 	ret = sample_validate_write_read();
393 	if (ret) {
394 		printk("%s %s\n", "rtio_write_read", "corrupted");
395 		return 0;
396 	}
397 
398 	sample_reset_buffers();
399 
400 	printk("%s %s\n", "rtio_write_read_async", "running");
401 	ret = sample_rtio_write_read_async();
402 	if (ret) {
403 		printk("%s %s\n", "rtio_write_read_async", "failed");
404 		return 0;
405 	}
406 
407 	ret = sample_validate_write_read();
408 	if (ret) {
409 		printk("%s %s\n", "rtio_write_read_async", "corrupted");
410 		return 0;
411 	}
412 
413 	printk("sample complete\n");
414 	return 0;
415 }
416