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