Merge remote-tracking branch 'qemu-project/master'
[qemu/ar7.git] / hw / arm / s3c24xx_serial.c
bloba536fbb0a5908bc437599f7f7da7f5579bc7363e
1 /* hw/s3c24xx_serial.c
3 * Samsung S3C24XX Serial block
5 * Copyright 2006, 2007 Daniel Silverstone and Vincent Sanders
7 * Copyright 2010, 2013, 2020 Stefan Weil
9 * This file is under the terms of the GNU General Public License Version 2.
12 #include "qemu/osdep.h"
13 #include "chardev/char-fe.h" /* qemu_chr_fe_write */
14 #include "cpu.h"
15 #include "exec/address-spaces.h" /* get_system_memory */
16 #include "hw/hw.h"
17 #include "hw/irq.h" /* qemu_set_irq */
18 #include "sysemu/sysemu.h"
20 #include "s3c24xx.h"
22 #if 0
23 # define DEBUG_S3C24XX
24 #endif
26 #if defined(DEBUG_S3C24XX)
27 # define logout(fmt, ...) \
28 fprintf(stderr, "S3C24xx\t%-24s" fmt, __func__, ##__VA_ARGS__)
29 #else
30 # define logout(fmt, ...) (void)0
31 #endif
33 /* S3C24XX serial port registers */
35 /* Line control RW WORD */
36 #define S3C_SERIAL_ULCON 0x00
37 /* General control RW WORD */
38 #define S3C_SERIAL_UCON 0x04
39 /* Fifo control RW WORD */
40 #define S3C_SERIAL_UFCON 0x08
41 /* Modem control RW WORD */
42 #define S3C_SERIAL_UMCON 0x0C
43 /* TX/RX Status RO WORD */
44 #define S3C_SERIAL_UTRSTAT 0x10
45 /* Receive Error Status RO WORD */
46 #define S3C_SERIAL_UERSTAT 0x14
47 /* FiFo Status RO WORD */
48 #define S3C_SERIAL_UFSTAT 0x18
49 /* Modem Status RO WORD */
50 #define S3C_SERIAL_UMSTAT 0x1C
51 /* TX buffer WR BYTE */
52 #define S3C_SERIAL_UTXH 0x20
53 /* RX buffer RO BYTE */
54 #define S3C_SERIAL_URXH 0x24
55 /* BAUD Divisor RW WORD */
56 #define S3C_SERIAL_UBRDIV 0x28
58 /* S3C24XX serial port state */
59 typedef struct s3c24xx_serial_dev_s {
60 MemoryRegion mmio;
61 uint32_t ulcon, ucon, ufcon, umcon, ubrdiv;
62 unsigned char rx_byte;
63 /* Byte is available to be read */
64 unsigned int rx_available : 1;
65 CharBackend chr;
66 qemu_irq tx_irq;
67 qemu_irq rx_irq;
68 qemu_irq tx_level;
69 qemu_irq rx_level;
70 } s3c24xx_serial_dev;
72 static void s3c24xx_serial_write(void *opaque, hwaddr addr,
73 uint64_t value, unsigned size)
75 s3c24xx_serial_dev *s = opaque;
76 int reg = addr & 0x3f;
78 logout("0x" TARGET_FMT_plx " 0x%08x\n", addr, value);
80 switch(reg) {
81 case S3C_SERIAL_ULCON:
82 s->ulcon = value;
83 break;
85 case S3C_SERIAL_UCON:
86 s->ucon = value;
87 if( s->ucon & 1<<9 ) {
88 qemu_set_irq(s->tx_level, 1);
89 } else {
90 qemu_set_irq(s->tx_level, 0);
92 if( !(s->ucon & 1<<8) ) {
93 qemu_set_irq(s->rx_level, 0);
95 break;
97 case S3C_SERIAL_UFCON:
98 s->ufcon = (value & ~6);
99 break;
101 case S3C_SERIAL_UMCON:
102 s->umcon = value;
103 break;
105 case S3C_SERIAL_UTRSTAT:
106 break;
108 case S3C_SERIAL_UERSTAT:
109 break;
111 case S3C_SERIAL_UFSTAT:
112 break;
114 case S3C_SERIAL_UMSTAT:
115 break;
117 case S3C_SERIAL_UTXH: {
118 unsigned char ch = value & 0xff;
119 if ((s->ucon & 1 << 5) == 0) {
120 qemu_chr_fe_write(&s->chr, &ch, 1);
121 } else {
122 s->rx_byte = ch;
123 s->rx_available = 1;
124 if( s->ucon & 1<<8 ) {
125 qemu_set_irq(s->rx_level, 1);
126 } else {
127 qemu_set_irq(s->rx_irq, 1);
130 if (s->ucon & 1<<9) {
131 qemu_set_irq(s->tx_level, 1);
132 } else {
133 qemu_set_irq(s->tx_irq, 1);
135 break;
138 case S3C_SERIAL_URXH:
139 break;
141 case S3C_SERIAL_UBRDIV:
142 s->ubrdiv = value;
143 break;
145 default:
146 break;
150 static uint64_t s3c24xx_serial_read(void *opaque, hwaddr addr,
151 unsigned size)
153 s3c24xx_serial_dev *s = opaque;
154 int reg = addr & 0x3f;
156 logout("0x" TARGET_FMT_plx "\n", addr);
158 switch (reg) {
159 case S3C_SERIAL_ULCON:
160 return s->ulcon;
162 case S3C_SERIAL_UCON:
163 return s->ucon;
165 case S3C_SERIAL_UFCON:
166 return s->ufcon & ~0x8; /* bit 3 is reserved, must be zero */
168 case S3C_SERIAL_UMCON:
169 return s->umcon & 0x11; /* Rest are reserved, must be zero */
171 case S3C_SERIAL_UTRSTAT:
172 return 6 | s->rx_available; /* TX always clear, RX when available */
174 case S3C_SERIAL_UERSTAT:
175 return 0; /* Later, break detect comes in here */
177 case S3C_SERIAL_UFSTAT:
178 return s->rx_available; /* TXFIFO, always empty, RXFIFO 0 or 1 bytes */
180 case S3C_SERIAL_UMSTAT:
181 return 0;
183 case S3C_SERIAL_UTXH:
184 return 0;
186 case S3C_SERIAL_URXH:
187 s->rx_available = 0;
188 if (s->ucon & 1<<8) {
189 qemu_set_irq(s->rx_level, 0);
191 return s->rx_byte;
193 case S3C_SERIAL_UBRDIV:
194 return s->ubrdiv;
196 default:
197 return 0;
201 static const MemoryRegionOps s3c24xx_serial_ops = {
202 .read = s3c24xx_serial_read,
203 .write = s3c24xx_serial_write,
204 .endianness = DEVICE_NATIVE_ENDIAN,
205 .valid = {
206 .min_access_size = 1,
207 .max_access_size = 4
211 static void s3c24xx_serial_event(void *opaque, int event)
215 static int
216 s3c24xx_serial_can_receive(void *opaque)
218 s3c24xx_serial_dev *s = opaque;
220 /* If there's no byte to be read, we can receive a new one */
221 return !s->rx_available;
224 static void
225 s3c24xx_serial_receive(void *opaque, const uint8_t *buf, int size)
227 s3c24xx_serial_dev *s = opaque;
228 s->rx_byte = buf[0];
229 s->rx_available = 1;
230 if ( s->ucon & 1 << 8 ) {
231 qemu_set_irq(s->rx_level, 1);
232 } else {
233 /* Is there something we can do here to ensure it's just a pulse ? */
234 qemu_set_irq(s->rx_irq, 1);
238 /* Create a S3C serial port, the port implementation is common to all
239 * current s3c devices only differing in the I/O base address and number of
240 * ports.
242 struct s3c24xx_serial_dev_s *
243 s3c24xx_serial_init(S3CState *soc,
244 Chardev *chr,
245 hwaddr base_addr,
246 int irqn)
248 /* Initialise a serial port at the given port address */
249 s3c24xx_serial_dev *s = g_new0(s3c24xx_serial_dev, 1);
251 /* initialise serial port context */
252 s->rx_irq = s3c24xx_get_irq(soc->irq, irqn);
253 s->rx_level = s3c24xx_get_irq(soc->irq, irqn + 64);
255 s->tx_irq = s3c24xx_get_irq(soc->irq, irqn + 1);
256 s->tx_level = s3c24xx_get_irq(soc->irq, irqn + 1 + 64);
258 /* Register the MMIO region. */
259 memory_region_init_io(&s->mmio, OBJECT(s), &s3c24xx_serial_ops, s,
260 "s3c24xx.serial", 44);
261 memory_region_add_subregion(get_system_memory(), base_addr, &s->mmio);
263 qemu_chr_fe_set_handlers(&s->chr,
264 s3c24xx_serial_can_receive,
265 s3c24xx_serial_receive,
266 s3c24xx_serial_event,
267 NULL, s, NULL, true);
269 return s;