1 /*
2  * Copyright 2020 Google LLC
3  * Copyright (c) 2020 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT atmel_at24
9 
10 #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(atmel_at24);
13 
14 #include <zephyr/device.h>
15 #include <zephyr/drivers/emul.h>
16 #include <zephyr/drivers/i2c.h>
17 #include <zephyr/drivers/i2c_emul.h>
18 
19 /** Run-time data used by the emulator */
20 struct at24_emul_data {
21 	/** I2C emulator detail */
22 	struct i2c_emul emul;
23 	/** AT24 device being emulated */
24 	const struct device *i2c;
25 	/** Current register to read (address) */
26 	uint32_t cur_reg;
27 };
28 
29 /** Static configuration for the emulator */
30 struct at24_emul_cfg {
31 	/** EEPROM data contents */
32 	uint8_t *buf;
33 	/** Size of EEPROM in bytes */
34 	uint32_t size;
35 	/** Address of EEPROM on i2c bus */
36 	uint16_t addr;
37 	/** Address width for EEPROM in bits (only 8 is supported at present) */
38 	uint8_t addr_width;
39 };
40 
41 /**
42  * Emulator an I2C transfer to an AT24 chip
43  *
44  * This handles simple reads and writes
45  *
46  * @param emul I2C emulation information
47  * @param msgs List of messages to process. For 'read' messages, this function
48  *	updates the 'buf' member with the data that was read
49  * @param num_msgs Number of messages to process
50  * @param addr Address of the I2C target device. This is assumed to be correct,
51  *	due to the
52  * @retval 0 If successful
53  * @retval -EIO General input / output error
54  */
at24_emul_transfer(const struct emul * target,struct i2c_msg * msgs,int num_msgs,int addr)55 static int at24_emul_transfer(const struct emul *target, struct i2c_msg *msgs,
56 			      int num_msgs, int addr)
57 {
58 	struct at24_emul_data *data;
59 	const struct at24_emul_cfg *cfg;
60 	unsigned int len;
61 	bool too_fast;
62 	uint32_t i2c_cfg;
63 
64 	data = target->data;
65 	cfg = target->cfg;
66 
67 	if (cfg->addr != addr) {
68 		LOG_ERR("Address mismatch, expected %02x, got %02x", cfg->addr,
69 			addr);
70 		return -EIO;
71 	}
72 
73 	if (i2c_get_config(data->i2c, &i2c_cfg)) {
74 		LOG_ERR("i2c_get_config failed");
75 		return -EIO;
76 	}
77 	/* For testing purposes, fail if the bus speed is above standard */
78 	too_fast = (I2C_SPEED_GET(i2c_cfg) > I2C_SPEED_STANDARD);
79 	if (too_fast) {
80 		LOG_ERR("Speed too high");
81 		return -EIO;
82 	}
83 
84 	i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
85 	switch (num_msgs) {
86 	case 1:
87 		if (msgs->flags & I2C_MSG_READ) {
88 			/* handle read */
89 			break;
90 		}
91 		data->cur_reg = msgs->buf[0];
92 		len = MIN(msgs->len - 1, cfg->size - data->cur_reg);
93 		memcpy(&cfg->buf[data->cur_reg], &msgs->buf[1], len);
94 		return 0;
95 	case 2:
96 		if (msgs->flags & I2C_MSG_READ) {
97 			LOG_ERR("Unexpected read");
98 			return -EIO;
99 		}
100 		data->cur_reg = msgs->buf[0];
101 
102 		/* Now process the 'read' part of the message */
103 		msgs++;
104 		if (!(msgs->flags & I2C_MSG_READ)) {
105 			LOG_ERR("Unexpected write");
106 			return -EIO;
107 		}
108 		break;
109 	default:
110 		LOG_ERR("Invalid number of messages");
111 		return -EIO;
112 	}
113 
114 	/* Read data from the EEPROM into the buffer */
115 	len = MIN(msgs->len, cfg->size - data->cur_reg);
116 	memcpy(msgs->buf, &cfg->buf[data->cur_reg], len);
117 	data->cur_reg += len;
118 
119 	return 0;
120 }
121 
122 /* Device instantiation */
123 
124 static struct i2c_emul_api bus_api = {
125 	.transfer = at24_emul_transfer,
126 };
127 
128 /**
129  * Set up a new AT24 emulator
130  *
131  * This should be called for each AT24 device that needs to be emulated. It
132  * registers it with the I2C emulation controller.
133  *
134  * @param target Emulation information
135  * @param parent Device to emulate (must use AT24 driver)
136  * @return 0 indicating success (always)
137  */
emul_atmel_at24_init(const struct emul * target,const struct device * parent)138 static int emul_atmel_at24_init(const struct emul *target, const struct device *parent)
139 {
140 	const struct at24_emul_cfg *cfg = target->cfg;
141 	struct at24_emul_data *data = target->data;
142 
143 	data->emul.api = &bus_api;
144 	data->emul.addr = cfg->addr;
145 	data->emul.target = target;
146 	data->i2c = parent;
147 	data->cur_reg = 0;
148 
149 	/* Start with an erased EEPROM, assuming all 0xff */
150 	memset(cfg->buf, 0xff, cfg->size);
151 
152 	return 0;
153 }
154 
155 #define EEPROM_AT24_EMUL(n)                                                                        \
156 	static uint8_t at24_emul_buf_##n[DT_INST_PROP(n, size)];                                   \
157 	static struct at24_emul_data at24_emul_data_##n;                                           \
158 	static const struct at24_emul_cfg at24_emul_cfg_##n = {                                    \
159 		.buf = at24_emul_buf_##n,                                                          \
160 		.size = DT_INST_PROP(n, size),                                                     \
161 		.addr = DT_INST_REG_ADDR(n),                                                       \
162 		.addr_width = 8,                                                                   \
163 	};                                                                                         \
164 	EMUL_DT_INST_DEFINE(n, emul_atmel_at24_init, &at24_emul_data_##n, &at24_emul_cfg_##n,      \
165 			    &bus_api, NULL)
166 
167 DT_INST_FOREACH_STATUS_OKAY(EEPROM_AT24_EMUL)
168