1 /*
2  * This driver creates fake I2C buses which can contain emulated devices,
3  * implemented by a separate emulation driver. The API between this driver and
4  * its emulators is defined by struct i2c_emul_driver_api.
5  *
6  * Copyright 2020 Google LLC
7  * Copyright (c) 2020 Nordic Semiconductor ASA
8  *
9  * SPDX-License-Identifier: Apache-2.0
10  */
11 
12 #define DT_DRV_COMPAT zephyr_i2c_emul_controller
13 
14 #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
15 #include <logging/log.h>
16 LOG_MODULE_REGISTER(i2c_emul_ctlr);
17 
18 #include <device.h>
19 #include <drivers/emul.h>
20 #include <drivers/i2c.h>
21 #include <drivers/i2c_emul.h>
22 
23 /** Working data for the device */
24 struct i2c_emul_data {
25 	/* List of struct i2c_emul associated with the device */
26 	sys_slist_t emuls;
27 	/* I2C host configuration */
28 	uint32_t config;
29 };
30 
i2c_emul_get_config(const struct device * dev)31 uint32_t i2c_emul_get_config(const struct device *dev)
32 {
33 	struct i2c_emul_data *data = dev->data;
34 
35 	return data->config;
36 }
37 
38 /**
39  * Find an emulator by its I2C address
40  *
41  * @param dev I2C emulation controller device
42  * @param addr I2C address of that device
43  * @return emulator ro use
44  * @return NULL if not found
45  */
i2c_emul_find(const struct device * dev,int addr)46 static struct i2c_emul *i2c_emul_find(const struct device *dev, int addr)
47 {
48 	struct i2c_emul_data *data = dev->data;
49 	sys_snode_t *node;
50 
51 	SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) {
52 		struct i2c_emul *emul = NULL;
53 
54 		emul = CONTAINER_OF(node, struct i2c_emul, node);
55 		if (emul->addr == addr) {
56 			return emul;
57 		}
58 	}
59 
60 	return NULL;
61 }
62 
i2c_emul_configure(const struct device * dev,uint32_t dev_config)63 static int i2c_emul_configure(const struct device *dev, uint32_t dev_config)
64 {
65 	struct i2c_emul_data *data = dev->data;
66 
67 	data->config = dev_config;
68 
69 	return 0;
70 }
71 
i2c_emul_transfer(const struct device * dev,struct i2c_msg * msgs,uint8_t num_msgs,uint16_t addr)72 static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs,
73 			     uint8_t num_msgs, uint16_t addr)
74 {
75 	struct i2c_emul *emul;
76 	const struct i2c_emul_api *api;
77 	int ret;
78 
79 	emul = i2c_emul_find(dev, addr);
80 	if (!emul) {
81 		return -EIO;
82 	}
83 
84 	api = emul->api;
85 	__ASSERT_NO_MSG(emul->api);
86 	__ASSERT_NO_MSG(emul->api->transfer);
87 
88 	ret = api->transfer(emul, msgs, num_msgs, addr);
89 	if (ret) {
90 		return ret;
91 	}
92 
93 	return 0;
94 }
95 
96 /**
97  * Set up a new emulator and add it to the list
98  *
99  * @param dev I2C emulation controller device
100  */
i2c_emul_init(const struct device * dev)101 static int i2c_emul_init(const struct device *dev)
102 {
103 	struct i2c_emul_data *data = dev->data;
104 	const struct emul_list_for_bus *list = dev->config;
105 	int rc;
106 
107 	sys_slist_init(&data->emuls);
108 
109 	rc = emul_init_for_bus_from_list(dev, list);
110 
111 	return rc;
112 }
113 
i2c_emul_register(const struct device * dev,const char * name,struct i2c_emul * emul)114 int i2c_emul_register(const struct device *dev, const char *name,
115 		      struct i2c_emul *emul)
116 {
117 	struct i2c_emul_data *data = dev->data;
118 
119 	sys_slist_append(&data->emuls, &emul->node);
120 
121 	LOG_INF("Register emulator '%s' at I2C addr %02x\n", name, emul->addr);
122 
123 	return 0;
124 }
125 
126 /* Device instantiation */
127 
128 static struct i2c_driver_api i2c_emul_api = {
129 	.configure = i2c_emul_configure,
130 	.transfer = i2c_emul_transfer,
131 };
132 
133 #define EMUL_LINK_AND_COMMA(node_id) {		\
134 	.label = DT_LABEL(node_id),		\
135 },
136 
137 #define I2C_EMUL_INIT(n) \
138 	static const struct emul_link_for_bus emuls_##n[] = { \
139 		DT_FOREACH_CHILD(DT_DRV_INST(n), EMUL_LINK_AND_COMMA) \
140 	}; \
141 	static struct emul_list_for_bus i2c_emul_cfg_##n = { \
142 		.children = emuls_##n, \
143 		.num_children = ARRAY_SIZE(emuls_##n), \
144 	}; \
145 	static struct i2c_emul_data i2c_emul_data_##n; \
146 	DEVICE_DT_INST_DEFINE(n, \
147 			    i2c_emul_init, \
148 			    NULL, \
149 			    &i2c_emul_data_##n, \
150 			    &i2c_emul_cfg_##n, \
151 			    POST_KERNEL, \
152 			    CONFIG_I2C_INIT_PRIORITY, \
153 			    &i2c_emul_api);
154 
155 DT_INST_FOREACH_STATUS_OKAY(I2C_EMUL_INIT)
156