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