root / hw / exynos4210_i2c.c @ 7830cf78
History | View | Annotate | Download (9.9 kB)
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 |
|
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, hwaddr 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, hwaddr 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) |