1 | /* |
2 | * Exynos4210 UART Emulation |
3 | * |
4 | * Copyright (C) 2011 Samsung Electronics Co Ltd. |
5 | * Maksim Kozlov, <m.kozlov@samsung.com> |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify it |
8 | * under the terms of the GNU General Public License as published by the |
9 | * Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, but WITHOUT |
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
15 | * for more details. |
16 | * |
17 | * You should have received a copy of the GNU General Public License along |
18 | * with this program; if not, see <http://www.gnu.org/licenses/>. |
19 | * |
20 | */ |
21 | |
22 | #include "qemu/osdep.h" |
23 | #include "hw/sysbus.h" |
24 | #include "migration/vmstate.h" |
25 | #include "qemu/error-report.h" |
26 | #include "qemu/module.h" |
27 | #include "chardev/char-fe.h" |
28 | #include "chardev/char-serial.h" |
29 | |
30 | #include "hw/arm/exynos4210.h" |
31 | #include "hw/irq.h" |
32 | #include "hw/qdev-properties.h" |
33 | |
34 | #undef DEBUG_UART |
35 | #undef DEBUG_UART_EXTEND |
36 | #undef DEBUG_IRQ |
37 | #undef DEBUG_Rx_DATA |
38 | #undef DEBUG_Tx_DATA |
39 | |
40 | #define DEBUG_UART 0 |
41 | #define DEBUG_UART_EXTEND 0 |
42 | #define DEBUG_IRQ 0 |
43 | #define DEBUG_Rx_DATA 0 |
44 | #define DEBUG_Tx_DATA 0 |
45 | |
46 | #if DEBUG_UART |
47 | #define PRINT_DEBUG(fmt, args...) \ |
48 | do { \ |
49 | fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ |
50 | } while (0) |
51 | |
52 | #if DEBUG_UART_EXTEND |
53 | #define PRINT_DEBUG_EXTEND(fmt, args...) \ |
54 | do { \ |
55 | fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ |
56 | } while (0) |
57 | #else |
58 | #define PRINT_DEBUG_EXTEND(fmt, args...) \ |
59 | do {} while (0) |
60 | #endif /* EXTEND */ |
61 | |
62 | #else |
63 | #define PRINT_DEBUG(fmt, args...) \ |
64 | do {} while (0) |
65 | #define PRINT_DEBUG_EXTEND(fmt, args...) \ |
66 | do {} while (0) |
67 | #endif |
68 | |
69 | #define PRINT_ERROR(fmt, args...) \ |
70 | do { \ |
71 | fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ |
72 | } while (0) |
73 | |
74 | /* |
75 | * Offsets for UART registers relative to SFR base address |
76 | * for UARTn |
77 | * |
78 | */ |
79 | #define ULCON 0x0000 /* Line Control */ |
80 | #define UCON 0x0004 /* Control */ |
81 | #define UFCON 0x0008 /* FIFO Control */ |
82 | #define UMCON 0x000C /* Modem Control */ |
83 | #define UTRSTAT 0x0010 /* Tx/Rx Status */ |
84 | #define UERSTAT 0x0014 /* UART Error Status */ |
85 | #define UFSTAT 0x0018 /* FIFO Status */ |
86 | #define UMSTAT 0x001C /* Modem Status */ |
87 | #define UTXH 0x0020 /* Transmit Buffer */ |
88 | #define URXH 0x0024 /* Receive Buffer */ |
89 | #define UBRDIV 0x0028 /* Baud Rate Divisor */ |
90 | #define UFRACVAL 0x002C /* Divisor Fractional Value */ |
91 | #define UINTP 0x0030 /* Interrupt Pending */ |
92 | #define UINTSP 0x0034 /* Interrupt Source Pending */ |
93 | #define UINTM 0x0038 /* Interrupt Mask */ |
94 | |
95 | /* |
96 | * for indexing register in the uint32_t array |
97 | * |
98 | * 'reg' - register offset (see offsets definitions above) |
99 | * |
100 | */ |
101 | #define I_(reg) (reg / sizeof(uint32_t)) |
102 | |
103 | typedef struct Exynos4210UartReg { |
104 | const char *name; /* the only reason is the debug output */ |
105 | hwaddr offset; |
106 | uint32_t reset_value; |
107 | } Exynos4210UartReg; |
108 | |
109 | static const Exynos4210UartReg exynos4210_uart_regs[] = { |
110 | {"ULCON" , ULCON, 0x00000000}, |
111 | {"UCON" , UCON, 0x00003000}, |
112 | {"UFCON" , UFCON, 0x00000000}, |
113 | {"UMCON" , UMCON, 0x00000000}, |
114 | {"UTRSTAT" , UTRSTAT, 0x00000006}, /* RO */ |
115 | {"UERSTAT" , UERSTAT, 0x00000000}, /* RO */ |
116 | {"UFSTAT" , UFSTAT, 0x00000000}, /* RO */ |
117 | {"UMSTAT" , UMSTAT, 0x00000000}, /* RO */ |
118 | {"UTXH" , UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/ |
119 | {"URXH" , URXH, 0x00000000}, /* RO */ |
120 | {"UBRDIV" , UBRDIV, 0x00000000}, |
121 | {"UFRACVAL" , UFRACVAL, 0x00000000}, |
122 | {"UINTP" , UINTP, 0x00000000}, |
123 | {"UINTSP" , UINTSP, 0x00000000}, |
124 | {"UINTM" , UINTM, 0x00000000}, |
125 | }; |
126 | |
127 | #define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C |
128 | |
129 | /* UART FIFO Control */ |
130 | #define UFCON_FIFO_ENABLE 0x1 |
131 | #define UFCON_Rx_FIFO_RESET 0x2 |
132 | #define UFCON_Tx_FIFO_RESET 0x4 |
133 | #define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8 |
134 | #define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT) |
135 | #define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4 |
136 | #define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT) |
137 | |
138 | /* Uart FIFO Status */ |
139 | #define UFSTAT_Rx_FIFO_COUNT 0xff |
140 | #define UFSTAT_Rx_FIFO_FULL 0x100 |
141 | #define UFSTAT_Rx_FIFO_ERROR 0x200 |
142 | #define UFSTAT_Tx_FIFO_COUNT_SHIFT 16 |
143 | #define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT) |
144 | #define UFSTAT_Tx_FIFO_FULL_SHIFT 24 |
145 | #define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT) |
146 | |
147 | /* UART Interrupt Source Pending */ |
148 | #define UINTSP_RXD 0x1 /* Receive interrupt */ |
149 | #define UINTSP_ERROR 0x2 /* Error interrupt */ |
150 | #define UINTSP_TXD 0x4 /* Transmit interrupt */ |
151 | #define UINTSP_MODEM 0x8 /* Modem interrupt */ |
152 | |
153 | /* UART Line Control */ |
154 | #define ULCON_IR_MODE_SHIFT 6 |
155 | #define ULCON_PARITY_SHIFT 3 |
156 | #define ULCON_STOP_BIT_SHIFT 1 |
157 | |
158 | /* UART Tx/Rx Status */ |
159 | #define UTRSTAT_TRANSMITTER_EMPTY 0x4 |
160 | #define UTRSTAT_Tx_BUFFER_EMPTY 0x2 |
161 | #define UTRSTAT_Rx_BUFFER_DATA_READY 0x1 |
162 | |
163 | /* UART Error Status */ |
164 | #define UERSTAT_OVERRUN 0x1 |
165 | #define UERSTAT_PARITY 0x2 |
166 | #define UERSTAT_FRAME 0x4 |
167 | #define UERSTAT_BREAK 0x8 |
168 | |
169 | typedef struct { |
170 | uint8_t *data; |
171 | uint32_t sp, rp; /* store and retrieve pointers */ |
172 | uint32_t size; |
173 | } Exynos4210UartFIFO; |
174 | |
175 | #define TYPE_EXYNOS4210_UART "exynos4210.uart" |
176 | #define EXYNOS4210_UART(obj) \ |
177 | OBJECT_CHECK(Exynos4210UartState, (obj), TYPE_EXYNOS4210_UART) |
178 | |
179 | typedef struct Exynos4210UartState { |
180 | SysBusDevice parent_obj; |
181 | |
182 | MemoryRegion iomem; |
183 | |
184 | uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)]; |
185 | Exynos4210UartFIFO rx; |
186 | Exynos4210UartFIFO tx; |
187 | |
188 | CharBackend chr; |
189 | qemu_irq irq; |
190 | |
191 | uint32_t channel; |
192 | |
193 | } Exynos4210UartState; |
194 | |
195 | |
196 | #if DEBUG_UART |
197 | /* Used only for debugging inside PRINT_DEBUG_... macros */ |
198 | static const char *exynos4210_uart_regname(hwaddr offset) |
199 | { |
200 | |
201 | int i; |
202 | |
203 | for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) { |
204 | if (offset == exynos4210_uart_regs[i].offset) { |
205 | return exynos4210_uart_regs[i].name; |
206 | } |
207 | } |
208 | |
209 | return NULL; |
210 | } |
211 | #endif |
212 | |
213 | |
214 | static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch) |
215 | { |
216 | q->data[q->sp] = ch; |
217 | q->sp = (q->sp + 1) % q->size; |
218 | } |
219 | |
220 | static uint8_t fifo_retrieve(Exynos4210UartFIFO *q) |
221 | { |
222 | uint8_t ret = q->data[q->rp]; |
223 | q->rp = (q->rp + 1) % q->size; |
224 | return ret; |
225 | } |
226 | |
227 | static int fifo_elements_number(const Exynos4210UartFIFO *q) |
228 | { |
229 | if (q->sp < q->rp) { |
230 | return q->size - q->rp + q->sp; |
231 | } |
232 | |
233 | return q->sp - q->rp; |
234 | } |
235 | |
236 | static int fifo_empty_elements_number(const Exynos4210UartFIFO *q) |
237 | { |
238 | return q->size - fifo_elements_number(q); |
239 | } |
240 | |
241 | static void fifo_reset(Exynos4210UartFIFO *q) |
242 | { |
243 | g_free(q->data); |
244 | q->data = NULL; |
245 | |
246 | q->data = (uint8_t *)g_malloc0(q->size); |
247 | |
248 | q->sp = 0; |
249 | q->rp = 0; |
250 | } |
251 | |
252 | static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(const Exynos4210UartState *s) |
253 | { |
254 | uint32_t level = 0; |
255 | uint32_t reg; |
256 | |
257 | reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >> |
258 | UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT; |
259 | |
260 | switch (s->channel) { |
261 | case 0: |
262 | level = reg * 32; |
263 | break; |
264 | case 1: |
265 | case 4: |
266 | level = reg * 8; |
267 | break; |
268 | case 2: |
269 | case 3: |
270 | level = reg * 2; |
271 | break; |
272 | default: |
273 | level = 0; |
274 | PRINT_ERROR("Wrong UART channel number: %d\n" , s->channel); |
275 | } |
276 | |
277 | return level; |
278 | } |
279 | |
280 | static void exynos4210_uart_update_irq(Exynos4210UartState *s) |
281 | { |
282 | /* |
283 | * The Tx interrupt is always requested if the number of data in the |
284 | * transmit FIFO is smaller than the trigger level. |
285 | */ |
286 | if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { |
287 | |
288 | uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >> |
289 | UFSTAT_Tx_FIFO_COUNT_SHIFT; |
290 | |
291 | if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) { |
292 | s->reg[I_(UINTSP)] |= UINTSP_TXD; |
293 | } |
294 | } |
295 | |
296 | s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)]; |
297 | |
298 | if (s->reg[I_(UINTP)]) { |
299 | qemu_irq_raise(s->irq); |
300 | |
301 | #if DEBUG_IRQ |
302 | fprintf(stderr, "UART%d: IRQ has been raised: %08x\n" , |
303 | s->channel, s->reg[I_(UINTP)]); |
304 | #endif |
305 | |
306 | } else { |
307 | qemu_irq_lower(s->irq); |
308 | } |
309 | } |
310 | |
311 | static void exynos4210_uart_update_parameters(Exynos4210UartState *s) |
312 | { |
313 | int speed, parity, data_bits, stop_bits; |
314 | QEMUSerialSetParams ssp; |
315 | uint64_t uclk_rate; |
316 | |
317 | if (s->reg[I_(UBRDIV)] == 0) { |
318 | return; |
319 | } |
320 | |
321 | if (s->reg[I_(ULCON)] & 0x20) { |
322 | if (s->reg[I_(ULCON)] & 0x28) { |
323 | parity = 'E'; |
324 | } else { |
325 | parity = 'O'; |
326 | } |
327 | } else { |
328 | parity = 'N'; |
329 | } |
330 | |
331 | if (s->reg[I_(ULCON)] & 0x4) { |
332 | stop_bits = 2; |
333 | } else { |
334 | stop_bits = 1; |
335 | } |
336 | |
337 | data_bits = (s->reg[I_(ULCON)] & 0x3) + 5; |
338 | |
339 | uclk_rate = 24000000; |
340 | |
341 | speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) + |
342 | (s->reg[I_(UFRACVAL)] & 0x7) + 16); |
343 | |
344 | ssp.speed = speed; |
345 | ssp.parity = parity; |
346 | ssp.data_bits = data_bits; |
347 | ssp.stop_bits = stop_bits; |
348 | |
349 | qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); |
350 | |
351 | PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n" , |
352 | s->channel, speed, parity, data_bits, stop_bits); |
353 | } |
354 | |
355 | static void exynos4210_uart_write(void *opaque, hwaddr offset, |
356 | uint64_t val, unsigned size) |
357 | { |
358 | Exynos4210UartState *s = (Exynos4210UartState *)opaque; |
359 | uint8_t ch; |
360 | |
361 | PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n" , s->channel, |
362 | offset, exynos4210_uart_regname(offset), (long long unsigned int)val); |
363 | |
364 | switch (offset) { |
365 | case ULCON: |
366 | case UBRDIV: |
367 | case UFRACVAL: |
368 | s->reg[I_(offset)] = val; |
369 | exynos4210_uart_update_parameters(s); |
370 | break; |
371 | case UFCON: |
372 | s->reg[I_(UFCON)] = val; |
373 | if (val & UFCON_Rx_FIFO_RESET) { |
374 | fifo_reset(&s->rx); |
375 | s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET; |
376 | PRINT_DEBUG("UART%d: Rx FIFO Reset\n" , s->channel); |
377 | } |
378 | if (val & UFCON_Tx_FIFO_RESET) { |
379 | fifo_reset(&s->tx); |
380 | s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET; |
381 | PRINT_DEBUG("UART%d: Tx FIFO Reset\n" , s->channel); |
382 | } |
383 | break; |
384 | |
385 | case UTXH: |
386 | if (qemu_chr_fe_backend_connected(&s->chr)) { |
387 | s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY | |
388 | UTRSTAT_Tx_BUFFER_EMPTY); |
389 | ch = (uint8_t)val; |
390 | /* XXX this blocks entire thread. Rewrite to use |
391 | * qemu_chr_fe_write and background I/O callbacks */ |
392 | qemu_chr_fe_write_all(&s->chr, &ch, 1); |
393 | #if DEBUG_Tx_DATA |
394 | fprintf(stderr, "%c" , ch); |
395 | #endif |
396 | s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY | |
397 | UTRSTAT_Tx_BUFFER_EMPTY; |
398 | s->reg[I_(UINTSP)] |= UINTSP_TXD; |
399 | exynos4210_uart_update_irq(s); |
400 | } |
401 | break; |
402 | |
403 | case UINTP: |
404 | s->reg[I_(UINTP)] &= ~val; |
405 | s->reg[I_(UINTSP)] &= ~val; |
406 | PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n" , |
407 | s->channel, offset, s->reg[I_(UINTP)]); |
408 | exynos4210_uart_update_irq(s); |
409 | break; |
410 | case UTRSTAT: |
411 | case UERSTAT: |
412 | case UFSTAT: |
413 | case UMSTAT: |
414 | case URXH: |
415 | PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n" , |
416 | s->channel, exynos4210_uart_regname(offset), offset); |
417 | break; |
418 | case UINTSP: |
419 | s->reg[I_(UINTSP)] &= ~val; |
420 | break; |
421 | case UINTM: |
422 | s->reg[I_(UINTM)] = val; |
423 | exynos4210_uart_update_irq(s); |
424 | break; |
425 | case UCON: |
426 | case UMCON: |
427 | default: |
428 | s->reg[I_(offset)] = val; |
429 | break; |
430 | } |
431 | } |
432 | static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset, |
433 | unsigned size) |
434 | { |
435 | Exynos4210UartState *s = (Exynos4210UartState *)opaque; |
436 | uint32_t res; |
437 | |
438 | switch (offset) { |
439 | case UERSTAT: /* Read Only */ |
440 | res = s->reg[I_(UERSTAT)]; |
441 | s->reg[I_(UERSTAT)] = 0; |
442 | return res; |
443 | case UFSTAT: /* Read Only */ |
444 | s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff; |
445 | if (fifo_empty_elements_number(&s->rx) == 0) { |
446 | s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL; |
447 | s->reg[I_(UFSTAT)] &= ~0xff; |
448 | } |
449 | return s->reg[I_(UFSTAT)]; |
450 | case URXH: |
451 | if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { |
452 | if (fifo_elements_number(&s->rx)) { |
453 | res = fifo_retrieve(&s->rx); |
454 | #if DEBUG_Rx_DATA |
455 | fprintf(stderr, "%c" , res); |
456 | #endif |
457 | if (!fifo_elements_number(&s->rx)) { |
458 | s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; |
459 | } else { |
460 | s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; |
461 | } |
462 | } else { |
463 | s->reg[I_(UINTSP)] |= UINTSP_ERROR; |
464 | exynos4210_uart_update_irq(s); |
465 | res = 0; |
466 | } |
467 | } else { |
468 | s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; |
469 | res = s->reg[I_(URXH)]; |
470 | } |
471 | return res; |
472 | case UTXH: |
473 | PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n" , |
474 | s->channel, exynos4210_uart_regname(offset), offset); |
475 | break; |
476 | default: |
477 | return s->reg[I_(offset)]; |
478 | } |
479 | |
480 | return 0; |
481 | } |
482 | |
483 | static const MemoryRegionOps exynos4210_uart_ops = { |
484 | .read = exynos4210_uart_read, |
485 | .write = exynos4210_uart_write, |
486 | .endianness = DEVICE_NATIVE_ENDIAN, |
487 | .valid = { |
488 | .max_access_size = 4, |
489 | .unaligned = false |
490 | }, |
491 | }; |
492 | |
493 | static int exynos4210_uart_can_receive(void *opaque) |
494 | { |
495 | Exynos4210UartState *s = (Exynos4210UartState *)opaque; |
496 | |
497 | return fifo_empty_elements_number(&s->rx); |
498 | } |
499 | |
500 | |
501 | static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size) |
502 | { |
503 | Exynos4210UartState *s = (Exynos4210UartState *)opaque; |
504 | int i; |
505 | |
506 | if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { |
507 | if (fifo_empty_elements_number(&s->rx) < size) { |
508 | for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) { |
509 | fifo_store(&s->rx, buf[i]); |
510 | } |
511 | s->reg[I_(UINTSP)] |= UINTSP_ERROR; |
512 | s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; |
513 | } else { |
514 | for (i = 0; i < size; i++) { |
515 | fifo_store(&s->rx, buf[i]); |
516 | } |
517 | s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; |
518 | } |
519 | /* XXX: Around here we maybe should check Rx trigger level */ |
520 | s->reg[I_(UINTSP)] |= UINTSP_RXD; |
521 | } else { |
522 | s->reg[I_(URXH)] = buf[0]; |
523 | s->reg[I_(UINTSP)] |= UINTSP_RXD; |
524 | s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; |
525 | } |
526 | |
527 | exynos4210_uart_update_irq(s); |
528 | } |
529 | |
530 | |
531 | static void exynos4210_uart_event(void *opaque, int event) |
532 | { |
533 | Exynos4210UartState *s = (Exynos4210UartState *)opaque; |
534 | |
535 | if (event == CHR_EVENT_BREAK) { |
536 | /* When the RxDn is held in logic 0, then a null byte is pushed into the |
537 | * fifo */ |
538 | fifo_store(&s->rx, '\0'); |
539 | s->reg[I_(UERSTAT)] |= UERSTAT_BREAK; |
540 | exynos4210_uart_update_irq(s); |
541 | } |
542 | } |
543 | |
544 | |
545 | static void exynos4210_uart_reset(DeviceState *dev) |
546 | { |
547 | Exynos4210UartState *s = EXYNOS4210_UART(dev); |
548 | int i; |
549 | |
550 | for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) { |
551 | s->reg[I_(exynos4210_uart_regs[i].offset)] = |
552 | exynos4210_uart_regs[i].reset_value; |
553 | } |
554 | |
555 | fifo_reset(&s->rx); |
556 | fifo_reset(&s->tx); |
557 | |
558 | PRINT_DEBUG("UART%d: Rx FIFO size: %d\n" , s->channel, s->rx.size); |
559 | } |
560 | |
561 | static const VMStateDescription vmstate_exynos4210_uart_fifo = { |
562 | .name = "exynos4210.uart.fifo" , |
563 | .version_id = 1, |
564 | .minimum_version_id = 1, |
565 | .fields = (VMStateField[]) { |
566 | VMSTATE_UINT32(sp, Exynos4210UartFIFO), |
567 | VMSTATE_UINT32(rp, Exynos4210UartFIFO), |
568 | VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, size), |
569 | VMSTATE_END_OF_LIST() |
570 | } |
571 | }; |
572 | |
573 | static const VMStateDescription vmstate_exynos4210_uart = { |
574 | .name = "exynos4210.uart" , |
575 | .version_id = 1, |
576 | .minimum_version_id = 1, |
577 | .fields = (VMStateField[]) { |
578 | VMSTATE_STRUCT(rx, Exynos4210UartState, 1, |
579 | vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO), |
580 | VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState, |
581 | EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)), |
582 | VMSTATE_END_OF_LIST() |
583 | } |
584 | }; |
585 | |
586 | DeviceState *exynos4210_uart_create(hwaddr addr, |
587 | int fifo_size, |
588 | int channel, |
589 | Chardev *chr, |
590 | qemu_irq irq) |
591 | { |
592 | DeviceState *dev; |
593 | SysBusDevice *bus; |
594 | |
595 | dev = qdev_create(NULL, TYPE_EXYNOS4210_UART); |
596 | |
597 | qdev_prop_set_chr(dev, "chardev" , chr); |
598 | qdev_prop_set_uint32(dev, "channel" , channel); |
599 | qdev_prop_set_uint32(dev, "rx-size" , fifo_size); |
600 | qdev_prop_set_uint32(dev, "tx-size" , fifo_size); |
601 | |
602 | bus = SYS_BUS_DEVICE(dev); |
603 | qdev_init_nofail(dev); |
604 | if (addr != (hwaddr)-1) { |
605 | sysbus_mmio_map(bus, 0, addr); |
606 | } |
607 | sysbus_connect_irq(bus, 0, irq); |
608 | |
609 | return dev; |
610 | } |
611 | |
612 | static void exynos4210_uart_init(Object *obj) |
613 | { |
614 | SysBusDevice *dev = SYS_BUS_DEVICE(obj); |
615 | Exynos4210UartState *s = EXYNOS4210_UART(dev); |
616 | |
617 | /* memory mapping */ |
618 | memory_region_init_io(&s->iomem, obj, &exynos4210_uart_ops, s, |
619 | "exynos4210.uart" , EXYNOS4210_UART_REGS_MEM_SIZE); |
620 | sysbus_init_mmio(dev, &s->iomem); |
621 | |
622 | sysbus_init_irq(dev, &s->irq); |
623 | } |
624 | |
625 | static void exynos4210_uart_realize(DeviceState *dev, Error **errp) |
626 | { |
627 | Exynos4210UartState *s = EXYNOS4210_UART(dev); |
628 | |
629 | qemu_chr_fe_set_handlers(&s->chr, exynos4210_uart_can_receive, |
630 | exynos4210_uart_receive, exynos4210_uart_event, |
631 | NULL, s, NULL, true); |
632 | } |
633 | |
634 | static Property exynos4210_uart_properties[] = { |
635 | DEFINE_PROP_CHR("chardev" , Exynos4210UartState, chr), |
636 | DEFINE_PROP_UINT32("channel" , Exynos4210UartState, channel, 0), |
637 | DEFINE_PROP_UINT32("rx-size" , Exynos4210UartState, rx.size, 16), |
638 | DEFINE_PROP_UINT32("tx-size" , Exynos4210UartState, tx.size, 16), |
639 | DEFINE_PROP_END_OF_LIST(), |
640 | }; |
641 | |
642 | static void exynos4210_uart_class_init(ObjectClass *klass, void *data) |
643 | { |
644 | DeviceClass *dc = DEVICE_CLASS(klass); |
645 | |
646 | dc->realize = exynos4210_uart_realize; |
647 | dc->reset = exynos4210_uart_reset; |
648 | dc->props = exynos4210_uart_properties; |
649 | dc->vmsd = &vmstate_exynos4210_uart; |
650 | } |
651 | |
652 | static const TypeInfo exynos4210_uart_info = { |
653 | .name = TYPE_EXYNOS4210_UART, |
654 | .parent = TYPE_SYS_BUS_DEVICE, |
655 | .instance_size = sizeof(Exynos4210UartState), |
656 | .instance_init = exynos4210_uart_init, |
657 | .class_init = exynos4210_uart_class_init, |
658 | }; |
659 | |
660 | static void exynos4210_uart_register(void) |
661 | { |
662 | type_register_static(&exynos4210_uart_info); |
663 | } |
664 | |
665 | type_init(exynos4210_uart_register) |
666 | |