blob: db278393343d2dca11308b27386efb9e5bcc8375 [file] [log] [blame]
/*
* Copyright (c) 2024 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c/rtio.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/rtio/work.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(i2c_rtio, CONFIG_I2C_LOG_LEVEL);
static inline void i2c_msg_from_rx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
{
__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_RX);
msg->buf = iodev_sqe->sqe.rx.buf;
msg->len = iodev_sqe->sqe.rx.buf_len;
msg->flags =
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
I2C_MSG_READ;
}
static inline void i2c_msg_from_tx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
{
__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_TX);
msg->buf = (uint8_t *)iodev_sqe->sqe.tx.buf;
msg->len = iodev_sqe->sqe.tx.buf_len;
msg->flags =
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
I2C_MSG_WRITE;
}
static inline void i2c_msg_from_tiny_tx(const struct rtio_iodev_sqe *iodev_sqe, struct i2c_msg *msg)
{
__ASSERT_NO_MSG(iodev_sqe->sqe.op == RTIO_OP_TINY_TX);
msg->buf = (uint8_t *)iodev_sqe->sqe.tiny_tx.buf;
msg->len = iodev_sqe->sqe.tiny_tx.buf_len;
msg->flags =
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_STOP) ? I2C_MSG_STOP : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_RESTART) ? I2C_MSG_RESTART : 0) |
((iodev_sqe->sqe.iodev_flags & RTIO_IODEV_I2C_10_BITS) ? I2C_MSG_ADDR_10_BITS : 0) |
I2C_MSG_WRITE;
}
void i2c_iodev_submit_work_handler(struct rtio_iodev_sqe *txn_first)
{
const struct i2c_dt_spec *dt_spec = (const struct i2c_dt_spec *)txn_first->sqe.iodev->data;
const struct device *dev = dt_spec->bus;
LOG_DBG("Sync RTIO work item for: %p", (void *)txn_first);
uint32_t num_msgs = 0;
int rc = 0;
struct rtio_iodev_sqe *txn_last = txn_first;
/* We allocate the i2c_msg's on the stack, to do so
* the count of messages needs to be determined to
* ensure we don't go over the statically sized array.
*/
do {
switch (txn_last->sqe.op) {
case RTIO_OP_RX:
case RTIO_OP_TX:
case RTIO_OP_TINY_TX:
num_msgs++;
break;
default:
LOG_ERR("Invalid op code %d for submission %p", txn_last->sqe.op,
(void *)&txn_last->sqe);
rc = -EIO;
break;
}
txn_last = rtio_txn_next(txn_last);
} while (rc == 0 && txn_last != NULL);
if (rc != 0) {
rtio_iodev_sqe_err(txn_first, rc);
return;
}
/* Allocate msgs on the stack, MISRA doesn't like VLAs so we need a statically
* sized array here. It's pretty unlikely we have more than 4 i2c messages
* in a transaction as we typically would only have 2, one to write a
* register address, and another to read/write the register into an array
*/
if (num_msgs > CONFIG_I2C_RTIO_FALLBACK_MSGS) {
LOG_ERR("At most CONFIG_I2C_RTIO_FALLBACK_MSGS"
" submissions in a transaction are"
" allowed in the default handler");
rtio_iodev_sqe_err(txn_first, -ENOMEM);
return;
}
struct i2c_msg msgs[CONFIG_I2C_RTIO_FALLBACK_MSGS];
rc = 0;
txn_last = txn_first;
/* Copy the transaction into the stack allocated msgs */
for (int i = 0; i < num_msgs; i++) {
switch (txn_last->sqe.op) {
case RTIO_OP_RX:
i2c_msg_from_rx(txn_last, &msgs[i]);
break;
case RTIO_OP_TX:
i2c_msg_from_tx(txn_last, &msgs[i]);
break;
case RTIO_OP_TINY_TX:
i2c_msg_from_tiny_tx(txn_last, &msgs[i]);
break;
default:
rc = -EIO;
break;
}
txn_last = rtio_txn_next(txn_last);
}
if (rc == 0) {
__ASSERT_NO_MSG(num_msgs > 0);
rc = i2c_transfer(dev, msgs, num_msgs, dt_spec->addr);
}
if (rc != 0) {
rtio_iodev_sqe_err(txn_first, rc);
} else {
rtio_iodev_sqe_ok(txn_first, 0);
}
}
void i2c_iodev_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
LOG_DBG("Executing fallback for dev: %p, sqe: %p", (void *)dev, (void *)iodev_sqe);
struct rtio_work_req *req = rtio_work_req_alloc();
if (req == NULL) {
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
return;
}
rtio_work_req_submit(req, iodev_sqe, i2c_iodev_submit_work_handler);
}