]>
Commit | Line | Data |
---|---|---|
ffbbe7d0 MI |
1 | /* |
2 | * Exynos4210 I2C Bus Serial Interface Emulation | |
3 | * | |
4 | * Copyright (C) 2012 Samsung Electronics Co Ltd. | |
5 | * Maksim Kozlov, <[email protected]> | |
6 | * Igor Mitsyanko, <[email protected]> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
16 | * for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along | |
19 | * with this program; if not, see <http://www.gnu.org/licenses/>. | |
20 | * | |
21 | */ | |
22 | ||
8ef94f0b | 23 | #include "qemu/osdep.h" |
1de7afc9 | 24 | #include "qemu/timer.h" |
83c9f4ca | 25 | #include "hw/sysbus.h" |
0d09e41a | 26 | #include "hw/i2c/i2c.h" |
ffbbe7d0 MI |
27 | |
28 | #ifndef EXYNOS4_I2C_DEBUG | |
29 | #define EXYNOS4_I2C_DEBUG 0 | |
30 | #endif | |
31 | ||
32 | #define TYPE_EXYNOS4_I2C "exynos4210.i2c" | |
33 | #define EXYNOS4_I2C(obj) \ | |
34 | OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C) | |
35 | ||
36 | /* Exynos4210 I2C memory map */ | |
37 | #define EXYNOS4_I2C_MEM_SIZE 0x14 | |
38 | #define I2CCON_ADDR 0x00 /* control register */ | |
39 | #define I2CSTAT_ADDR 0x04 /* control/status register */ | |
40 | #define I2CADD_ADDR 0x08 /* address register */ | |
41 | #define I2CDS_ADDR 0x0c /* data shift register */ | |
42 | #define I2CLC_ADDR 0x10 /* line control register */ | |
43 | ||
44 | #define I2CCON_ACK_GEN (1 << 7) | |
45 | #define I2CCON_INTRS_EN (1 << 5) | |
46 | #define I2CCON_INT_PEND (1 << 4) | |
47 | ||
48 | #define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3) | |
49 | #define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2) | |
50 | #define I2CMODE_MASTER_Rx 0x2 | |
51 | #define I2CMODE_MASTER_Tx 0x3 | |
52 | #define I2CSTAT_LAST_BIT (1 << 0) | |
53 | #define I2CSTAT_OUTPUT_EN (1 << 4) | |
54 | #define I2CSTAT_START_BUSY (1 << 5) | |
55 | ||
56 | ||
57 | #if EXYNOS4_I2C_DEBUG | |
58 | #define DPRINT(fmt, args...) \ | |
59 | do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0) | |
60 | ||
61 | static const char *exynos4_i2c_get_regname(unsigned offset) | |
62 | { | |
63 | switch (offset) { | |
64 | case I2CCON_ADDR: | |
65 | return "I2CCON"; | |
66 | case I2CSTAT_ADDR: | |
67 | return "I2CSTAT"; | |
68 | case I2CADD_ADDR: | |
69 | return "I2CADD"; | |
70 | case I2CDS_ADDR: | |
71 | return "I2CDS"; | |
72 | case I2CLC_ADDR: | |
73 | return "I2CLC"; | |
74 | default: | |
75 | return "[?]"; | |
76 | } | |
77 | } | |
78 | ||
79 | #else | |
80 | #define DPRINT(fmt, args...) do { } while (0) | |
81 | #endif | |
82 | ||
83 | typedef struct Exynos4210I2CState { | |
43603329 AF |
84 | SysBusDevice parent_obj; |
85 | ||
ffbbe7d0 | 86 | MemoryRegion iomem; |
a5c82852 | 87 | I2CBus *bus; |
ffbbe7d0 MI |
88 | qemu_irq irq; |
89 | ||
90 | uint8_t i2ccon; | |
91 | uint8_t i2cstat; | |
92 | uint8_t i2cadd; | |
93 | uint8_t i2cds; | |
94 | uint8_t i2clc; | |
95 | bool scl_free; | |
96 | } Exynos4210I2CState; | |
97 | ||
98 | static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s) | |
99 | { | |
100 | if (s->i2ccon & I2CCON_INTRS_EN) { | |
101 | s->i2ccon |= I2CCON_INT_PEND; | |
102 | qemu_irq_raise(s->irq); | |
103 | } | |
104 | } | |
105 | ||
106 | static void exynos4210_i2c_data_receive(void *opaque) | |
107 | { | |
108 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
109 | int ret; | |
110 | ||
111 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
112 | s->scl_free = false; | |
113 | ret = i2c_recv(s->bus); | |
114 | if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
115 | s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */ | |
116 | } else { | |
117 | s->i2cds = ret; | |
118 | } | |
119 | exynos4210_i2c_raise_interrupt(s); | |
120 | } | |
121 | ||
122 | static void exynos4210_i2c_data_send(void *opaque) | |
123 | { | |
124 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
125 | ||
126 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
127 | s->scl_free = false; | |
128 | if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
129 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
130 | } | |
131 | exynos4210_i2c_raise_interrupt(s); | |
132 | } | |
133 | ||
a8170e5e | 134 | static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset, |
ffbbe7d0 MI |
135 | unsigned size) |
136 | { | |
137 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
138 | uint8_t value; | |
139 | ||
140 | switch (offset) { | |
141 | case I2CCON_ADDR: | |
142 | value = s->i2ccon; | |
143 | break; | |
144 | case I2CSTAT_ADDR: | |
145 | value = s->i2cstat; | |
146 | break; | |
147 | case I2CADD_ADDR: | |
148 | value = s->i2cadd; | |
149 | break; | |
150 | case I2CDS_ADDR: | |
151 | value = s->i2cds; | |
152 | s->scl_free = true; | |
153 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx && | |
154 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
155 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
156 | exynos4210_i2c_data_receive(s); | |
157 | } | |
158 | break; | |
159 | case I2CLC_ADDR: | |
160 | value = s->i2clc; | |
161 | break; | |
162 | default: | |
163 | value = 0; | |
164 | DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset); | |
165 | break; | |
166 | } | |
167 | ||
168 | DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset), | |
169 | (unsigned int)offset, value); | |
170 | return value; | |
171 | } | |
172 | ||
a8170e5e | 173 | static void exynos4210_i2c_write(void *opaque, hwaddr offset, |
ffbbe7d0 MI |
174 | uint64_t value, unsigned size) |
175 | { | |
176 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
177 | uint8_t v = value & 0xff; | |
178 | ||
179 | DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset), | |
180 | (unsigned int)offset, v); | |
181 | ||
182 | switch (offset) { | |
183 | case I2CCON_ADDR: | |
184 | s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND); | |
185 | if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) { | |
186 | s->i2ccon &= ~I2CCON_INT_PEND; | |
187 | qemu_irq_lower(s->irq); | |
188 | if (!(s->i2ccon & I2CCON_INTRS_EN)) { | |
189 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
190 | } | |
191 | ||
192 | if (s->i2cstat & I2CSTAT_START_BUSY) { | |
193 | if (s->scl_free) { | |
194 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) { | |
195 | exynos4210_i2c_data_send(s); | |
196 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == | |
197 | I2CMODE_MASTER_Rx) { | |
198 | exynos4210_i2c_data_receive(s); | |
199 | } | |
200 | } else { | |
201 | s->i2ccon |= I2CCON_INT_PEND; | |
202 | qemu_irq_raise(s->irq); | |
203 | } | |
204 | } | |
205 | } | |
206 | break; | |
207 | case I2CSTAT_ADDR: | |
208 | s->i2cstat = | |
209 | (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY); | |
210 | ||
211 | if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) { | |
212 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
213 | s->scl_free = true; | |
214 | qemu_irq_lower(s->irq); | |
215 | break; | |
216 | } | |
217 | ||
218 | /* Nothing to do if in i2c slave mode */ | |
219 | if (!I2C_IN_MASTER_MODE(s->i2cstat)) { | |
220 | break; | |
221 | } | |
222 | ||
223 | if (v & I2CSTAT_START_BUSY) { | |
224 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
225 | s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */ | |
226 | s->scl_free = false; | |
227 | ||
228 | /* Generate start bit and send slave address */ | |
229 | if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) && | |
230 | (s->i2ccon & I2CCON_ACK_GEN)) { | |
231 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
232 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) { | |
233 | exynos4210_i2c_data_receive(s); | |
234 | } | |
235 | exynos4210_i2c_raise_interrupt(s); | |
236 | } else { | |
237 | i2c_end_transfer(s->bus); | |
238 | if (!(s->i2ccon & I2CCON_INT_PEND)) { | |
239 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
240 | } | |
241 | s->scl_free = true; | |
242 | } | |
243 | break; | |
244 | case I2CADD_ADDR: | |
245 | if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) { | |
246 | s->i2cadd = v; | |
247 | } | |
248 | break; | |
249 | case I2CDS_ADDR: | |
250 | if (s->i2cstat & I2CSTAT_OUTPUT_EN) { | |
251 | s->i2cds = v; | |
252 | s->scl_free = true; | |
253 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx && | |
254 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
255 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
256 | exynos4210_i2c_data_send(s); | |
257 | } | |
258 | } | |
259 | break; | |
260 | case I2CLC_ADDR: | |
261 | s->i2clc = v; | |
262 | break; | |
263 | default: | |
264 | DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset); | |
265 | break; | |
266 | } | |
267 | } | |
268 | ||
269 | static const MemoryRegionOps exynos4210_i2c_ops = { | |
270 | .read = exynos4210_i2c_read, | |
271 | .write = exynos4210_i2c_write, | |
272 | .endianness = DEVICE_NATIVE_ENDIAN, | |
273 | }; | |
274 | ||
275 | static const VMStateDescription exynos4210_i2c_vmstate = { | |
6783ecf1 | 276 | .name = "exynos4210.i2c", |
ffbbe7d0 MI |
277 | .version_id = 1, |
278 | .minimum_version_id = 1, | |
279 | .fields = (VMStateField[]) { | |
280 | VMSTATE_UINT8(i2ccon, Exynos4210I2CState), | |
281 | VMSTATE_UINT8(i2cstat, Exynos4210I2CState), | |
282 | VMSTATE_UINT8(i2cds, Exynos4210I2CState), | |
283 | VMSTATE_UINT8(i2cadd, Exynos4210I2CState), | |
284 | VMSTATE_UINT8(i2clc, Exynos4210I2CState), | |
285 | VMSTATE_BOOL(scl_free, Exynos4210I2CState), | |
286 | VMSTATE_END_OF_LIST() | |
287 | } | |
288 | }; | |
289 | ||
290 | static void exynos4210_i2c_reset(DeviceState *d) | |
291 | { | |
292 | Exynos4210I2CState *s = EXYNOS4_I2C(d); | |
293 | ||
294 | s->i2ccon = 0x00; | |
295 | s->i2cstat = 0x00; | |
296 | s->i2cds = 0xFF; | |
297 | s->i2clc = 0x00; | |
298 | s->i2cadd = 0xFF; | |
299 | s->scl_free = true; | |
300 | } | |
301 | ||
93d6599f | 302 | static void exynos4210_i2c_init(Object *obj) |
ffbbe7d0 | 303 | { |
93d6599f XZ |
304 | DeviceState *dev = DEVICE(obj); |
305 | Exynos4210I2CState *s = EXYNOS4_I2C(obj); | |
306 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
ffbbe7d0 | 307 | |
93d6599f | 308 | memory_region_init_io(&s->iomem, obj, &exynos4210_i2c_ops, s, |
1437c94b | 309 | TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE); |
43603329 AF |
310 | sysbus_init_mmio(sbd, &s->iomem); |
311 | sysbus_init_irq(sbd, &s->irq); | |
312 | s->bus = i2c_init_bus(dev, "i2c"); | |
ffbbe7d0 MI |
313 | } |
314 | ||
315 | static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) | |
316 | { | |
317 | DeviceClass *dc = DEVICE_CLASS(klass); | |
ffbbe7d0 MI |
318 | |
319 | dc->vmsd = &exynos4210_i2c_vmstate; | |
320 | dc->reset = exynos4210_i2c_reset; | |
ffbbe7d0 MI |
321 | } |
322 | ||
323 | static const TypeInfo exynos4210_i2c_type_info = { | |
324 | .name = TYPE_EXYNOS4_I2C, | |
325 | .parent = TYPE_SYS_BUS_DEVICE, | |
326 | .instance_size = sizeof(Exynos4210I2CState), | |
93d6599f | 327 | .instance_init = exynos4210_i2c_init, |
ffbbe7d0 MI |
328 | .class_init = exynos4210_i2c_class_init, |
329 | }; | |
330 | ||
331 | static void exynos4210_i2c_register_types(void) | |
332 | { | |
333 | type_register_static(&exynos4210_i2c_type_info); | |
334 | } | |
335 | ||
336 | type_init(exynos4210_i2c_register_types) |