1 /*
2  * Copyright (c) 2024 Google LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/i2c.h>
8 #include <zephyr/drivers/i2c/rtio.h>
9 #include <zephyr/rtio/rtio.h>
10 #include <zephyr/rtio/work.h>
11 
12 #include <zephyr/logging/log.h>
13 LOG_MODULE_DECLARE(i2c_rtio, CONFIG_I2C_LOG_LEVEL);
14 
i2c_msg_from_rx(const struct rtio_iodev_sqe * iodev_sqe,struct i2c_msg * msg)15 static inline void i2c_msg_from_rx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
16 {
17 	__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_RX);
18 
19 	msg->buf = iodev_sqe->sqe.rx.buf;
20 	msg->len = iodev_sqe->sqe.rx.buf_len;
21 	msg->flags =
22 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
23 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
24 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
25 		I2C_MSG_READ;
26 }
27 
i2c_msg_from_tx(const struct rtio_iodev_sqe * iodev_sqe,struct i2c_msg * msg)28 static inline void i2c_msg_from_tx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
29 {
30 	__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_TX);
31 
32 	msg->buf = (uint8_t *)iodev_sqe->sqe.tx.buf;
33 	msg->len = iodev_sqe->sqe.tx.buf_len;
34 	msg->flags =
35 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
36 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
37 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
38 		I2C_MSG_WRITE;
39 }
40 
i2c_msg_from_tiny_tx(const struct rtio_iodev_sqe * iodev_sqe,struct i2c_msg * msg)41 static inline void i2c_msg_from_tiny_tx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
42 {
43 	__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_TINY_TX);
44 
45 	msg->buf = (uint8_t *)iodev_sqe->sqe.tiny_tx.buf;
46 	msg->len = iodev_sqe->sqe.tiny_tx.buf_len;
47 	msg->flags =
48 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
49 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
50 		((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
51 		I2C_MSG_WRITE;
52 }
53 
i2c_iodev_submit_work_handler(struct rtio_iodev_sqe * txn_first)54 void i2c_iodev_submit_work_handler(struct rtio_iodev_sqe *txn_first)
55 {
56 	const struct i2c_dt_spec *dt_spec = (const struct i2c_dt_spec *)txn_first->sqe.iodev->data;
57 	const struct device *dev = dt_spec->bus;
58 
59 	LOG_DBG("Sync RTIO work item for: %p", (void *)txn_first);
60 	uint32_t num_msgs = 0;
61 	int rc = 0;
62 	struct rtio_iodev_sqe *txn_last = txn_first;
63 
64 	/* We allocate the i2c_msg's on the stack, to do so
65 	 * the count of messages needs to be determined to
66 	 * ensure we don't go over the statically sized array.
67 	 */
68 	do {
69 		switch (txn_last->sqe.op) {
70 		case RTIO_OP_RX:
71 		case RTIO_OP_TX:
72 		case RTIO_OP_TINY_TX:
73 			num_msgs++;
74 			break;
75 		default:
76 			LOG_ERR("Invalid op code %d for submission %p", txn_last->sqe.op,
77 				(void *)&txn_last->sqe);
78 			rc = -EIO;
79 			break;
80 		}
81 		txn_last = rtio_txn_next(txn_last);
82 	} while (rc == 0 && txn_last != NULL);
83 
84 	if (rc != 0) {
85 		rtio_iodev_sqe_err(txn_first, rc);
86 		return;
87 	}
88 
89 	/* Allocate msgs on the stack, MISRA doesn't like VLAs so we need a statically
90 	 * sized array here. It's pretty unlikely we have more than 4 i2c messages
91 	 * in a transaction as we typically would only have 2, one to write a
92 	 * register address, and another to read/write the register into an array
93 	 */
94 	if (num_msgs > CONFIG_I2C_RTIO_FALLBACK_MSGS) {
95 		LOG_ERR("At most CONFIG_I2C_RTIO_FALLBACK_MSGS"
96 			" submissions in a transaction are"
97 			" allowed in the default handler");
98 		rtio_iodev_sqe_err(txn_first, -ENOMEM);
99 		return;
100 	}
101 	struct i2c_msg msgs[CONFIG_I2C_RTIO_FALLBACK_MSGS];
102 
103 	rc = 0;
104 	txn_last = txn_first;
105 
106 	/* Copy the transaction into the stack allocated msgs */
107 	for (int i = 0; i < num_msgs; i++) {
108 		switch (txn_last->sqe.op) {
109 		case RTIO_OP_RX:
110 			i2c_msg_from_rx(txn_last, &msgs[i]);
111 			break;
112 		case RTIO_OP_TX:
113 			i2c_msg_from_tx(txn_last, &msgs[i]);
114 			break;
115 		case RTIO_OP_TINY_TX:
116 			i2c_msg_from_tiny_tx(txn_last, &msgs[i]);
117 			break;
118 		default:
119 			rc = -EIO;
120 			break;
121 		}
122 
123 		txn_last = rtio_txn_next(txn_last);
124 	}
125 
126 	if (rc == 0) {
127 		__ASSERT_NO_MSG(num_msgs > 0);
128 
129 		rc = i2c_transfer(dev, msgs, num_msgs, dt_spec->addr);
130 	}
131 
132 	if (rc != 0) {
133 		rtio_iodev_sqe_err(txn_first, rc);
134 	} else {
135 		rtio_iodev_sqe_ok(txn_first, 0);
136 	}
137 }
138 
i2c_iodev_submit_fallback(const struct device * dev,struct rtio_iodev_sqe * iodev_sqe)139 void i2c_iodev_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
140 {
141 	LOG_DBG("Executing fallback for dev: %p, sqe: %p", (void *)dev, (void *)iodev_sqe);
142 
143 	struct rtio_work_req *req = rtio_work_req_alloc();
144 
145 	if (req == NULL) {
146 		rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
147 		return;
148 	}
149 
150 	rtio_work_req_submit(req, iodev_sqe, i2c_iodev_submit_work_handler);
151 }
152