1 | /* |
2 | * QEMU model of the UART on the SiFive E300 and U500 series SOCs. |
3 | * |
4 | * Copyright (c) 2016 Stefan O'Rear |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it |
7 | * under the terms and conditions of the GNU General Public License, |
8 | * version 2 or later, as published by the Free Software Foundation. |
9 | * |
10 | * This program is distributed in the hope it will be useful, but WITHOUT |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
13 | * more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along with |
16 | * this program. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include "qemu/osdep.h" |
20 | #include "qapi/error.h" |
21 | #include "hw/sysbus.h" |
22 | #include "chardev/char.h" |
23 | #include "chardev/char-fe.h" |
24 | #include "target/riscv/cpu.h" |
25 | #include "hw/hw.h" |
26 | #include "hw/irq.h" |
27 | #include "hw/riscv/sifive_uart.h" |
28 | |
29 | /* |
30 | * Not yet implemented: |
31 | * |
32 | * Transmit FIFO using "qemu/fifo8.h" |
33 | */ |
34 | |
35 | /* Returns the state of the IP (interrupt pending) register */ |
36 | static uint64_t uart_ip(SiFiveUARTState *s) |
37 | { |
38 | uint64_t ret = 0; |
39 | |
40 | uint64_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl); |
41 | uint64_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl); |
42 | |
43 | if (txcnt != 0) { |
44 | ret |= SIFIVE_UART_IP_TXWM; |
45 | } |
46 | if (s->rx_fifo_len > rxcnt) { |
47 | ret |= SIFIVE_UART_IP_RXWM; |
48 | } |
49 | |
50 | return ret; |
51 | } |
52 | |
53 | static void update_irq(SiFiveUARTState *s) |
54 | { |
55 | int cond = 0; |
56 | if ((s->ie & SIFIVE_UART_IE_TXWM) || |
57 | ((s->ie & SIFIVE_UART_IE_RXWM) && s->rx_fifo_len)) { |
58 | cond = 1; |
59 | } |
60 | if (cond) { |
61 | qemu_irq_raise(s->irq); |
62 | } else { |
63 | qemu_irq_lower(s->irq); |
64 | } |
65 | } |
66 | |
67 | static uint64_t |
68 | uart_read(void *opaque, hwaddr addr, unsigned int size) |
69 | { |
70 | SiFiveUARTState *s = opaque; |
71 | unsigned char r; |
72 | switch (addr) { |
73 | case SIFIVE_UART_RXFIFO: |
74 | if (s->rx_fifo_len) { |
75 | r = s->rx_fifo[0]; |
76 | memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1); |
77 | s->rx_fifo_len--; |
78 | qemu_chr_fe_accept_input(&s->chr); |
79 | update_irq(s); |
80 | return r; |
81 | } |
82 | return 0x80000000; |
83 | |
84 | case SIFIVE_UART_TXFIFO: |
85 | return 0; /* Should check tx fifo */ |
86 | case SIFIVE_UART_IE: |
87 | return s->ie; |
88 | case SIFIVE_UART_IP: |
89 | return uart_ip(s); |
90 | case SIFIVE_UART_TXCTRL: |
91 | return s->txctrl; |
92 | case SIFIVE_UART_RXCTRL: |
93 | return s->rxctrl; |
94 | case SIFIVE_UART_DIV: |
95 | return s->div; |
96 | } |
97 | |
98 | hw_error("%s: bad read: addr=0x%x\n" , |
99 | __func__, (int)addr); |
100 | return 0; |
101 | } |
102 | |
103 | static void |
104 | uart_write(void *opaque, hwaddr addr, |
105 | uint64_t val64, unsigned int size) |
106 | { |
107 | SiFiveUARTState *s = opaque; |
108 | uint32_t value = val64; |
109 | unsigned char ch = value; |
110 | |
111 | switch (addr) { |
112 | case SIFIVE_UART_TXFIFO: |
113 | qemu_chr_fe_write(&s->chr, &ch, 1); |
114 | update_irq(s); |
115 | return; |
116 | case SIFIVE_UART_IE: |
117 | s->ie = val64; |
118 | update_irq(s); |
119 | return; |
120 | case SIFIVE_UART_TXCTRL: |
121 | s->txctrl = val64; |
122 | return; |
123 | case SIFIVE_UART_RXCTRL: |
124 | s->rxctrl = val64; |
125 | return; |
126 | case SIFIVE_UART_DIV: |
127 | s->div = val64; |
128 | return; |
129 | } |
130 | hw_error("%s: bad write: addr=0x%x v=0x%x\n" , |
131 | __func__, (int)addr, (int)value); |
132 | } |
133 | |
134 | static const MemoryRegionOps uart_ops = { |
135 | .read = uart_read, |
136 | .write = uart_write, |
137 | .endianness = DEVICE_NATIVE_ENDIAN, |
138 | .valid = { |
139 | .min_access_size = 4, |
140 | .max_access_size = 4 |
141 | } |
142 | }; |
143 | |
144 | static void uart_rx(void *opaque, const uint8_t *buf, int size) |
145 | { |
146 | SiFiveUARTState *s = opaque; |
147 | |
148 | /* Got a byte. */ |
149 | if (s->rx_fifo_len >= sizeof(s->rx_fifo)) { |
150 | printf("WARNING: UART dropped char.\n" ); |
151 | return; |
152 | } |
153 | s->rx_fifo[s->rx_fifo_len++] = *buf; |
154 | |
155 | update_irq(s); |
156 | } |
157 | |
158 | static int uart_can_rx(void *opaque) |
159 | { |
160 | SiFiveUARTState *s = opaque; |
161 | |
162 | return s->rx_fifo_len < sizeof(s->rx_fifo); |
163 | } |
164 | |
165 | static void uart_event(void *opaque, int event) |
166 | { |
167 | } |
168 | |
169 | static int uart_be_change(void *opaque) |
170 | { |
171 | SiFiveUARTState *s = opaque; |
172 | |
173 | qemu_chr_fe_set_handlers(&s->chr, uart_can_rx, uart_rx, uart_event, |
174 | uart_be_change, s, NULL, true); |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | /* |
180 | * Create UART device. |
181 | */ |
182 | SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base, |
183 | Chardev *chr, qemu_irq irq) |
184 | { |
185 | SiFiveUARTState *s = g_malloc0(sizeof(SiFiveUARTState)); |
186 | s->irq = irq; |
187 | qemu_chr_fe_init(&s->chr, chr, &error_abort); |
188 | qemu_chr_fe_set_handlers(&s->chr, uart_can_rx, uart_rx, uart_event, |
189 | uart_be_change, s, NULL, true); |
190 | memory_region_init_io(&s->mmio, NULL, &uart_ops, s, |
191 | TYPE_SIFIVE_UART, SIFIVE_UART_MAX); |
192 | memory_region_add_subregion(address_space, base, &s->mmio); |
193 | return s; |
194 | } |
195 | |