1 | /* |
2 | * *AT24C* series I2C EEPROM |
3 | * |
4 | * Copyright (c) 2015 Michael Davidsaver |
5 | * |
6 | * This work is licensed under the terms of the GNU GPL, version 2. See |
7 | * the LICENSE file in the top-level directory. |
8 | */ |
9 | |
10 | #include "qemu/osdep.h" |
11 | |
12 | #include "qapi/error.h" |
13 | #include "qemu/module.h" |
14 | #include "hw/i2c/i2c.h" |
15 | #include "hw/qdev-properties.h" |
16 | #include "sysemu/block-backend.h" |
17 | |
18 | /* #define DEBUG_AT24C */ |
19 | |
20 | #ifdef DEBUG_AT24C |
21 | #define DPRINTK(FMT, ...) printf(TYPE_AT24C_EE " : " FMT, ## __VA_ARGS__) |
22 | #else |
23 | #define DPRINTK(FMT, ...) do {} while (0) |
24 | #endif |
25 | |
26 | #define ERR(FMT, ...) fprintf(stderr, TYPE_AT24C_EE " : " FMT, \ |
27 | ## __VA_ARGS__) |
28 | |
29 | #define TYPE_AT24C_EE "at24c-eeprom" |
30 | #define AT24C_EE(obj) OBJECT_CHECK(EEPROMState, (obj), TYPE_AT24C_EE) |
31 | |
32 | typedef struct EEPROMState { |
33 | I2CSlave parent_obj; |
34 | |
35 | /* address counter */ |
36 | uint16_t cur; |
37 | /* total size in bytes */ |
38 | uint32_t rsize; |
39 | bool writable; |
40 | /* cells changed since last START? */ |
41 | bool changed; |
42 | /* during WRITE, # of address bytes transfered */ |
43 | uint8_t haveaddr; |
44 | |
45 | uint8_t *mem; |
46 | |
47 | BlockBackend *blk; |
48 | } EEPROMState; |
49 | |
50 | static |
51 | int at24c_eeprom_event(I2CSlave *s, enum i2c_event event) |
52 | { |
53 | EEPROMState *ee = container_of(s, EEPROMState, parent_obj); |
54 | |
55 | switch (event) { |
56 | case I2C_START_SEND: |
57 | case I2C_START_RECV: |
58 | case I2C_FINISH: |
59 | ee->haveaddr = 0; |
60 | DPRINTK("clear\n" ); |
61 | if (ee->blk && ee->changed) { |
62 | int len = blk_pwrite(ee->blk, 0, ee->mem, ee->rsize, 0); |
63 | if (len != ee->rsize) { |
64 | ERR(TYPE_AT24C_EE |
65 | " : failed to write backing file\n" ); |
66 | } |
67 | DPRINTK("Wrote to backing file\n" ); |
68 | } |
69 | ee->changed = false; |
70 | break; |
71 | case I2C_NACK: |
72 | break; |
73 | } |
74 | return 0; |
75 | } |
76 | |
77 | static |
78 | uint8_t at24c_eeprom_recv(I2CSlave *s) |
79 | { |
80 | EEPROMState *ee = AT24C_EE(s); |
81 | uint8_t ret; |
82 | |
83 | ret = ee->mem[ee->cur]; |
84 | |
85 | ee->cur = (ee->cur + 1u) % ee->rsize; |
86 | DPRINTK("Recv %02x %c\n" , ret, ret); |
87 | |
88 | return ret; |
89 | } |
90 | |
91 | static |
92 | int at24c_eeprom_send(I2CSlave *s, uint8_t data) |
93 | { |
94 | EEPROMState *ee = AT24C_EE(s); |
95 | |
96 | if (ee->haveaddr < 2) { |
97 | ee->cur <<= 8; |
98 | ee->cur |= data; |
99 | ee->haveaddr++; |
100 | if (ee->haveaddr == 2) { |
101 | ee->cur %= ee->rsize; |
102 | DPRINTK("Set pointer %04x\n" , ee->cur); |
103 | } |
104 | |
105 | } else { |
106 | if (ee->writable) { |
107 | DPRINTK("Send %02x\n" , data); |
108 | ee->mem[ee->cur] = data; |
109 | ee->changed = true; |
110 | } else { |
111 | DPRINTK("Send error %02x read-only\n" , data); |
112 | } |
113 | ee->cur = (ee->cur + 1u) % ee->rsize; |
114 | |
115 | } |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static void at24c_eeprom_realize(DeviceState *dev, Error **errp) |
121 | { |
122 | EEPROMState *ee = AT24C_EE(dev); |
123 | |
124 | if (ee->blk) { |
125 | int64_t len = blk_getlength(ee->blk); |
126 | |
127 | if (len != ee->rsize) { |
128 | error_setg(errp, "%s: Backing file size %" PRId64 " != %u" , |
129 | TYPE_AT24C_EE, len, ee->rsize); |
130 | return; |
131 | } |
132 | |
133 | if (blk_set_perm(ee->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, |
134 | BLK_PERM_ALL, &error_fatal) < 0) |
135 | { |
136 | error_setg(errp, "%s: Backing file incorrect permission" , |
137 | TYPE_AT24C_EE); |
138 | return; |
139 | } |
140 | } |
141 | |
142 | ee->mem = g_malloc0(ee->rsize); |
143 | } |
144 | |
145 | static |
146 | void at24c_eeprom_reset(DeviceState *state) |
147 | { |
148 | EEPROMState *ee = AT24C_EE(state); |
149 | |
150 | ee->changed = false; |
151 | ee->cur = 0; |
152 | ee->haveaddr = 0; |
153 | |
154 | memset(ee->mem, 0, ee->rsize); |
155 | |
156 | if (ee->blk) { |
157 | int len = blk_pread(ee->blk, 0, ee->mem, ee->rsize); |
158 | |
159 | if (len != ee->rsize) { |
160 | ERR(TYPE_AT24C_EE |
161 | " : Failed initial sync with backing file\n" ); |
162 | } |
163 | DPRINTK("Reset read backing file\n" ); |
164 | } |
165 | } |
166 | |
167 | static Property at24c_eeprom_props[] = { |
168 | DEFINE_PROP_UINT32("rom-size" , EEPROMState, rsize, 0), |
169 | DEFINE_PROP_BOOL("writable" , EEPROMState, writable, true), |
170 | DEFINE_PROP_DRIVE("drive" , EEPROMState, blk), |
171 | DEFINE_PROP_END_OF_LIST() |
172 | }; |
173 | |
174 | static |
175 | void at24c_eeprom_class_init(ObjectClass *klass, void *data) |
176 | { |
177 | DeviceClass *dc = DEVICE_CLASS(klass); |
178 | I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); |
179 | |
180 | dc->realize = &at24c_eeprom_realize; |
181 | k->event = &at24c_eeprom_event; |
182 | k->recv = &at24c_eeprom_recv; |
183 | k->send = &at24c_eeprom_send; |
184 | |
185 | dc->props = at24c_eeprom_props; |
186 | dc->reset = at24c_eeprom_reset; |
187 | } |
188 | |
189 | static |
190 | const TypeInfo at24c_eeprom_type = { |
191 | .name = TYPE_AT24C_EE, |
192 | .parent = TYPE_I2C_SLAVE, |
193 | .instance_size = sizeof(EEPROMState), |
194 | .class_size = sizeof(I2CSlaveClass), |
195 | .class_init = at24c_eeprom_class_init, |
196 | }; |
197 | |
198 | static void at24c_eeprom_register(void) |
199 | { |
200 | type_register_static(&at24c_eeprom_type); |
201 | } |
202 | |
203 | type_init(at24c_eeprom_register) |
204 | |