|
1 /* |
|
2 * Arm PrimeCell PL022 Synchronous Serial Port |
|
3 * |
|
4 * Copyright (c) 2007 CodeSourcery. |
|
5 * Written by Paul Brook |
|
6 * |
|
7 * This code is licenced under the GPL. |
|
8 */ |
|
9 |
|
10 #include "hw.h" |
|
11 #include "primecell.h" |
|
12 |
|
13 //#define DEBUG_PL022 1 |
|
14 |
|
15 #ifdef DEBUG_PL022 |
|
16 #define DPRINTF(fmt, args...) \ |
|
17 do { printf("pl022: " fmt , ##args); } while (0) |
|
18 #define BADF(fmt, args...) \ |
|
19 do { fprintf(stderr, "pl022: error: " fmt , ##args); exit(1);} while (0) |
|
20 #else |
|
21 #define DPRINTF(fmt, args...) do {} while(0) |
|
22 #define BADF(fmt, args...) \ |
|
23 do { fprintf(stderr, "pl022: error: " fmt , ##args);} while (0) |
|
24 #endif |
|
25 |
|
26 #define PL022_CR1_LBM 0x01 |
|
27 #define PL022_CR1_SSE 0x02 |
|
28 #define PL022_CR1_MS 0x04 |
|
29 #define PL022_CR1_SDO 0x08 |
|
30 |
|
31 #define PL022_SR_TFE 0x01 |
|
32 #define PL022_SR_TNF 0x02 |
|
33 #define PL022_SR_RNE 0x04 |
|
34 #define PL022_SR_RFF 0x08 |
|
35 #define PL022_SR_BSY 0x10 |
|
36 |
|
37 #define PL022_INT_ROR 0x01 |
|
38 #define PL022_INT_RT 0x04 |
|
39 #define PL022_INT_RX 0x04 |
|
40 #define PL022_INT_TX 0x08 |
|
41 |
|
42 typedef struct { |
|
43 uint32_t cr0; |
|
44 uint32_t cr1; |
|
45 uint32_t bitmask; |
|
46 uint32_t sr; |
|
47 uint32_t cpsr; |
|
48 uint32_t is; |
|
49 uint32_t im; |
|
50 /* The FIFO head points to the next empty entry. */ |
|
51 int tx_fifo_head; |
|
52 int rx_fifo_head; |
|
53 int tx_fifo_len; |
|
54 int rx_fifo_len; |
|
55 uint16_t tx_fifo[8]; |
|
56 uint16_t rx_fifo[8]; |
|
57 qemu_irq irq; |
|
58 int (*xfer_cb)(void *, int); |
|
59 void *opaque; |
|
60 } pl022_state; |
|
61 |
|
62 static const unsigned char pl022_id[8] = |
|
63 { 0x22, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
|
64 |
|
65 static void pl022_update(pl022_state *s) |
|
66 { |
|
67 s->sr = 0; |
|
68 if (s->tx_fifo_len == 0) |
|
69 s->sr |= PL022_SR_TFE; |
|
70 if (s->tx_fifo_len != 8) |
|
71 s->sr |= PL022_SR_TNF; |
|
72 if (s->rx_fifo_len != 0) |
|
73 s->sr |= PL022_SR_RNE; |
|
74 if (s->rx_fifo_len == 8) |
|
75 s->sr |= PL022_SR_RFF; |
|
76 if (s->tx_fifo_len) |
|
77 s->sr |= PL022_SR_BSY; |
|
78 s->is = 0; |
|
79 if (s->rx_fifo_len >= 4) |
|
80 s->is |= PL022_INT_RX; |
|
81 if (s->tx_fifo_len <= 4) |
|
82 s->is |= PL022_INT_TX; |
|
83 |
|
84 qemu_set_irq(s->irq, (s->is & s->im) != 0); |
|
85 } |
|
86 |
|
87 static void pl022_xfer(pl022_state *s) |
|
88 { |
|
89 int i; |
|
90 int o; |
|
91 int val; |
|
92 |
|
93 if ((s->cr1 & PL022_CR1_SSE) == 0) { |
|
94 pl022_update(s); |
|
95 DPRINTF("Disabled\n"); |
|
96 return; |
|
97 } |
|
98 |
|
99 DPRINTF("Maybe xfer %d/%d\n", s->tx_fifo_len, s->rx_fifo_len); |
|
100 i = (s->tx_fifo_head - s->tx_fifo_len) & 7; |
|
101 o = s->rx_fifo_head; |
|
102 /* ??? We do not emulate the line speed. |
|
103 This may break some applications. The are two problematic cases: |
|
104 (a) A driver feeds data into the TX FIFO until it is full, |
|
105 and only then drains the RX FIFO. On real hardware the CPU can |
|
106 feed data fast enough that the RX fifo never gets chance to overflow. |
|
107 (b) A driver transmits data, deliberately allowing the RX FIFO to |
|
108 overflow because it ignores the RX data anyway. |
|
109 |
|
110 We choose to support (a) by stalling the transmit engine if it would |
|
111 cause the RX FIFO to overflow. In practice much transmit-only code |
|
112 falls into (a) because it flushes the RX FIFO to determine when |
|
113 the transfer has completed. */ |
|
114 while (s->tx_fifo_len && s->rx_fifo_len < 8) { |
|
115 DPRINTF("xfer\n"); |
|
116 val = s->tx_fifo[i]; |
|
117 if (s->cr1 & PL022_CR1_LBM) { |
|
118 /* Loopback mode. */ |
|
119 } else if (s->xfer_cb) { |
|
120 val = s->xfer_cb(s->opaque, val); |
|
121 } else { |
|
122 val = 0; |
|
123 } |
|
124 s->rx_fifo[o] = val & s->bitmask; |
|
125 i = (i + 1) & 7; |
|
126 o = (o + 1) & 7; |
|
127 s->tx_fifo_len--; |
|
128 s->rx_fifo_len++; |
|
129 } |
|
130 s->rx_fifo_head = o; |
|
131 pl022_update(s); |
|
132 } |
|
133 |
|
134 static uint32_t pl022_read(void *opaque, target_phys_addr_t offset) |
|
135 { |
|
136 pl022_state *s = (pl022_state *)opaque; |
|
137 int val; |
|
138 |
|
139 if (offset >= 0xfe0 && offset < 0x1000) { |
|
140 return pl022_id[(offset - 0xfe0) >> 2]; |
|
141 } |
|
142 switch (offset) { |
|
143 case 0x00: /* CR0 */ |
|
144 return s->cr0; |
|
145 case 0x04: /* CR1 */ |
|
146 return s->cr1; |
|
147 case 0x08: /* DR */ |
|
148 if (s->rx_fifo_len) { |
|
149 val = s->rx_fifo[(s->rx_fifo_head - s->rx_fifo_len) & 7]; |
|
150 DPRINTF("RX %02x\n", val); |
|
151 s->rx_fifo_len--; |
|
152 pl022_xfer(s); |
|
153 } else { |
|
154 val = 0; |
|
155 } |
|
156 return val; |
|
157 case 0x0c: /* SR */ |
|
158 return s->sr; |
|
159 case 0x10: /* CPSR */ |
|
160 return s->cpsr; |
|
161 case 0x14: /* IMSC */ |
|
162 return s->im; |
|
163 case 0x18: /* RIS */ |
|
164 return s->is; |
|
165 case 0x1c: /* MIS */ |
|
166 return s->im & s->is; |
|
167 case 0x20: /* DMACR */ |
|
168 /* Not implemented. */ |
|
169 return 0; |
|
170 default: |
|
171 cpu_abort (cpu_single_env, "pl022_read: Bad offset %x\n", |
|
172 (int)offset); |
|
173 return 0; |
|
174 } |
|
175 } |
|
176 |
|
177 static void pl022_write(void *opaque, target_phys_addr_t offset, |
|
178 uint32_t value) |
|
179 { |
|
180 pl022_state *s = (pl022_state *)opaque; |
|
181 |
|
182 switch (offset) { |
|
183 case 0x00: /* CR0 */ |
|
184 s->cr0 = value; |
|
185 /* Clock rate and format are ignored. */ |
|
186 s->bitmask = (1 << ((value & 15) + 1)) - 1; |
|
187 break; |
|
188 case 0x04: /* CR1 */ |
|
189 s->cr1 = value; |
|
190 if ((s->cr1 & (PL022_CR1_MS | PL022_CR1_SSE)) |
|
191 == (PL022_CR1_MS | PL022_CR1_SSE)) { |
|
192 BADF("SPI slave mode not implemented\n"); |
|
193 } |
|
194 pl022_xfer(s); |
|
195 break; |
|
196 case 0x08: /* DR */ |
|
197 if (s->tx_fifo_len < 8) { |
|
198 DPRINTF("TX %02x\n", value); |
|
199 s->tx_fifo[s->tx_fifo_head] = value & s->bitmask; |
|
200 s->tx_fifo_head = (s->tx_fifo_head + 1) & 7; |
|
201 s->tx_fifo_len++; |
|
202 pl022_xfer(s); |
|
203 } |
|
204 break; |
|
205 case 0x10: /* CPSR */ |
|
206 /* Prescaler. Ignored. */ |
|
207 s->cpsr = value & 0xff; |
|
208 break; |
|
209 case 0x14: /* IMSC */ |
|
210 s->im = value; |
|
211 pl022_update(s); |
|
212 break; |
|
213 case 0x20: /* DMACR */ |
|
214 if (value) |
|
215 cpu_abort (cpu_single_env, "pl022: DMA not implemented\n"); |
|
216 break; |
|
217 default: |
|
218 cpu_abort (cpu_single_env, "pl022_write: Bad offset %x\n", |
|
219 (int)offset); |
|
220 } |
|
221 } |
|
222 |
|
223 static void pl022_reset(pl022_state *s) |
|
224 { |
|
225 s->rx_fifo_len = 0; |
|
226 s->tx_fifo_len = 0; |
|
227 s->im = 0; |
|
228 s->is = PL022_INT_TX; |
|
229 s->sr = PL022_SR_TFE | PL022_SR_TNF; |
|
230 } |
|
231 |
|
232 static CPUReadMemoryFunc *pl022_readfn[] = { |
|
233 pl022_read, |
|
234 pl022_read, |
|
235 pl022_read |
|
236 }; |
|
237 |
|
238 static CPUWriteMemoryFunc *pl022_writefn[] = { |
|
239 pl022_write, |
|
240 pl022_write, |
|
241 pl022_write |
|
242 }; |
|
243 |
|
244 static void pl022_save(QEMUFile *f, void *opaque) |
|
245 { |
|
246 pl022_state *s = (pl022_state *)opaque; |
|
247 int i; |
|
248 |
|
249 qemu_put_be32(f, s->cr0); |
|
250 qemu_put_be32(f, s->cr1); |
|
251 qemu_put_be32(f, s->bitmask); |
|
252 qemu_put_be32(f, s->sr); |
|
253 qemu_put_be32(f, s->cpsr); |
|
254 qemu_put_be32(f, s->is); |
|
255 qemu_put_be32(f, s->im); |
|
256 qemu_put_be32(f, s->tx_fifo_head); |
|
257 qemu_put_be32(f, s->rx_fifo_head); |
|
258 qemu_put_be32(f, s->tx_fifo_len); |
|
259 qemu_put_be32(f, s->rx_fifo_len); |
|
260 for (i = 0; i < 8; i++) { |
|
261 qemu_put_be16(f, s->tx_fifo[i]); |
|
262 qemu_put_be16(f, s->rx_fifo[i]); |
|
263 } |
|
264 } |
|
265 |
|
266 static int pl022_load(QEMUFile *f, void *opaque, int version_id) |
|
267 { |
|
268 pl022_state *s = (pl022_state *)opaque; |
|
269 int i; |
|
270 |
|
271 if (version_id != 1) |
|
272 return -EINVAL; |
|
273 |
|
274 s->cr0 = qemu_get_be32(f); |
|
275 s->cr1 = qemu_get_be32(f); |
|
276 s->bitmask = qemu_get_be32(f); |
|
277 s->sr = qemu_get_be32(f); |
|
278 s->cpsr = qemu_get_be32(f); |
|
279 s->is = qemu_get_be32(f); |
|
280 s->im = qemu_get_be32(f); |
|
281 s->tx_fifo_head = qemu_get_be32(f); |
|
282 s->rx_fifo_head = qemu_get_be32(f); |
|
283 s->tx_fifo_len = qemu_get_be32(f); |
|
284 s->rx_fifo_len = qemu_get_be32(f); |
|
285 for (i = 0; i < 8; i++) { |
|
286 s->tx_fifo[i] = qemu_get_be16(f); |
|
287 s->rx_fifo[i] = qemu_get_be16(f); |
|
288 } |
|
289 |
|
290 return 0; |
|
291 } |
|
292 |
|
293 void pl022_init(uint32_t base, qemu_irq irq, int (*xfer_cb)(void *, int), |
|
294 void * opaque) |
|
295 { |
|
296 int iomemtype; |
|
297 pl022_state *s; |
|
298 |
|
299 s = (pl022_state *)qemu_mallocz(sizeof(pl022_state)); |
|
300 iomemtype = cpu_register_io_memory(0, pl022_readfn, |
|
301 pl022_writefn, s); |
|
302 cpu_register_physical_memory(base, 0x00001000, iomemtype); |
|
303 s->irq = irq; |
|
304 s->xfer_cb = xfer_cb; |
|
305 s->opaque = opaque; |
|
306 pl022_reset(s); |
|
307 register_savevm("pl022_ssp", -1, 1, pl022_save, pl022_load, s); |
|
308 } |