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