]>
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; |
2f062c72 TS |
97 | unsigned char ch; |
98 | ||
ad52cfc1 | 99 | trace_sh_serial_write(size, offs, val); |
f94bff13 | 100 | switch (offs) { |
2f062c72 TS |
101 | case 0x00: /* SMR */ |
102 | s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff); | |
103 | return; | |
104 | case 0x04: /* BRR */ | |
105 | s->brr = val; | |
7d37435b | 106 | return; |
2f062c72 | 107 | case 0x08: /* SCR */ |
63242a00 | 108 | /* TODO : For SH7751, SCIF mask should be 0xfb. */ |
bf5b7423 | 109 | s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff); |
ac3c9e74 | 110 | if (!(val & (1 << 5))) { |
2f062c72 | 111 | s->flags |= SH_SERIAL_FLAG_TEND; |
ac3c9e74 | 112 | } |
bf5b7423 | 113 | if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) { |
7d37435b | 114 | qemu_set_irq(s->txi, val & (1 << 7)); |
bf5b7423 | 115 | } |
4e7ed2d1 | 116 | if (!(val & (1 << 6))) { |
7d37435b | 117 | qemu_set_irq(s->rxi, 0); |
63242a00 | 118 | } |
2f062c72 TS |
119 | return; |
120 | case 0x0c: /* FTDR / TDR */ | |
30650701 | 121 | if (qemu_chr_fe_backend_connected(&s->chr)) { |
2f062c72 | 122 | ch = val; |
22138965 BZ |
123 | /* |
124 | * XXX this blocks entire thread. Rewrite to use | |
125 | * qemu_chr_fe_write and background I/O callbacks | |
126 | */ | |
5345fdb4 | 127 | qemu_chr_fe_write_all(&s->chr, &ch, 1); |
7d37435b PB |
128 | } |
129 | s->dr = val; | |
130 | s->flags &= ~SH_SERIAL_FLAG_TDE; | |
2f062c72 TS |
131 | return; |
132 | #if 0 | |
133 | case 0x14: /* FRDR / RDR */ | |
134 | ret = 0; | |
135 | break; | |
136 | #endif | |
137 | } | |
138 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
f94bff13 | 139 | switch (offs) { |
2f062c72 | 140 | case 0x10: /* FSR */ |
ac3c9e74 | 141 | if (!(val & (1 << 6))) { |
2f062c72 | 142 | s->flags &= ~SH_SERIAL_FLAG_TEND; |
ac3c9e74 BZ |
143 | } |
144 | if (!(val & (1 << 5))) { | |
2f062c72 | 145 | s->flags &= ~SH_SERIAL_FLAG_TDE; |
ac3c9e74 BZ |
146 | } |
147 | if (!(val & (1 << 4))) { | |
2f062c72 | 148 | s->flags &= ~SH_SERIAL_FLAG_BRK; |
ac3c9e74 BZ |
149 | } |
150 | if (!(val & (1 << 1))) { | |
2f062c72 | 151 | s->flags &= ~SH_SERIAL_FLAG_RDF; |
ac3c9e74 BZ |
152 | } |
153 | if (!(val & (1 << 0))) { | |
2f062c72 | 154 | s->flags &= ~SH_SERIAL_FLAG_DR; |
ac3c9e74 | 155 | } |
63242a00 AJ |
156 | |
157 | if (!(val & (1 << 1)) || !(val & (1 << 0))) { | |
4e7ed2d1 AJ |
158 | if (s->rxi) { |
159 | qemu_set_irq(s->rxi, 0); | |
63242a00 AJ |
160 | } |
161 | } | |
2f062c72 TS |
162 | return; |
163 | case 0x18: /* FCR */ | |
164 | s->fcr = val; | |
63242a00 AJ |
165 | switch ((val >> 6) & 3) { |
166 | case 0: | |
167 | s->rtrg = 1; | |
168 | break; | |
169 | case 1: | |
170 | s->rtrg = 4; | |
171 | break; | |
172 | case 2: | |
173 | s->rtrg = 8; | |
174 | break; | |
175 | case 3: | |
176 | s->rtrg = 14; | |
177 | break; | |
178 | } | |
179 | if (val & (1 << 1)) { | |
180 | sh_serial_clear_fifo(s); | |
181 | s->sr &= ~(1 << 1); | |
182 | } | |
183 | ||
2f062c72 TS |
184 | return; |
185 | case 0x20: /* SPTR */ | |
63242a00 | 186 | s->sptr = val & 0xf3; |
2f062c72 TS |
187 | return; |
188 | case 0x24: /* LSR */ | |
189 | return; | |
190 | } | |
f94bff13 BZ |
191 | } else { |
192 | switch (offs) { | |
d1f193b0 | 193 | #if 0 |
2f062c72 TS |
194 | case 0x0c: |
195 | ret = s->dr; | |
196 | break; | |
197 | case 0x10: | |
198 | ret = 0; | |
199 | break; | |
d1f193b0 | 200 | #endif |
2f062c72 | 201 | case 0x1c: |
d1f193b0 AJ |
202 | s->sptr = val & 0x8f; |
203 | return; | |
2f062c72 | 204 | } |
2f062c72 | 205 | } |
3cf7ce43 BZ |
206 | qemu_log_mask(LOG_GUEST_ERROR, |
207 | "%s: unsupported write to 0x%02" HWADDR_PRIx "\n", | |
208 | __func__, offs); | |
2f062c72 TS |
209 | } |
210 | ||
a8170e5e | 211 | static uint64_t sh_serial_read(void *opaque, hwaddr offs, |
9a9d0b81 | 212 | unsigned size) |
2f062c72 | 213 | { |
2f6df137 | 214 | SHSerialState *s = opaque; |
3cf7ce43 | 215 | uint32_t ret = UINT32_MAX; |
2f062c72 TS |
216 | |
217 | #if 0 | |
f94bff13 | 218 | switch (offs) { |
2f062c72 TS |
219 | case 0x00: |
220 | ret = s->smr; | |
221 | break; | |
222 | case 0x04: | |
223 | ret = s->brr; | |
7d37435b | 224 | break; |
2f062c72 TS |
225 | case 0x08: |
226 | ret = s->scr; | |
227 | break; | |
228 | case 0x14: | |
229 | ret = 0; | |
230 | break; | |
231 | } | |
232 | #endif | |
233 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
f94bff13 | 234 | switch (offs) { |
bf5b7423 AJ |
235 | case 0x00: /* SMR */ |
236 | ret = s->smr; | |
237 | break; | |
238 | case 0x08: /* SCR */ | |
239 | ret = s->scr; | |
240 | break; | |
2f062c72 TS |
241 | case 0x10: /* FSR */ |
242 | ret = 0; | |
ac3c9e74 | 243 | if (s->flags & SH_SERIAL_FLAG_TEND) { |
2f062c72 | 244 | ret |= (1 << 6); |
ac3c9e74 BZ |
245 | } |
246 | if (s->flags & SH_SERIAL_FLAG_TDE) { | |
2f062c72 | 247 | ret |= (1 << 5); |
ac3c9e74 BZ |
248 | } |
249 | if (s->flags & SH_SERIAL_FLAG_BRK) { | |
2f062c72 | 250 | ret |= (1 << 4); |
ac3c9e74 BZ |
251 | } |
252 | if (s->flags & SH_SERIAL_FLAG_RDF) { | |
2f062c72 | 253 | ret |= (1 << 1); |
ac3c9e74 BZ |
254 | } |
255 | if (s->flags & SH_SERIAL_FLAG_DR) { | |
2f062c72 | 256 | ret |= (1 << 0); |
ac3c9e74 | 257 | } |
2f062c72 | 258 | |
ac3c9e74 | 259 | if (s->scr & (1 << 5)) { |
2f062c72 | 260 | s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND; |
ac3c9e74 | 261 | } |
2f062c72 | 262 | |
63242a00 AJ |
263 | break; |
264 | case 0x14: | |
265 | if (s->rx_cnt > 0) { | |
266 | ret = s->rx_fifo[s->rx_tail++]; | |
267 | s->rx_cnt--; | |
ac3c9e74 | 268 | if (s->rx_tail == SH_RX_FIFO_LENGTH) { |
63242a00 | 269 | s->rx_tail = 0; |
ac3c9e74 BZ |
270 | } |
271 | if (s->rx_cnt < s->rtrg) { | |
63242a00 | 272 | s->flags &= ~SH_SERIAL_FLAG_RDF; |
ac3c9e74 | 273 | } |
63242a00 | 274 | } |
2f062c72 | 275 | break; |
2f062c72 TS |
276 | case 0x18: |
277 | ret = s->fcr; | |
278 | break; | |
2f062c72 TS |
279 | case 0x1c: |
280 | ret = s->rx_cnt; | |
281 | break; | |
282 | case 0x20: | |
283 | ret = s->sptr; | |
284 | break; | |
285 | case 0x24: | |
286 | ret = 0; | |
287 | break; | |
288 | } | |
f94bff13 BZ |
289 | } else { |
290 | switch (offs) { | |
d1f193b0 | 291 | #if 0 |
2f062c72 TS |
292 | case 0x0c: |
293 | ret = s->dr; | |
294 | break; | |
295 | case 0x10: | |
296 | ret = 0; | |
297 | break; | |
63242a00 AJ |
298 | case 0x14: |
299 | ret = s->rx_fifo[0]; | |
300 | break; | |
d1f193b0 | 301 | #endif |
2f062c72 TS |
302 | case 0x1c: |
303 | ret = s->sptr; | |
304 | break; | |
305 | } | |
2f062c72 | 306 | } |
ad52cfc1 | 307 | trace_sh_serial_read(size, offs, ret); |
2f062c72 | 308 | |
3cf7ce43 BZ |
309 | if (ret > UINT16_MAX) { |
310 | qemu_log_mask(LOG_GUEST_ERROR, | |
311 | "%s: unsupported read from 0x%02" HWADDR_PRIx "\n", | |
312 | __func__, offs); | |
313 | ret = 0; | |
2f062c72 TS |
314 | } |
315 | ||
316 | return ret; | |
317 | } | |
318 | ||
2f6df137 | 319 | static int sh_serial_can_receive(SHSerialState *s) |
2f062c72 | 320 | { |
63242a00 | 321 | return s->scr & (1 << 4); |
2f062c72 TS |
322 | } |
323 | ||
2f6df137 | 324 | static void sh_serial_receive_break(SHSerialState *s) |
2f062c72 | 325 | { |
ac3c9e74 | 326 | if (s->feat & SH_SERIAL_FEAT_SCIF) { |
63242a00 | 327 | s->sr |= (1 << 4); |
ac3c9e74 | 328 | } |
2f062c72 TS |
329 | } |
330 | ||
331 | static int sh_serial_can_receive1(void *opaque) | |
332 | { | |
2f6df137 | 333 | SHSerialState *s = opaque; |
2f062c72 TS |
334 | return sh_serial_can_receive(s); |
335 | } | |
336 | ||
71bb4ce1 GU |
337 | static void sh_serial_timeout_int(void *opaque) |
338 | { | |
2f6df137 | 339 | SHSerialState *s = opaque; |
71bb4ce1 GU |
340 | |
341 | s->flags |= SH_SERIAL_FLAG_RDF; | |
342 | if (s->scr & (1 << 6) && s->rxi) { | |
343 | qemu_set_irq(s->rxi, 1); | |
344 | } | |
345 | } | |
346 | ||
2f062c72 TS |
347 | static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size) |
348 | { | |
2f6df137 | 349 | SHSerialState *s = opaque; |
b7d2b020 AJ |
350 | |
351 | if (s->feat & SH_SERIAL_FEAT_SCIF) { | |
352 | int i; | |
353 | for (i = 0; i < size; i++) { | |
354 | if (s->rx_cnt < SH_RX_FIFO_LENGTH) { | |
355 | s->rx_fifo[s->rx_head++] = buf[i]; | |
356 | if (s->rx_head == SH_RX_FIFO_LENGTH) { | |
357 | s->rx_head = 0; | |
358 | } | |
359 | s->rx_cnt++; | |
360 | if (s->rx_cnt >= s->rtrg) { | |
361 | s->flags |= SH_SERIAL_FLAG_RDF; | |
362 | if (s->scr & (1 << 6) && s->rxi) { | |
5b344b02 | 363 | timer_del(&s->fifo_timeout_timer); |
b7d2b020 AJ |
364 | qemu_set_irq(s->rxi, 1); |
365 | } | |
71bb4ce1 | 366 | } else { |
5b344b02 | 367 | timer_mod(&s->fifo_timeout_timer, |
71bb4ce1 | 368 | qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); |
b7d2b020 AJ |
369 | } |
370 | } | |
371 | } | |
372 | } else { | |
373 | s->rx_fifo[0] = buf[0]; | |
374 | } | |
2f062c72 TS |
375 | } |
376 | ||
083b266f | 377 | static void sh_serial_event(void *opaque, QEMUChrEvent event) |
2f062c72 | 378 | { |
2f6df137 | 379 | SHSerialState *s = opaque; |
ac3c9e74 | 380 | if (event == CHR_EVENT_BREAK) { |
2f062c72 | 381 | sh_serial_receive_break(s); |
ac3c9e74 | 382 | } |
2f062c72 TS |
383 | } |
384 | ||
9a9d0b81 BC |
385 | static const MemoryRegionOps sh_serial_ops = { |
386 | .read = sh_serial_read, | |
387 | .write = sh_serial_write, | |
388 | .endianness = DEVICE_NATIVE_ENDIAN, | |
2f062c72 TS |
389 | }; |
390 | ||
beeb5209 | 391 | static void sh_serial_reset(DeviceState *dev) |
2f062c72 | 392 | { |
beeb5209 BZ |
393 | SHSerialState *s = SH_SERIAL(dev); |
394 | ||
2f062c72 | 395 | s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE; |
63242a00 | 396 | s->rtrg = 1; |
2f062c72 TS |
397 | |
398 | s->smr = 0; | |
399 | s->brr = 0xff; | |
b7d35e65 | 400 | s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */ |
2f062c72 TS |
401 | s->sptr = 0; |
402 | ||
017f77bb | 403 | if (s->feat & SH_SERIAL_FEAT_SCIF) { |
2f062c72 | 404 | s->fcr = 0; |
f94bff13 | 405 | } else { |
2f062c72 TS |
406 | s->dr = 0xff; |
407 | } | |
408 | ||
63242a00 | 409 | sh_serial_clear_fifo(s); |
017f77bb BZ |
410 | } |
411 | ||
beeb5209 | 412 | static void sh_serial_realize(DeviceState *d, Error **errp) |
017f77bb | 413 | { |
beeb5209 BZ |
414 | SHSerialState *s = SH_SERIAL(d); |
415 | MemoryRegion *iomem = g_malloc(sizeof(*iomem)); | |
416 | ||
417 | assert(d->id); | |
418 | memory_region_init_io(iomem, OBJECT(d), &sh_serial_ops, s, d->id, 0x28); | |
419 | sysbus_init_mmio(SYS_BUS_DEVICE(d), iomem); | |
420 | qdev_init_gpio_out_named(d, &s->eri, "eri", 1); | |
421 | qdev_init_gpio_out_named(d, &s->rxi, "rxi", 1); | |
422 | qdev_init_gpio_out_named(d, &s->txi, "txi", 1); | |
423 | qdev_init_gpio_out_named(d, &s->tei, "tei", 1); | |
424 | qdev_init_gpio_out_named(d, &s->bri, "bri", 1); | |
425 | ||
426 | if (qemu_chr_fe_backend_connected(&s->chr)) { | |
5345fdb4 MAL |
427 | qemu_chr_fe_set_handlers(&s->chr, sh_serial_can_receive1, |
428 | sh_serial_receive1, | |
81517ba3 | 429 | sh_serial_event, NULL, s, NULL, true); |
456d6069 | 430 | } |
bf5b7423 | 431 | |
5b344b02 BZ |
432 | timer_init_ns(&s->fifo_timeout_timer, QEMU_CLOCK_VIRTUAL, |
433 | sh_serial_timeout_int, s); | |
71bb4ce1 | 434 | s->etu = NANOSECONDS_PER_SECOND / 9600; |
beeb5209 BZ |
435 | } |
436 | ||
437 | static void sh_serial_finalize(Object *obj) | |
438 | { | |
439 | SHSerialState *s = SH_SERIAL(obj); | |
440 | ||
441 | timer_del(&s->fifo_timeout_timer); | |
442 | } | |
443 | ||
444 | static void sh_serial_init(Object *obj) | |
445 | { | |
446 | } | |
447 | ||
448 | static Property sh_serial_properties[] = { | |
449 | DEFINE_PROP_CHR("chardev", SHSerialState, chr), | |
450 | DEFINE_PROP_UINT8("features", SHSerialState, feat, 0), | |
451 | DEFINE_PROP_END_OF_LIST() | |
452 | }; | |
453 | ||
454 | static void sh_serial_class_init(ObjectClass *oc, void *data) | |
455 | { | |
456 | DeviceClass *dc = DEVICE_CLASS(oc); | |
457 | ||
458 | device_class_set_props(dc, sh_serial_properties); | |
459 | dc->realize = sh_serial_realize; | |
460 | dc->reset = sh_serial_reset; | |
461 | /* Reason: part of SuperH CPU/SoC, needs to be wired up */ | |
462 | dc->user_creatable = false; | |
2f062c72 | 463 | } |