/* * Copyright (c) 2017 BayLibre, SAS * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL #include LOG_MODULE_DECLARE(main); struct i2c_virtual_data { sys_slist_t targets; }; int i2c_virtual_runtime_configure(const struct device *dev, uint32_t config) { return 0; } static struct i2c_target_config *find_address(struct i2c_virtual_data *data, uint16_t address, bool is_10bit) { struct i2c_target_config *cfg = NULL; sys_snode_t *node; bool search_10bit; SYS_SLIST_FOR_EACH_NODE(&data->targets, node) { cfg = CONTAINER_OF(node, struct i2c_target_config, node); search_10bit = (cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS); if (cfg->address == address && search_10bit == is_10bit) { return cfg; } } return NULL; } /* Attach I2C targets */ int i2c_virtual_target_register(const struct device *dev, struct i2c_target_config *config) { struct i2c_virtual_data *data = dev->data; if (!config) { return -EINVAL; } /* Check the address is unique */ if (find_address(data, config->address, (config->flags & I2C_TARGET_FLAGS_ADDR_10_BITS))) { return -EINVAL; } sys_slist_append(&data->targets, &config->node); return 0; } int i2c_virtual_target_unregister(const struct device *dev, struct i2c_target_config *config) { struct i2c_virtual_data *data = dev->data; if (!config) { return -EINVAL; } if (!sys_slist_find_and_remove(&data->targets, &config->node)) { return -EINVAL; } return 0; } static int i2c_virtual_msg_write(const struct device *dev, struct i2c_msg *msg, struct i2c_target_config *config, bool prev_write) { unsigned int len = 0U; uint8_t *buf = msg->buf; int ret; if (!prev_write) { config->callbacks->write_requested(config); } len = msg->len; while (len) { ret = config->callbacks->write_received(config, *buf); if (ret) { goto error; } buf++; len--; } if (!(msg->flags & I2C_MSG_RESTART) && msg->flags & I2C_MSG_STOP) { config->callbacks->stop(config); } return 0; error: LOG_DBG("%s: NACK", __func__); return -EIO; } static int i2c_virtual_msg_read(const struct device *dev, struct i2c_msg *msg, struct i2c_target_config *config) { unsigned int len = msg->len; uint8_t *buf = msg->buf; if (!msg->len) { return 0; } config->callbacks->read_requested(config, buf); buf++; len--; while (len) { config->callbacks->read_processed(config, buf); buf++; len--; } if (!(msg->flags & I2C_MSG_RESTART) && msg->flags & I2C_MSG_STOP) { config->callbacks->stop(config); } return 0; } #define OPERATION(msg) (((struct i2c_msg *) msg)->flags & I2C_MSG_RW_MASK) static int i2c_virtual_transfer(const struct device *dev, struct i2c_msg *msg, uint8_t num_msgs, uint16_t target) { struct i2c_virtual_data *data = dev->data; struct i2c_msg *current, *next; struct i2c_target_config *cfg; bool is_write = false; int ret = 0; cfg = find_address(data, target, (msg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS)); if (!cfg) { return -EIO; } current = msg; current->flags |= I2C_MSG_RESTART; while (num_msgs > 0) { if (num_msgs > 1) { next = current + 1; /* * Stop or restart condition between messages * of different directions is required */ if (OPERATION(current) != OPERATION(next)) { if (!(next->flags & I2C_MSG_RESTART)) { ret = -EINVAL; break; } } } /* Stop condition is required for the last message */ if ((num_msgs == 1U) && !(current->flags & I2C_MSG_STOP)) { ret = -EINVAL; break; } if ((current->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { ret = i2c_virtual_msg_write(dev, current, cfg, is_write); is_write = true; } else { ret = i2c_virtual_msg_read(dev, current, cfg); is_write = false; } if (ret < 0) { break; } current++; num_msgs--; } return ret; } static DEVICE_API(i2c, api_funcs) = { .configure = i2c_virtual_runtime_configure, .transfer = i2c_virtual_transfer, .target_register = i2c_virtual_target_register, .target_unregister = i2c_virtual_target_unregister, }; static int i2c_virtual_init(const struct device *dev) { struct i2c_virtual_data *data = dev->data; sys_slist_init(&data->targets); return 0; } static struct i2c_virtual_data i2c_virtual_dev_data_0; DEVICE_DEFINE(i2c_virtual_0, CONFIG_I2C_VIRTUAL_NAME, &i2c_virtual_init, NULL, &i2c_virtual_dev_data_0, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api_funcs);