]>
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 | 12 | #include "qapi/error.h" |
0b8fa32f | 13 | #include "qemu/module.h" |
5d8424db | 14 | #include "hw/i2c/i2c.h" |
a27bd6c7 | 15 | #include "hw/qdev-properties.h" |
5d8424db MD |
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 | |
2ac4c5f4 | 78 | uint8_t at24c_eeprom_recv(I2CSlave *s) |
5d8424db MD |
79 | { |
80 | EEPROMState *ee = AT24C_EE(s); | |
2ac4c5f4 | 81 | uint8_t ret; |
5d8424db MD |
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 | ||
c8c9e103 | 120 | static void at24c_eeprom_realize(DeviceState *dev, Error **errp) |
5d8424db | 121 | { |
c8c9e103 | 122 | EEPROMState *ee = AT24C_EE(dev); |
5d8424db MD |
123 | |
124 | if (ee->blk) { | |
125 | int64_t len = blk_getlength(ee->blk); | |
126 | ||
127 | if (len != ee->rsize) { | |
c8c9e103 PMD |
128 | error_setg(errp, "%s: Backing file size %" PRId64 " != %u", |
129 | TYPE_AT24C_EE, len, ee->rsize); | |
130 | return; | |
5d8424db MD |
131 | } |
132 | ||
133 | if (blk_set_perm(ee->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, | |
134 | BLK_PERM_ALL, &error_fatal) < 0) | |
135 | { | |
c8c9e103 PMD |
136 | error_setg(errp, "%s: Backing file incorrect permission", |
137 | TYPE_AT24C_EE); | |
138 | return; | |
5d8424db MD |
139 | } |
140 | } | |
c8c9e103 PMD |
141 | |
142 | ee->mem = g_malloc0(ee->rsize); | |
5d8424db MD |
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 | ||
c8c9e103 | 180 | dc->realize = &at24c_eeprom_realize; |
5d8424db MD |
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) |