]>
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 | ||
23 | #include "qemu-timer.h" | |
24 | #include "sysbus.h" | |
25 | #include "i2c.h" | |
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 { | |
83 | SysBusDevice busdev; | |
84 | MemoryRegion iomem; | |
85 | i2c_bus *bus; | |
86 | qemu_irq irq; | |
87 | ||
88 | uint8_t i2ccon; | |
89 | uint8_t i2cstat; | |
90 | uint8_t i2cadd; | |
91 | uint8_t i2cds; | |
92 | uint8_t i2clc; | |
93 | bool scl_free; | |
94 | } Exynos4210I2CState; | |
95 | ||
96 | static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s) | |
97 | { | |
98 | if (s->i2ccon & I2CCON_INTRS_EN) { | |
99 | s->i2ccon |= I2CCON_INT_PEND; | |
100 | qemu_irq_raise(s->irq); | |
101 | } | |
102 | } | |
103 | ||
104 | static void exynos4210_i2c_data_receive(void *opaque) | |
105 | { | |
106 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
107 | int ret; | |
108 | ||
109 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
110 | s->scl_free = false; | |
111 | ret = i2c_recv(s->bus); | |
112 | if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
113 | s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */ | |
114 | } else { | |
115 | s->i2cds = ret; | |
116 | } | |
117 | exynos4210_i2c_raise_interrupt(s); | |
118 | } | |
119 | ||
120 | static void exynos4210_i2c_data_send(void *opaque) | |
121 | { | |
122 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
123 | ||
124 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
125 | s->scl_free = false; | |
126 | if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { | |
127 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
128 | } | |
129 | exynos4210_i2c_raise_interrupt(s); | |
130 | } | |
131 | ||
132 | static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset, | |
133 | unsigned size) | |
134 | { | |
135 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
136 | uint8_t value; | |
137 | ||
138 | switch (offset) { | |
139 | case I2CCON_ADDR: | |
140 | value = s->i2ccon; | |
141 | break; | |
142 | case I2CSTAT_ADDR: | |
143 | value = s->i2cstat; | |
144 | break; | |
145 | case I2CADD_ADDR: | |
146 | value = s->i2cadd; | |
147 | break; | |
148 | case I2CDS_ADDR: | |
149 | value = s->i2cds; | |
150 | s->scl_free = true; | |
151 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx && | |
152 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
153 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
154 | exynos4210_i2c_data_receive(s); | |
155 | } | |
156 | break; | |
157 | case I2CLC_ADDR: | |
158 | value = s->i2clc; | |
159 | break; | |
160 | default: | |
161 | value = 0; | |
162 | DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset); | |
163 | break; | |
164 | } | |
165 | ||
166 | DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset), | |
167 | (unsigned int)offset, value); | |
168 | return value; | |
169 | } | |
170 | ||
171 | static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset, | |
172 | uint64_t value, unsigned size) | |
173 | { | |
174 | Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; | |
175 | uint8_t v = value & 0xff; | |
176 | ||
177 | DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset), | |
178 | (unsigned int)offset, v); | |
179 | ||
180 | switch (offset) { | |
181 | case I2CCON_ADDR: | |
182 | s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND); | |
183 | if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) { | |
184 | s->i2ccon &= ~I2CCON_INT_PEND; | |
185 | qemu_irq_lower(s->irq); | |
186 | if (!(s->i2ccon & I2CCON_INTRS_EN)) { | |
187 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
188 | } | |
189 | ||
190 | if (s->i2cstat & I2CSTAT_START_BUSY) { | |
191 | if (s->scl_free) { | |
192 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) { | |
193 | exynos4210_i2c_data_send(s); | |
194 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == | |
195 | I2CMODE_MASTER_Rx) { | |
196 | exynos4210_i2c_data_receive(s); | |
197 | } | |
198 | } else { | |
199 | s->i2ccon |= I2CCON_INT_PEND; | |
200 | qemu_irq_raise(s->irq); | |
201 | } | |
202 | } | |
203 | } | |
204 | break; | |
205 | case I2CSTAT_ADDR: | |
206 | s->i2cstat = | |
207 | (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY); | |
208 | ||
209 | if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) { | |
210 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
211 | s->scl_free = true; | |
212 | qemu_irq_lower(s->irq); | |
213 | break; | |
214 | } | |
215 | ||
216 | /* Nothing to do if in i2c slave mode */ | |
217 | if (!I2C_IN_MASTER_MODE(s->i2cstat)) { | |
218 | break; | |
219 | } | |
220 | ||
221 | if (v & I2CSTAT_START_BUSY) { | |
222 | s->i2cstat &= ~I2CSTAT_LAST_BIT; | |
223 | s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */ | |
224 | s->scl_free = false; | |
225 | ||
226 | /* Generate start bit and send slave address */ | |
227 | if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) && | |
228 | (s->i2ccon & I2CCON_ACK_GEN)) { | |
229 | s->i2cstat |= I2CSTAT_LAST_BIT; | |
230 | } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) { | |
231 | exynos4210_i2c_data_receive(s); | |
232 | } | |
233 | exynos4210_i2c_raise_interrupt(s); | |
234 | } else { | |
235 | i2c_end_transfer(s->bus); | |
236 | if (!(s->i2ccon & I2CCON_INT_PEND)) { | |
237 | s->i2cstat &= ~I2CSTAT_START_BUSY; | |
238 | } | |
239 | s->scl_free = true; | |
240 | } | |
241 | break; | |
242 | case I2CADD_ADDR: | |
243 | if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) { | |
244 | s->i2cadd = v; | |
245 | } | |
246 | break; | |
247 | case I2CDS_ADDR: | |
248 | if (s->i2cstat & I2CSTAT_OUTPUT_EN) { | |
249 | s->i2cds = v; | |
250 | s->scl_free = true; | |
251 | if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx && | |
252 | (s->i2cstat & I2CSTAT_START_BUSY) && | |
253 | !(s->i2ccon & I2CCON_INT_PEND)) { | |
254 | exynos4210_i2c_data_send(s); | |
255 | } | |
256 | } | |
257 | break; | |
258 | case I2CLC_ADDR: | |
259 | s->i2clc = v; | |
260 | break; | |
261 | default: | |
262 | DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset); | |
263 | break; | |
264 | } | |
265 | } | |
266 | ||
267 | static const MemoryRegionOps exynos4210_i2c_ops = { | |
268 | .read = exynos4210_i2c_read, | |
269 | .write = exynos4210_i2c_write, | |
270 | .endianness = DEVICE_NATIVE_ENDIAN, | |
271 | }; | |
272 | ||
273 | static const VMStateDescription exynos4210_i2c_vmstate = { | |
274 | .name = TYPE_EXYNOS4_I2C, | |
275 | .version_id = 1, | |
276 | .minimum_version_id = 1, | |
277 | .fields = (VMStateField[]) { | |
278 | VMSTATE_UINT8(i2ccon, Exynos4210I2CState), | |
279 | VMSTATE_UINT8(i2cstat, Exynos4210I2CState), | |
280 | VMSTATE_UINT8(i2cds, Exynos4210I2CState), | |
281 | VMSTATE_UINT8(i2cadd, Exynos4210I2CState), | |
282 | VMSTATE_UINT8(i2clc, Exynos4210I2CState), | |
283 | VMSTATE_BOOL(scl_free, Exynos4210I2CState), | |
284 | VMSTATE_END_OF_LIST() | |
285 | } | |
286 | }; | |
287 | ||
288 | static void exynos4210_i2c_reset(DeviceState *d) | |
289 | { | |
290 | Exynos4210I2CState *s = EXYNOS4_I2C(d); | |
291 | ||
292 | s->i2ccon = 0x00; | |
293 | s->i2cstat = 0x00; | |
294 | s->i2cds = 0xFF; | |
295 | s->i2clc = 0x00; | |
296 | s->i2cadd = 0xFF; | |
297 | s->scl_free = true; | |
298 | } | |
299 | ||
300 | static int exynos4210_i2c_realize(SysBusDevice *dev) | |
301 | { | |
302 | Exynos4210I2CState *s = EXYNOS4_I2C(dev); | |
303 | ||
304 | memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C, | |
305 | EXYNOS4_I2C_MEM_SIZE); | |
306 | sysbus_init_mmio(dev, &s->iomem); | |
307 | sysbus_init_irq(dev, &s->irq); | |
308 | s->bus = i2c_init_bus(&dev->qdev, "i2c"); | |
309 | return 0; | |
310 | } | |
311 | ||
312 | static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) | |
313 | { | |
314 | DeviceClass *dc = DEVICE_CLASS(klass); | |
315 | SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); | |
316 | ||
317 | dc->vmsd = &exynos4210_i2c_vmstate; | |
318 | dc->reset = exynos4210_i2c_reset; | |
319 | sbdc->init = exynos4210_i2c_realize; | |
320 | } | |
321 | ||
322 | static const TypeInfo exynos4210_i2c_type_info = { | |
323 | .name = TYPE_EXYNOS4_I2C, | |
324 | .parent = TYPE_SYS_BUS_DEVICE, | |
325 | .instance_size = sizeof(Exynos4210I2CState), | |
326 | .class_init = exynos4210_i2c_class_init, | |
327 | }; | |
328 | ||
329 | static void exynos4210_i2c_register_types(void) | |
330 | { | |
331 | type_register_static(&exynos4210_i2c_type_info); | |
332 | } | |
333 | ||
334 | type_init(exynos4210_i2c_register_types) |