xref: /illumos-gate/usr/src/test/i2c-tests/i2csimd/i2csimd_at24c.c (revision 0cbe48189888d02563dba9c90132ac391ba233b6)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  */
15 
16 /*
17  * Basic emulation of the AT24C family devices.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <err.h>
23 #include <sys/sysmacros.h>
24 
25 #include "i2csimd.h"
26 
27 typedef struct at24c {
28 	uint8_t *at_data;
29 	uint32_t at_len;
30 	uint8_t at_page;
31 	bool at_addr16;
32 	uint16_t at_curaddr;
33 } at24c_t;
34 
35 static bool
at24c_write(void * arg,uint32_t len,const uint8_t * buf)36 at24c_write(void *arg, uint32_t len, const uint8_t *buf)
37 {
38 	at24c_t *at24c = arg;
39 
40 	/*
41 	 * If we have no data to write, then we're done and that's fine.
42 	 */
43 	if (len == 0) {
44 		return (true);
45 	}
46 
47 	/*
48 	 * The first byte or two (depending on the part) always indicates the
49 	 * address that we should write to. If we require a 2-byte address but
50 	 * we don't have enough bytes, it's not quite clear what the device
51 	 * expects. We just treat it as setting the high byte.
52 	 */
53 	if (at24c->at_addr16) {
54 		at24c->at_curaddr = buf[0] << 8;
55 		if (len == 1) {
56 			return (true);
57 		}
58 		at24c->at_curaddr |= buf[1];
59 		len -= 2;
60 		buf += 2;
61 	} else {
62 		at24c->at_curaddr = buf[0];
63 		len--;
64 		buf++;
65 	}
66 
67 	/*
68 	 * Now that we've set the address, write to data within the page. Note
69 	 * that once we hit the end of the page, we go back to the start of the
70 	 * page.
71 	 */
72 	while (len > 0) {
73 		uint32_t page_start = at24c->at_curaddr &
74 		    ~(at24c->at_page - 1);
75 		uint32_t page_end = page_start + at24c->at_page - 1;
76 		uint32_t page_rem = page_end - at24c->at_curaddr + 1;
77 		uint32_t towrite = MIN(page_rem, len);
78 
79 		(void) memcpy(&at24c->at_data[at24c->at_curaddr], buf, towrite);
80 		len -= towrite;
81 		buf += towrite;
82 		at24c->at_curaddr += towrite;
83 		if (at24c->at_curaddr == page_end + 1) {
84 			at24c->at_curaddr = page_start;
85 		}
86 	}
87 
88 	return (true);
89 }
90 
91 static bool
at24c_read(void * arg,uint32_t len,uint8_t * buf)92 at24c_read(void *arg, uint32_t len, uint8_t *buf)
93 {
94 	at24c_t *at24c = arg;
95 
96 	/*
97 	 * Read from the current device offset. It should be incremented when
98 	 * we're done. If we read the entire device, then we wrap around to the
99 	 * start.
100 	 */
101 	while (len > 0) {
102 		uint16_t rem = at24c->at_len - at24c->at_curaddr + 1;
103 		uint16_t toread = MIN(rem, len);
104 
105 		(void) memcpy(buf, &at24c->at_data[at24c->at_curaddr], toread);
106 		len -= toread;
107 		buf += toread;
108 		at24c->at_curaddr += toread;
109 		if (at24c->at_curaddr == at24c->at_len)
110 			at24c->at_curaddr = 0;
111 	}
112 
113 	return (true);
114 }
115 
116 static const i2csimd_ops_t at24c_ops = {
117 	.sop_write = at24c_write,
118 	.sop_read = at24c_read
119 };
120 
121 static i2csimd_dev_t *
i2csimd_make_at24c(uint8_t addr,const char * data,size_t dlen,uint32_t len,uint8_t page,bool addr16)122 i2csimd_make_at24c(uint8_t addr, const char *data, size_t dlen, uint32_t len,
123     uint8_t page, bool addr16)
124 {
125 	at24c_t *at24c = calloc(1, sizeof (at24c_t));
126 	if (at24c == NULL) {
127 		err(EXIT_FAILURE, "failed to allocate at24c");
128 	}
129 
130 	at24c->at_len = len;
131 	at24c->at_data = calloc(sizeof (uint8_t), len);
132 	if (at24c->at_data == NULL) {
133 		err(EXIT_FAILURE, "failed to allocate %u bytes for data", len);
134 	}
135 
136 	(void) memset(at24c->at_data, 0xff, at24c->at_len);
137 	if (dlen > 0) {
138 		(void) memcpy(at24c->at_data, data, dlen);
139 	}
140 
141 	at24c->at_page = page;
142 	at24c->at_addr16 = addr16;
143 
144 	i2csimd_dev_t *dev = calloc(1, sizeof (i2csimd_dev_t));
145 	if (dev == NULL) {
146 		err(EXIT_FAILURE, "failed to allocate i2csimd_dev_t");
147 	}
148 
149 	dev->dev_name = "at24c";
150 	dev->dev_addr = addr;
151 	dev->dev_arg = at24c;
152 	dev->dev_ops = &at24c_ops;
153 
154 	return (dev);
155 }
156 
157 i2csimd_dev_t *
i2csimd_make_at24c32(uint8_t addr,const char * data,size_t len)158 i2csimd_make_at24c32(uint8_t addr, const char *data, size_t len)
159 {
160 	return (i2csimd_make_at24c(addr, data, len, 4096, 32, true));
161 }
162 
163 /*
164  * This makes a 256-bit AT24C slice that is 256 bytes wide. This is designed for
165  * the multi-address devices like the at24c04 or at24c16.
166  */
167 i2csimd_dev_t *
i2csimd_make_at24cXX(uint8_t addr,const char * data,size_t len)168 i2csimd_make_at24cXX(uint8_t addr, const char *data, size_t len)
169 {
170 	return (i2csimd_make_at24c(addr, data, len, 256, 16, false));
171 }
172