]>
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, <m.kozlov@samsung.com> | |
6 | * Igor Mitsyanko, <i.mitsyanko@samsung.com> | |
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 | ||
1de7afc9 | 23 | #include "qemu/timer.h" |
83c9f4ca | 24 | #include "hw/sysbus.h" |
0d09e41a | 25 | #include "hw/i2c/i2c.h" |
ffbbe7d0 MI |
26 | |
27 | #ifndef EXYNOS4_I2C_DEBUG | |
28 | #define EXYNOS4_I2C_DEBUG 0 | |
29 | #endif | |
30 | ||
31 | #define TYPE_EXYNOS4_I2C "exynos4210.i2c" | |
32 | #define EXYNOS4_I2C(obj) \ | |
33 | OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C) | |
34 | ||
35 | /* Exynos4210 I2C memory map */ | |
36 | #define EXYNOS4_I2C_MEM_SIZE 0x14 | |
37 | #define I2CCON_ADDR 0x00 /* control register */ | |
38 | #define I2CSTAT_ADDR 0x04 /* control/status register */ | |
39 | #define I2CADD_ADDR 0x08 /* address register */ | |
40 | #define I2CDS_ADDR 0x0c /* data shift register */ | |
41 | #define I2CLC_ADDR 0x10 /* line control register */ | |
42 | ||
43 | #define I2CCON_ACK_GEN (1 << 7) | |
44 | #define I2CCON_INTRS_EN (1 << 5) | |
45 | #define I2CCON_INT_PEND (1 << 4) | |
46 | ||
47 | #define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3) | |
48 | #define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2) | |
49 | #define I2CMODE_MASTER_Rx 0x2 | |
50 | #define I2CMODE_MASTER_Tx 0x3 | |
51 | #define I2CSTAT_LAST_BIT (1 << 0) | |
52 | #define I2CSTAT_OUTPUT_EN (1 << 4) | |
53 | #define I2CSTAT_START_BUSY (1 << 5) | |
54 | ||
55 | ||
56 | #if EXYNOS4_I2C_DEBUG | |
57 | #define DPRINT(fmt, args...) \ | |
58 | do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0) | |
59 | ||
60 | static const char *exynos4_i2c_get_regname(unsigned offset) | |
61 | { | |
62 | switch (offset) { | |
63 | case I2CCON_ADDR: | |
64 | return "I2CCON"; | |
65 | case I2CSTAT_ADDR: | |
66 | return "I2CSTAT"; | |
67 | case I2CADD_ADDR: | |
68 | return "I2CADD"; | |
69 | case I2CDS_ADDR: | |
70 | return "I2CDS"; | |
71 | case I2CLC_ADDR: | |
72 | return "I2CLC"; | |
73 | default: | |
74 | return "[?]"; | |
75 | } | |
76 | } | |
77 | ||
78 | #else | |
79 | #define DPRINT(fmt, args...) do { } while (0) | |
80 | #endif | |
81 | ||
82 | typedef struct Exynos4210I2CState { | |
43603329 AF |
83 | SysBusDevice parent_obj; |
84 | ||
ffbbe7d0 MI |
85 | MemoryRegion iomem; |
86 | i2c_bus *bus; | |
87 | qemu_irq irq; | |
88 | ||
89 | uint8_t i2ccon; | |
90 | uint8_t i2cstat; | |
91 | uint8_t i2cadd; | |
92 | uint8_t i2cds; | |
93 | uint8_t i2clc; | |
94 | bool scl_free; | |
95 | } Exynos4210I2CState; | |
96 | ||
97 | static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s) | |
98 | { | |
99 | if (s->i2ccon & I2CCON_INTRS_EN) { | |
100 | s->i2ccon |= I2CCON_INT_PEND; | |
101 | qemu_irq_raise(s->irq); | |
102 | } | |
103 | } | |
104 | ||
105 | static void exynos4210_i2c_data_receive(void *opaque) | |
106 | { | |
107 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
108 | int ret; | |
109 | ||
110 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
111 | s->scl_free = false; | |
112 | ret = i2c_recv(s->bus); | |
113 | if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
114 | s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */ | |
115 | } else { | |
116 | s->i2cds = ret; | |
117 | } | |
118 | exynos4210_i2c_raise_interrupt(s); | |
119 | } | |
120 | ||
121 | static void exynos4210_i2c_data_send(void *opaque) | |
122 | { | |
123 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
124 | ||
125 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
126 | s->scl_free = false; | |
127 | if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
128 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
129 | } | |
130 | exynos4210_i2c_raise_interrupt(s); | |
131 | } | |
132 | ||
a8170e5e | 133 | static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset, |
ffbbe7d0 MI |
134 | unsigned size) |
135 | { | |
136 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
137 | uint8_t value; | |
138 | ||
139 | switch (offset) { | |
140 | case I2CCON_ADDR: | |
141 | value = s->i2ccon; | |
142 | break; | |
143 | case I2CSTAT_ADDR: | |
144 | value = s->i2cstat; | |
145 | break; | |
146 | case I2CADD_ADDR: | |
147 | value = s->i2cadd; | |
148 | break; | |
149 | case I2CDS_ADDR: | |
150 | value = s->i2cds; | |
151 | s->scl_free = true; | |
152 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx && | |
153 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
154 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
155 | exynos4210_i2c_data_receive(s); | |
156 | } | |
157 | break; | |
158 | case I2CLC_ADDR: | |
159 | value = s->i2clc; | |
160 | break; | |
161 | default: | |
162 | value = 0; | |
163 | DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset); | |
164 | break; | |
165 | } | |
166 | ||
167 | DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset), | |
168 | (unsigned int)offset, value); | |
169 | return value; | |
170 | } | |
171 | ||
a8170e5e | 172 | static void exynos4210_i2c_write(void *opaque, hwaddr offset, |
ffbbe7d0 MI |
173 | uint64_t value, unsigned size) |
174 | { | |
175 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
176 | uint8_t v = value & 0xff; | |
177 | ||
178 | DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset), | |
179 | (unsigned int)offset, v); | |
180 | ||
181 | switch (offset) { | |
182 | case I2CCON_ADDR: | |
183 | s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND); | |
184 | if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) { | |
185 | s->i2ccon &= ~I2CCON_INT_PEND; | |
186 | qemu_irq_lower(s->irq); | |
187 | if (!(s->i2ccon & I2CCON_INTRS_EN)) { | |
188 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
189 | } | |
190 | ||
191 | if (s->i2cstat & I2CSTAT_START_BUSY) { | |
192 | if (s->scl_free) { | |
193 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) { | |
194 | exynos4210_i2c_data_send(s); | |
195 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == | |
196 | I2CMODE_MASTER_Rx) { | |
197 | exynos4210_i2c_data_receive(s); | |
198 | } | |
199 | } else { | |
200 | s->i2ccon |= I2CCON_INT_PEND; | |
201 | qemu_irq_raise(s->irq); | |
202 | } | |
203 | } | |
204 | } | |
205 | break; | |
206 | case I2CSTAT_ADDR: | |
207 | s->i2cstat = | |
208 | (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY); | |
209 | ||
210 | if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) { | |
211 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
212 | s->scl_free = true; | |
213 | qemu_irq_lower(s->irq); | |
214 | break; | |
215 | } | |
216 | ||
217 | /* Nothing to do if in i2c slave mode */ | |
218 | if (!I2C_IN_MASTER_MODE(s->i2cstat)) { | |
219 | break; | |
220 | } | |
221 | ||
222 | if (v & I2CSTAT_START_BUSY) { | |
223 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
224 | s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */ | |
225 | s->scl_free = false; | |
226 | ||
227 | /* Generate start bit and send slave address */ | |
228 | if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) && | |
229 | (s->i2ccon & I2CCON_ACK_GEN)) { | |
230 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
231 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) { | |
232 | exynos4210_i2c_data_receive(s); | |
233 | } | |
234 | exynos4210_i2c_raise_interrupt(s); | |
235 | } else { | |
236 | i2c_end_transfer(s->bus); | |
237 | if (!(s->i2ccon & I2CCON_INT_PEND)) { | |
238 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
239 | } | |
240 | s->scl_free = true; | |
241 | } | |
242 | break; | |
243 | case I2CADD_ADDR: | |
244 | if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) { | |
245 | s->i2cadd = v; | |
246 | } | |
247 | break; | |
248 | case I2CDS_ADDR: | |
249 | if (s->i2cstat & I2CSTAT_OUTPUT_EN) { | |
250 | s->i2cds = v; | |
251 | s->scl_free = true; | |
252 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx && | |
253 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
254 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
255 | exynos4210_i2c_data_send(s); | |
256 | } | |
257 | } | |
258 | break; | |
259 | case I2CLC_ADDR: | |
260 | s->i2clc = v; | |
261 | break; | |
262 | default: | |
263 | DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset); | |
264 | break; | |
265 | } | |
266 | } | |
267 | ||
268 | static const MemoryRegionOps exynos4210_i2c_ops = { | |
269 | .read = exynos4210_i2c_read, | |
270 | .write = exynos4210_i2c_write, | |
271 | .endianness = DEVICE_NATIVE_ENDIAN, | |
272 | }; | |
273 | ||
274 | static const VMStateDescription exynos4210_i2c_vmstate = { | |
6783ecf1 | 275 | .name = "exynos4210.i2c", |
ffbbe7d0 MI |
276 | .version_id = 1, |
277 | .minimum_version_id = 1, | |
278 | .fields = (VMStateField[]) { | |
279 | VMSTATE_UINT8(i2ccon, Exynos4210I2CState), | |
280 | VMSTATE_UINT8(i2cstat, Exynos4210I2CState), | |
281 | VMSTATE_UINT8(i2cds, Exynos4210I2CState), | |
282 | VMSTATE_UINT8(i2cadd, Exynos4210I2CState), | |
283 | VMSTATE_UINT8(i2clc, Exynos4210I2CState), | |
284 | VMSTATE_BOOL(scl_free, Exynos4210I2CState), | |
285 | VMSTATE_END_OF_LIST() | |
286 | } | |
287 | }; | |
288 | ||
289 | static void exynos4210_i2c_reset(DeviceState *d) | |
290 | { | |
291 | Exynos4210I2CState *s = EXYNOS4_I2C(d); | |
292 | ||
293 | s->i2ccon = 0x00; | |
294 | s->i2cstat = 0x00; | |
295 | s->i2cds = 0xFF; | |
296 | s->i2clc = 0x00; | |
297 | s->i2cadd = 0xFF; | |
298 | s->scl_free = true; | |
299 | } | |
300 | ||
43603329 | 301 | static int exynos4210_i2c_realize(SysBusDevice *sbd) |
ffbbe7d0 | 302 | { |
43603329 | 303 | DeviceState *dev = DEVICE(sbd); |
ffbbe7d0 MI |
304 | Exynos4210I2CState *s = EXYNOS4_I2C(dev); |
305 | ||
1437c94b PB |
306 | memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_i2c_ops, s, |
307 | TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE); | |
43603329 AF |
308 | sysbus_init_mmio(sbd, &s->iomem); |
309 | sysbus_init_irq(sbd, &s->irq); | |
310 | s->bus = i2c_init_bus(dev, "i2c"); | |
ffbbe7d0 MI |
311 | return 0; |
312 | } | |
313 | ||
314 | static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) | |
315 | { | |
316 | DeviceClass *dc = DEVICE_CLASS(klass); | |
317 | SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); | |
318 | ||
319 | dc->vmsd = &exynos4210_i2c_vmstate; | |
320 | dc->reset = exynos4210_i2c_reset; | |
321 | sbdc->init = exynos4210_i2c_realize; | |
322 | } | |
323 | ||
324 | static const TypeInfo exynos4210_i2c_type_info = { | |
325 | .name = TYPE_EXYNOS4_I2C, | |
326 | .parent = TYPE_SYS_BUS_DEVICE, | |
327 | .instance_size = sizeof(Exynos4210I2CState), | |
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) |