blob: d8833b69c0a128dc2c3a7cffa787035af54a01f1 [file] [log] [blame]
/*
* Copyright (c) 2017 BayLibre, SAS
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/drivers/i2c.h>
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
#include <zephyr/logging/log.h>
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 const struct i2c_driver_api 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);