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[CONFIG_I2C_RTIO_LOOPBACK_DATA_WRITE_MAX_SIZE];
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[CONFIG_I2C_RTIO_LOOPBACK_DATA_READ_MAX_SIZE];
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
97 #ifdef CONFIG_I2C_TARGET_BUFFER_MODE
sample_target_buf_write_received(struct i2c_target_config * target_config,uint8_t * data,uint32_t size)98 static void sample_target_buf_write_received(struct i2c_target_config *target_config,
99 uint8_t *data,
100 uint32_t size)
101 {
102 sample_write_buf_pos = MIN(size, ARRAY_SIZE(sample_write_buf));
103 memcpy(sample_write_buf, data, sample_write_buf_pos);
104 }
105
sample_target_buf_read_requested(struct i2c_target_config * target_config,uint8_t ** data,uint32_t * size)106 static int sample_target_buf_read_requested(struct i2c_target_config *target_config,
107 uint8_t **data,
108 uint32_t *size)
109 {
110 *data = sample_read_data;
111 *size = sizeof(sample_read_data);
112 return 0;
113 }
114 #endif /* CONFIG_I2C_TARGET_BUFFER_MODE */
115
sample_target_stop(struct i2c_target_config * config)116 static int sample_target_stop(struct i2c_target_config *config)
117 {
118 ARG_UNUSED(config);
119 return 0;
120 }
121
122 static const struct i2c_target_callbacks sample_target_callbacks = {
123 .write_requested = sample_target_write_requested,
124 .read_requested = sample_target_read_requested,
125 .write_received = sample_target_write_received,
126 .read_processed = sample_target_read_processed,
127 #ifdef CONFIG_I2C_TARGET_BUFFER_MODE
128 .buf_write_received = sample_target_buf_write_received,
129 .buf_read_requested = sample_target_buf_read_requested,
130 #endif
131 .stop = sample_target_stop,
132 };
133
134 static struct i2c_target_config sample_target_config = {
135 .address = I2C_TARGET_ADDR,
136 .callbacks = &sample_target_callbacks,
137 };
138
sample_init_i2c_target(void)139 static int sample_init_i2c_target(void)
140 {
141 return i2c_target_register(sample_i2c_controller_target, &sample_target_config);
142 }
143
sample_reset_buffers(void)144 static void sample_reset_buffers(void)
145 {
146 memset(sample_write_buf, 0, sizeof(sample_write_buf));
147 memset(sample_read_buf, 0, sizeof(sample_read_buf));
148 }
149
sample_standard_write_read(void)150 static int sample_standard_write_read(void)
151 {
152 int ret;
153 struct i2c_msg msgs[2];
154
155 msgs[0].buf = sample_write_data;
156 msgs[0].len = sizeof(sample_write_data);
157 msgs[0].flags = I2C_MSG_WRITE;
158
159 msgs[1].buf = sample_read_buf;
160 msgs[1].len = sizeof(sample_read_buf);
161 msgs[1].flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP;
162
163 ret = i2c_transfer(sample_i2c_controller,
164 msgs,
165 ARRAY_SIZE(msgs),
166 I2C_TARGET_ADDR);
167 if (ret) {
168 return -EIO;
169 }
170
171 return 0;
172 }
173
sample_validate_write_read(void)174 static int sample_validate_write_read(void)
175 {
176 int ret;
177
178 if (sample_write_buf_pos != sizeof(sample_write_data)) {
179 printk("Posittion error: %zu != %zu\n",
180 sample_write_buf_pos, sizeof(sample_write_data));
181 return -EIO;
182 }
183
184 ret = memcmp(sample_write_buf, sample_write_data, sizeof(sample_write_data));
185 if (ret) {
186 for (int n = 0; n < sizeof(sample_write_data); n++) {
187 if (sample_write_buf[n] != sample_write_data[n]) {
188 printk("Write at offset %u: %02x != %02x\n",
189 n, sample_write_buf[n], sample_write_data[n]);
190 }
191 }
192 return -EIO;
193 }
194
195 ret = memcmp(sample_read_buf, sample_read_data, sizeof(sample_read_data));
196 if (ret) {
197 for (int n = 0; n < sizeof(sample_read_data); n++) {
198 if (sample_read_buf[n] != sample_read_data[n]) {
199 printk("Read at offset %u: %02x != %02x\n",
200 n, sample_read_buf[n], sample_read_data[n]);
201 }
202 }
203 return -EIO;
204 }
205
206 return 0;
207 }
208
209 /* This is functionally identical to sample_standard_write_read() but uses RTIO */
sample_rtio_write_read(void)210 static int sample_rtio_write_read(void)
211 {
212 struct rtio_sqe *wr_sqe, *rd_sqe;
213 struct rtio_cqe *wr_rd_cqe;
214 int ret;
215
216 /*
217 * We allocate one of the 3 submission queue events (SQEs) as defined by
218 * RTIO_DEFINE() and configure it to write sample_write_data to
219 * sample_rtio_iodev.
220 */
221 wr_sqe = rtio_sqe_acquire(&sample_rtio);
222 rtio_sqe_prep_write(wr_sqe,
223 &sample_rtio_iodev,
224 0,
225 sample_write_data,
226 sizeof(sample_write_data),
227 NULL);
228
229 /*
230 * This write SQE is followed by a read SQE, which is part of a single
231 * transaction. We configure this by setting the RTIO_SQE_TRANSACTION.
232 */
233 wr_sqe->flags |= RTIO_SQE_TRANSACTION;
234
235 /*
236 * We then allocate an SQE and configure it to read
237 * sizeof(sample_read_buf) into sample_read_buf.
238 */
239 rd_sqe = rtio_sqe_acquire(&sample_rtio);
240 rtio_sqe_prep_read(rd_sqe,
241 &sample_rtio_iodev,
242 0,
243 sample_read_buf,
244 sizeof(sample_read_buf), NULL);
245
246 /*
247 * Since we are working with I2C messages, we need to specify the I2C
248 * message options. The I2C_READ and I2C_WRITE are implicit since we
249 * are preparing read and write SQEs, I2C_STOP and I2C_RESTART are not,
250 * so we add them to the read SQE.
251 */
252 rd_sqe->iodev_flags = RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
253
254 /*
255 * With the two SQEs of the sample_rtio context prepared, we call
256 * rtio_submit() to have them executed. This call will execute all
257 * prepared SQEs.
258 *
259 * In this case, we wait for the two SQEs to be completed before
260 * continuing, similar to calling i2c_transfer().
261 */
262 ret = rtio_submit(&sample_rtio, 2);
263 if (ret) {
264 return -EIO;
265 }
266
267 /*
268 * Since the RTIO SQEs are executed in the background, we need to
269 * get the CQE after and check its result. Since we configured the
270 * write and read SQEs as a single transaction, only one CQE is
271 * generated which includes both of them. If we had chained them
272 * instead, one CQE would be created for each of them.
273 */
274 wr_rd_cqe = rtio_cqe_consume(&sample_rtio);
275 if (wr_rd_cqe->result) {
276 return -EIO;
277 }
278
279 /* Release the CQE after having checked its result. */
280 rtio_cqe_release(&sample_rtio, wr_rd_cqe);
281 return 0;
282 }
283
rtio_write_read_done_callback(struct rtio * r,const struct rtio_sqe * sqe,int result,void * arg0)284 static void rtio_write_read_done_callback(struct rtio *r, const struct rtio_sqe *sqe,
285 int result, void *arg0)
286 {
287 struct k_sem *sem = arg0;
288 struct rtio_cqe *wr_rd_cqe;
289
290 /* See sample_rtio_write_read() */
291 wr_rd_cqe = rtio_cqe_consume(&sample_rtio);
292 if (wr_rd_cqe->result) {
293 /* Signal write and read SQEs completed with error */
294 k_sem_reset(sem);
295 }
296
297 /* See sample_rtio_write_read() */
298 rtio_cqe_release(&sample_rtio, wr_rd_cqe);
299
300 /* Signal write and read SQEs completed with success */
301 k_sem_give(sem);
302 }
303
304 /*
305 * Aside from the blocking wait for the sample_write_read_sem, async RTIO
306 * can be performed entirely from within ISRs.
307 */
sample_rtio_write_read_async(void)308 static int sample_rtio_write_read_async(void)
309 {
310 struct rtio_sqe *wr_sqe, *rd_sqe, *cb_sqe;
311 int ret;
312
313 /* See sample_rtio_write_read() */
314 wr_sqe = rtio_sqe_acquire(&sample_rtio);
315 rtio_sqe_prep_write(wr_sqe,
316 &sample_rtio_iodev,
317 0,
318 sample_write_data,
319 sizeof(sample_write_data), NULL);
320 wr_sqe->flags |= RTIO_SQE_TRANSACTION;
321
322 /* See sample_rtio_write_read() */
323 rd_sqe = rtio_sqe_acquire(&sample_rtio);
324 rtio_sqe_prep_read(rd_sqe,
325 &sample_rtio_iodev,
326 0,
327 sample_read_buf,
328 sizeof(sample_read_buf), NULL);
329 rd_sqe->iodev_flags = RTIO_IODEV_I2C_STOP | RTIO_IODEV_I2C_RESTART;
330
331 /*
332 * The next SQE is a callback which we will use to signal that the
333 * write and read SQEs have completed. It has to be executed only
334 * after the write and read SQEs, which we configure by setting the
335 * RTIO_SQE_CHAINED flag.
336 */
337 rd_sqe->flags |= RTIO_SQE_CHAINED;
338
339 /*
340 * Prepare the callback SQE. The SQE allows us to pass an optional
341 * argument to the handler, which we will use to store a pointer to
342 * the binary semaphore we will be waiting on.
343 */
344 cb_sqe = rtio_sqe_acquire(&sample_rtio);
345 rtio_sqe_prep_callback_no_cqe(cb_sqe,
346 rtio_write_read_done_callback,
347 &sample_write_read_sem,
348 NULL);
349
350 /*
351 * Submit the SQEs for execution, without waiting for any of them
352 * to be completed. We use the callback to signal completion of all
353 * of them.
354 */
355 ret = rtio_submit(&sample_rtio, 0);
356 if (ret) {
357 return -EIO;
358 }
359
360 /*
361 * We wait for the callback which signals RTIO transfer has completed.
362 *
363 * We will be checking the CQE result from within the callback, which
364 * is entirely safe given RTIO is designed to work from ISR context.
365 * If the result is ok, we give the sem, if its not ok, we reset the
366 * sem (which makes k_sem_take() return an error).
367 */
368 ret = k_sem_take(&sample_write_read_sem, SAMPLE_TIMEOUT);
369 if (ret) {
370 return -EIO;
371 }
372
373 return 0;
374 }
375
main(void)376 int main(void)
377 {
378 int ret, n;
379
380 for (n = 0; n < sizeof(sample_write_data); n++) {
381 sample_write_data[n] = (0xFF - n) % 0xFF;
382 }
383
384 for (n = 0; n < sizeof(sample_read_data); n++) {
385 sample_read_data[n] = n % 0xFF;
386 }
387
388 printk("%s %s\n", "init_i2c_target", "running");
389 ret = sample_init_i2c_target();
390 if (ret) {
391 printk("%s %s\n", "init_i2c_target", "failed");
392 return 0;
393 }
394
395 sample_reset_buffers();
396
397 printk("%s %s\n", "standard_write_read", "running");
398 ret = sample_standard_write_read();
399 if (ret) {
400 printk("%s %s\n", "standard_write_read", "failed");
401 return 0;
402 }
403
404 ret = sample_validate_write_read();
405 if (ret) {
406 printk("%s %s\n", "standard_write_read", "corrupted");
407 return 0;
408 }
409
410 sample_reset_buffers();
411
412 printk("%s %s\n", "rtio_write_read", "running");
413 ret = sample_rtio_write_read();
414 if (ret) {
415 printk("%s %s\n", "rtio_write_read", "failed");
416 return 0;
417 }
418
419 ret = sample_validate_write_read();
420 if (ret) {
421 printk("%s %s\n", "rtio_write_read", "corrupted");
422 return 0;
423 }
424
425 sample_reset_buffers();
426
427 printk("%s %s\n", "rtio_write_read_async", "running");
428 ret = sample_rtio_write_read_async();
429 if (ret) {
430 printk("%s %s\n", "rtio_write_read_async", "failed");
431 return 0;
432 }
433
434 ret = sample_validate_write_read();
435 if (ret) {
436 printk("%s %s\n", "rtio_write_read_async", "corrupted");
437 return 0;
438 }
439
440 printk("sample complete\n");
441 return 0;
442 }
443