]>
Commit | Line | Data |
---|---|---|
2f062c72 TS |
1 | /* |
2 | * QEMU SCI/SCIF serial port emulation | |
3 | * | |
4 | * Copyright (c) 2007 Magnus Damm | |
5 | * | |
6 | * Based on serial.c - QEMU 16450 UART emulation | |
7 | * Copyright (c) 2003-2004 Fabrice Bellard | |
8 | * | |
9 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
10 | * of this software and associated documentation files (the "Software"), to deal | |
11 | * in the Software without restriction, including without limitation the rights | |
12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
13 | * copies of the Software, and to permit persons to whom the Software is | |
14 | * furnished to do so, subject to the following conditions: | |
15 | * | |
16 | * The above copyright notice and this permission notice shall be included in | |
17 | * all copies or substantial portions of the Software. | |
18 | * | |
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
22 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
25 | * THE SOFTWARE. | |
26 | */ | |
64552b6b | 27 | |
0430891c | 28 | #include "qemu/osdep.h" |
beeb5209 | 29 | #include "hw/sysbus.h" |
64552b6b | 30 | #include "hw/irq.h" |
beeb5209 BZ |
31 | #include "hw/qdev-core.h" |
32 | #include "hw/qdev-properties.h" | |
33 | #include "hw/qdev-properties-system.h" | |
0d09e41a | 34 | #include "hw/sh4/sh.h" |
4d43a603 | 35 | #include "chardev/char-fe.h" |
32a6ebec | 36 | #include "qapi/error.h" |
71bb4ce1 | 37 | #include "qemu/timer.h" |
3cf7ce43 | 38 | #include "qemu/log.h" |
ad52cfc1 | 39 | #include "trace.h" |
2f062c72 TS |
40 | |
41 | #define SH_SERIAL_FLAG_TEND (1 << 0) | |
42 | #define SH_SERIAL_FLAG_TDE (1 << 1) | |
43 | #define SH_SERIAL_FLAG_RDF (1 << 2) | |
44 | #define SH_SERIAL_FLAG_BRK (1 << 3) | |
45 | #define SH_SERIAL_FLAG_DR (1 << 4) | |
46 | ||
63242a00 AJ |
47 | #define SH_RX_FIFO_LENGTH (16) |
48 | ||
beeb5209 BZ |
49 | OBJECT_DECLARE_SIMPLE_TYPE(SHSerialState, SH_SERIAL) |
50 | ||
51 | struct SHSerialState { | |
52 | SysBusDevice parent; | |
2f062c72 TS |
53 | uint8_t smr; |
54 | uint8_t brr; | |
55 | uint8_t scr; | |
56 | uint8_t dr; /* ftdr / tdr */ | |
57 | uint8_t sr; /* fsr / ssr */ | |
58 | uint16_t fcr; | |
59 | uint8_t sptr; | |
60 | ||
63242a00 | 61 | uint8_t rx_fifo[SH_RX_FIFO_LENGTH]; /* frdr / rdr */ |
2f062c72 | 62 | uint8_t rx_cnt; |
63242a00 AJ |
63 | uint8_t rx_tail; |
64 | uint8_t rx_head; | |
2f062c72 | 65 | |
beeb5209 | 66 | uint8_t feat; |
2f062c72 | 67 | int flags; |
63242a00 | 68 | int rtrg; |
2f062c72 | 69 | |
32a6ebec | 70 | CharBackend chr; |
5b344b02 | 71 | QEMUTimer fifo_timeout_timer; |
71bb4ce1 | 72 | uint64_t etu; /* Elementary Time Unit (ns) */ |
bf5b7423 | 73 | |
4e7ed2d1 AJ |
74 | qemu_irq eri; |
75 | qemu_irq rxi; | |
76 | qemu_irq txi; | |
77 | qemu_irq tei; | |
78 | qemu_irq bri; | |
beeb5209 BZ |
79 | }; |
80 | ||
81 | typedef struct {} SHSerialStateClass; | |
82 | ||
83 | OBJECT_DEFINE_TYPE(SHSerialState, sh_serial, SH_SERIAL, SYS_BUS_DEVICE) | |
2f062c72 | 84 | |
2f6df137 | 85 | static void sh_serial_clear_fifo(SHSerialState *s) |
63242a00 AJ |
86 | { |
87 | memset(s->rx_fifo, 0, SH_RX_FIFO_LENGTH); | |
88 | s->rx_cnt = 0; | |
89 | s->rx_head = 0; | |
90 | s->rx_tail = 0; | |
91 | } | |
92 | ||
a8170e5e | 93 | static void sh_serial_write(void *opaque, hwaddr offs, |
9a9d0b81 | 94 | uint64_t val, unsigned size) |
2f062c72 | 95 | { |
2f6df137 | 96 | SHSerialState *s = opaque; |
44ae04f0 | 97 | DeviceState *d = DEVICE(s); |
2f062c72 TS |
98 | unsigned char ch; |
99 | ||
44ae04f0 | 100 | trace_sh_serial_write(d->id, size, offs, val); |
f94bff13 | 101 | switch (offs) { |
2f062c72 TS |
102 | case 0x00: /* SMR */ |
103 | s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff); | |
104 | return; | |
105 | case 0x04: /* BRR */ | |
106 | s->brr = val; | |
7d37435b | 107 | return; |
2f062c72 | 108 | case 0x08: /* SCR */ |
63242a00 | 109 | /* TODO : For SH7751, SCIF mask should be 0xfb. */ |
bf5b7423 | 110 | s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff); |
ac3c9e74 | 111 | if (!(val & (1 << 5))) { |
2f062c72 | 112 | s->flags |= SH_SERIAL_FLAG_TEND; |
ac3c9e74 | 113 | } |
bf5b7423 | 114 | if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) { |
7d37435b | 115 | qemu_set_irq(s->txi, val & (1 << 7)); |
bf5b7423 | 116 | } |
4e7ed2d1 | 117 | if (!(val & (1 << 6))) { |
7d37435b | 118 | qemu_set_irq(s->rxi, 0); |
63242a00 | 119 | } |
2f062c72 TS |
120 | return; |
121 | case 0x0c: /* FTDR / TDR */ | |
30650701 | 122 | if (qemu_chr_fe_backend_connected(&s->chr)) { |
2f062c72 | 123 | ch = val; |
22138965 BZ |
124 | /* |
125 | * XXX this blocks entire thread. Rewrite to use | |
126 | * qemu_chr_fe_write and background I/O callbacks | |
127 | */ | |
5345fdb4 | 128 | qemu_chr_fe_write_all(&s->chr, &ch, 1); |
7d37435b PB |
129 | } |
130 | s->dr = val; | |
131 | s->flags &= ~SH_SERIAL_FLAG_TDE; | |
2f062c72 TS |
132 | return; |
133 | #if 0 | |
134 | case 0x14: /* FRDR / RDR */ | |
135 | ret = 0; | |
136 | break; | |
137 | #endif | |
138 | } | |
139 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
f94bff13 | 140 | switch (offs) { |
2f062c72 | 141 | case 0x10: /* FSR */ |
ac3c9e74 | 142 | if (!(val & (1 << 6))) { |
2f062c72 | 143 | s->flags &= ~SH_SERIAL_FLAG_TEND; |
ac3c9e74 BZ |
144 | } |
145 | if (!(val & (1 << 5))) { | |
2f062c72 | 146 | s->flags &= ~SH_SERIAL_FLAG_TDE; |
ac3c9e74 BZ |
147 | } |
148 | if (!(val & (1 << 4))) { | |
2f062c72 | 149 | s->flags &= ~SH_SERIAL_FLAG_BRK; |
ac3c9e74 BZ |
150 | } |
151 | if (!(val & (1 << 1))) { | |
2f062c72 | 152 | s->flags &= ~SH_SERIAL_FLAG_RDF; |
ac3c9e74 BZ |
153 | } |
154 | if (!(val & (1 << 0))) { | |
2f062c72 | 155 | s->flags &= ~SH_SERIAL_FLAG_DR; |
ac3c9e74 | 156 | } |
63242a00 AJ |
157 | |
158 | if (!(val & (1 << 1)) || !(val & (1 << 0))) { | |
4e7ed2d1 AJ |
159 | if (s->rxi) { |
160 | qemu_set_irq(s->rxi, 0); | |
63242a00 AJ |
161 | } |
162 | } | |
2f062c72 TS |
163 | return; |
164 | case 0x18: /* FCR */ | |
165 | s->fcr = val; | |
63242a00 AJ |
166 | switch ((val >> 6) & 3) { |
167 | case 0: | |
168 | s->rtrg = 1; | |
169 | break; | |
170 | case 1: | |
171 | s->rtrg = 4; | |
172 | break; | |
173 | case 2: | |
174 | s->rtrg = 8; | |
175 | break; | |
176 | case 3: | |
177 | s->rtrg = 14; | |
178 | break; | |
179 | } | |
180 | if (val & (1 << 1)) { | |
181 | sh_serial_clear_fifo(s); | |
182 | s->sr &= ~(1 << 1); | |
183 | } | |
184 | ||
2f062c72 TS |
185 | return; |
186 | case 0x20: /* SPTR */ | |
63242a00 | 187 | s->sptr = val & 0xf3; |
2f062c72 TS |
188 | return; |
189 | case 0x24: /* LSR */ | |
190 | return; | |
191 | } | |
f94bff13 BZ |
192 | } else { |
193 | switch (offs) { | |
d1f193b0 | 194 | #if 0 |
2f062c72 TS |
195 | case 0x0c: |
196 | ret = s->dr; | |
197 | break; | |
198 | case 0x10: | |
199 | ret = 0; | |
200 | break; | |
d1f193b0 | 201 | #endif |
2f062c72 | 202 | case 0x1c: |
d1f193b0 AJ |
203 | s->sptr = val & 0x8f; |
204 | return; | |
2f062c72 | 205 | } |
2f062c72 | 206 | } |
3cf7ce43 BZ |
207 | qemu_log_mask(LOG_GUEST_ERROR, |
208 | "%s: unsupported write to 0x%02" HWADDR_PRIx "\n", | |
209 | __func__, offs); | |
2f062c72 TS |
210 | } |
211 | ||
a8170e5e | 212 | static uint64_t sh_serial_read(void *opaque, hwaddr offs, |
9a9d0b81 | 213 | unsigned size) |
2f062c72 | 214 | { |
2f6df137 | 215 | SHSerialState *s = opaque; |
44ae04f0 | 216 | DeviceState *d = DEVICE(s); |
3cf7ce43 | 217 | uint32_t ret = UINT32_MAX; |
2f062c72 TS |
218 | |
219 | #if 0 | |
f94bff13 | 220 | switch (offs) { |
2f062c72 TS |
221 | case 0x00: |
222 | ret = s->smr; | |
223 | break; | |
224 | case 0x04: | |
225 | ret = s->brr; | |
7d37435b | 226 | break; |
2f062c72 TS |
227 | case 0x08: |
228 | ret = s->scr; | |
229 | break; | |
230 | case 0x14: | |
231 | ret = 0; | |
232 | break; | |
233 | } | |
234 | #endif | |
235 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
f94bff13 | 236 | switch (offs) { |
bf5b7423 AJ |
237 | case 0x00: /* SMR */ |
238 | ret = s->smr; | |
239 | break; | |
240 | case 0x08: /* SCR */ | |
241 | ret = s->scr; | |
242 | break; | |
2f062c72 TS |
243 | case 0x10: /* FSR */ |
244 | ret = 0; | |
ac3c9e74 | 245 | if (s->flags & SH_SERIAL_FLAG_TEND) { |
2f062c72 | 246 | ret |= (1 << 6); |
ac3c9e74 BZ |
247 | } |
248 | if (s->flags & SH_SERIAL_FLAG_TDE) { | |
2f062c72 | 249 | ret |= (1 << 5); |
ac3c9e74 BZ |
250 | } |
251 | if (s->flags & SH_SERIAL_FLAG_BRK) { | |
2f062c72 | 252 | ret |= (1 << 4); |
ac3c9e74 BZ |
253 | } |
254 | if (s->flags & SH_SERIAL_FLAG_RDF) { | |
2f062c72 | 255 | ret |= (1 << 1); |
ac3c9e74 BZ |
256 | } |
257 | if (s->flags & SH_SERIAL_FLAG_DR) { | |
2f062c72 | 258 | ret |= (1 << 0); |
ac3c9e74 | 259 | } |
2f062c72 | 260 | |
ac3c9e74 | 261 | if (s->scr & (1 << 5)) { |
2f062c72 | 262 | s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND; |
ac3c9e74 | 263 | } |
2f062c72 | 264 | |
63242a00 AJ |
265 | break; |
266 | case 0x14: | |
267 | if (s->rx_cnt > 0) { | |
268 | ret = s->rx_fifo[s->rx_tail++]; | |
269 | s->rx_cnt--; | |
ac3c9e74 | 270 | if (s->rx_tail == SH_RX_FIFO_LENGTH) { |
63242a00 | 271 | s->rx_tail = 0; |
ac3c9e74 BZ |
272 | } |
273 | if (s->rx_cnt < s->rtrg) { | |
63242a00 | 274 | s->flags &= ~SH_SERIAL_FLAG_RDF; |
ac3c9e74 | 275 | } |
63242a00 | 276 | } |
2f062c72 | 277 | break; |
2f062c72 TS |
278 | case 0x18: |
279 | ret = s->fcr; | |
280 | break; | |
2f062c72 TS |
281 | case 0x1c: |
282 | ret = s->rx_cnt; | |
283 | break; | |
284 | case 0x20: | |
285 | ret = s->sptr; | |
286 | break; | |
287 | case 0x24: | |
288 | ret = 0; | |
289 | break; | |
290 | } | |
f94bff13 BZ |
291 | } else { |
292 | switch (offs) { | |
d1f193b0 | 293 | #if 0 |
2f062c72 TS |
294 | case 0x0c: |
295 | ret = s->dr; | |
296 | break; | |
297 | case 0x10: | |
298 | ret = 0; | |
299 | break; | |
63242a00 AJ |
300 | case 0x14: |
301 | ret = s->rx_fifo[0]; | |
302 | break; | |
d1f193b0 | 303 | #endif |
2f062c72 TS |
304 | case 0x1c: |
305 | ret = s->sptr; | |
306 | break; | |
307 | } | |
2f062c72 | 308 | } |
44ae04f0 | 309 | trace_sh_serial_read(d->id, size, offs, ret); |
2f062c72 | 310 | |
3cf7ce43 BZ |
311 | if (ret > UINT16_MAX) { |
312 | qemu_log_mask(LOG_GUEST_ERROR, | |
313 | "%s: unsupported read from 0x%02" HWADDR_PRIx "\n", | |
314 | __func__, offs); | |
315 | ret = 0; | |
2f062c72 TS |
316 | } |
317 | ||
318 | return ret; | |
319 | } | |
320 | ||
2f6df137 | 321 | static int sh_serial_can_receive(SHSerialState *s) |
2f062c72 | 322 | { |
63242a00 | 323 | return s->scr & (1 << 4); |
2f062c72 TS |
324 | } |
325 | ||
2f6df137 | 326 | static void sh_serial_receive_break(SHSerialState *s) |
2f062c72 | 327 | { |
ac3c9e74 | 328 | if (s->feat & SH_SERIAL_FEAT_SCIF) { |
63242a00 | 329 | s->sr |= (1 << 4); |
ac3c9e74 | 330 | } |
2f062c72 TS |
331 | } |
332 | ||
333 | static int sh_serial_can_receive1(void *opaque) | |
334 | { | |
2f6df137 | 335 | SHSerialState *s = opaque; |
2f062c72 TS |
336 | return sh_serial_can_receive(s); |
337 | } | |
338 | ||
71bb4ce1 GU |
339 | static void sh_serial_timeout_int(void *opaque) |
340 | { | |
2f6df137 | 341 | SHSerialState *s = opaque; |
71bb4ce1 GU |
342 | |
343 | s->flags |= SH_SERIAL_FLAG_RDF; | |
344 | if (s->scr & (1 << 6) && s->rxi) { | |
345 | qemu_set_irq(s->rxi, 1); | |
346 | } | |
347 | } | |
348 | ||
2f062c72 TS |
349 | static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size) |
350 | { | |
2f6df137 | 351 | SHSerialState *s = opaque; |
b7d2b020 AJ |
352 | |
353 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
354 | int i; | |
355 | for (i = 0; i < size; i++) { | |
356 | if (s->rx_cnt < SH_RX_FIFO_LENGTH) { | |
357 | s->rx_fifo[s->rx_head++] = buf[i]; | |
358 | if (s->rx_head == SH_RX_FIFO_LENGTH) { | |
359 | s->rx_head = 0; | |
360 | } | |
361 | s->rx_cnt++; | |
362 | if (s->rx_cnt >= s->rtrg) { | |
363 | s->flags |= SH_SERIAL_FLAG_RDF; | |
364 | if (s->scr & (1 << 6) && s->rxi) { | |
5b344b02 | 365 | timer_del(&s->fifo_timeout_timer); |
b7d2b020 AJ |
366 | qemu_set_irq(s->rxi, 1); |
367 | } | |
71bb4ce1 | 368 | } else { |
5b344b02 | 369 | timer_mod(&s->fifo_timeout_timer, |
71bb4ce1 | 370 | qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); |
b7d2b020 AJ |
371 | } |
372 | } | |
373 | } | |
374 | } else { | |
375 | s->rx_fifo[0] = buf[0]; | |
376 | } | |
2f062c72 TS |
377 | } |
378 | ||
083b266f | 379 | static void sh_serial_event(void *opaque, QEMUChrEvent event) |
2f062c72 | 380 | { |
2f6df137 | 381 | SHSerialState *s = opaque; |
ac3c9e74 | 382 | if (event == CHR_EVENT_BREAK) { |
2f062c72 | 383 | sh_serial_receive_break(s); |
ac3c9e74 | 384 | } |
2f062c72 TS |
385 | } |
386 | ||
9a9d0b81 BC |
387 | static const MemoryRegionOps sh_serial_ops = { |
388 | .read = sh_serial_read, | |
389 | .write = sh_serial_write, | |
390 | .endianness = DEVICE_NATIVE_ENDIAN, | |
2f062c72 TS |
391 | }; |
392 | ||
beeb5209 | 393 | static void sh_serial_reset(DeviceState *dev) |
2f062c72 | 394 | { |
beeb5209 BZ |
395 | SHSerialState *s = SH_SERIAL(dev); |
396 | ||
2f062c72 | 397 | s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE; |
63242a00 | 398 | s->rtrg = 1; |
2f062c72 TS |
399 | |
400 | s->smr = 0; | |
401 | s->brr = 0xff; | |
b7d35e65 | 402 | s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */ |
2f062c72 TS |
403 | s->sptr = 0; |
404 | ||
017f77bb | 405 | if (s->feat & SH_SERIAL_FEAT_SCIF) { |
2f062c72 | 406 | s->fcr = 0; |
f94bff13 | 407 | } else { |
2f062c72 TS |
408 | s->dr = 0xff; |
409 | } | |
410 | ||
63242a00 | 411 | sh_serial_clear_fifo(s); |
017f77bb BZ |
412 | } |
413 | ||
beeb5209 | 414 | static void sh_serial_realize(DeviceState *d, Error **errp) |
017f77bb | 415 | { |
beeb5209 BZ |
416 | SHSerialState *s = SH_SERIAL(d); |
417 | MemoryRegion *iomem = g_malloc(sizeof(*iomem)); | |
418 | ||
419 | assert(d->id); | |
420 | memory_region_init_io(iomem, OBJECT(d), &sh_serial_ops, s, d->id, 0x28); | |
421 | sysbus_init_mmio(SYS_BUS_DEVICE(d), iomem); | |
422 | qdev_init_gpio_out_named(d, &s->eri, "eri", 1); | |
423 | qdev_init_gpio_out_named(d, &s->rxi, "rxi", 1); | |
424 | qdev_init_gpio_out_named(d, &s->txi, "txi", 1); | |
425 | qdev_init_gpio_out_named(d, &s->tei, "tei", 1); | |
426 | qdev_init_gpio_out_named(d, &s->bri, "bri", 1); | |
427 | ||
428 | if (qemu_chr_fe_backend_connected(&s->chr)) { | |
5345fdb4 MAL |
429 | qemu_chr_fe_set_handlers(&s->chr, sh_serial_can_receive1, |
430 | sh_serial_receive1, | |
81517ba3 | 431 | sh_serial_event, NULL, s, NULL, true); |
456d6069 | 432 | } |
bf5b7423 | 433 | |
5b344b02 BZ |
434 | timer_init_ns(&s->fifo_timeout_timer, QEMU_CLOCK_VIRTUAL, |
435 | sh_serial_timeout_int, s); | |
71bb4ce1 | 436 | s->etu = NANOSECONDS_PER_SECOND / 9600; |
beeb5209 BZ |
437 | } |
438 | ||
439 | static void sh_serial_finalize(Object *obj) | |
440 | { | |
441 | SHSerialState *s = SH_SERIAL(obj); | |
442 | ||
443 | timer_del(&s->fifo_timeout_timer); | |
444 | } | |
445 | ||
446 | static void sh_serial_init(Object *obj) | |
447 | { | |
448 | } | |
449 | ||
450 | static Property sh_serial_properties[] = { | |
451 | DEFINE_PROP_CHR("chardev", SHSerialState, chr), | |
452 | DEFINE_PROP_UINT8("features", SHSerialState, feat, 0), | |
453 | DEFINE_PROP_END_OF_LIST() | |
454 | }; | |
455 | ||
456 | static void sh_serial_class_init(ObjectClass *oc, void *data) | |
457 | { | |
458 | DeviceClass *dc = DEVICE_CLASS(oc); | |
459 | ||
460 | device_class_set_props(dc, sh_serial_properties); | |
461 | dc->realize = sh_serial_realize; | |
462 | dc->reset = sh_serial_reset; | |
463 | /* Reason: part of SuperH CPU/SoC, needs to be wired up */ | |
464 | dc->user_creatable = false; | |
2f062c72 | 465 | } |