pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Arm PrimeCell PL011 UART |
| 3 | * |
| 4 | * Copyright (c) 2006 CodeSourcery. |
| 5 | * Written by Paul Brook |
| 6 | * |
| 7 | * This code is licenced under the GPL. |
| 8 | */ |
| 9 | |
| 10 | #include "vl.h" |
| 11 | |
| 12 | typedef struct { |
| 13 | uint32_t base; |
| 14 | uint32_t readbuff; |
| 15 | uint32_t flags; |
| 16 | uint32_t lcr; |
| 17 | uint32_t cr; |
| 18 | uint32_t dmacr; |
| 19 | uint32_t int_enabled; |
| 20 | uint32_t int_level; |
| 21 | uint32_t read_fifo[16]; |
| 22 | uint32_t ilpr; |
| 23 | uint32_t ibrd; |
| 24 | uint32_t fbrd; |
| 25 | uint32_t ifl; |
| 26 | int read_pos; |
| 27 | int read_count; |
| 28 | int read_trigger; |
| 29 | CharDriverState *chr; |
pbrook | d537cf6 | 2007-04-07 18:14:41 +0000 | [diff] [blame] | 30 | qemu_irq irq; |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 31 | } pl011_state; |
| 32 | |
| 33 | #define PL011_INT_TX 0x20 |
| 34 | #define PL011_INT_RX 0x10 |
| 35 | |
| 36 | #define PL011_FLAG_TXFE 0x80 |
| 37 | #define PL011_FLAG_RXFF 0x40 |
| 38 | #define PL011_FLAG_TXFF 0x20 |
| 39 | #define PL011_FLAG_RXFE 0x10 |
| 40 | |
| 41 | static const unsigned char pl011_id[] = |
| 42 | { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
| 43 | |
| 44 | static void pl011_update(pl011_state *s) |
| 45 | { |
| 46 | uint32_t flags; |
| 47 | |
| 48 | flags = s->int_level & s->int_enabled; |
pbrook | d537cf6 | 2007-04-07 18:14:41 +0000 | [diff] [blame] | 49 | qemu_set_irq(s->irq, flags != 0); |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | static uint32_t pl011_read(void *opaque, target_phys_addr_t offset) |
| 53 | { |
| 54 | pl011_state *s = (pl011_state *)opaque; |
| 55 | uint32_t c; |
| 56 | |
| 57 | offset -= s->base; |
| 58 | if (offset >= 0xfe0 && offset < 0x1000) { |
| 59 | return pl011_id[(offset - 0xfe0) >> 2]; |
| 60 | } |
| 61 | switch (offset >> 2) { |
| 62 | case 0: /* UARTDR */ |
| 63 | s->flags &= ~PL011_FLAG_RXFF; |
| 64 | c = s->read_fifo[s->read_pos]; |
| 65 | if (s->read_count > 0) { |
| 66 | s->read_count--; |
| 67 | if (++s->read_pos == 16) |
| 68 | s->read_pos = 0; |
| 69 | } |
| 70 | if (s->read_count == 0) { |
| 71 | s->flags |= PL011_FLAG_RXFE; |
| 72 | } |
| 73 | if (s->read_count == s->read_trigger - 1) |
| 74 | s->int_level &= ~ PL011_INT_RX; |
| 75 | pl011_update(s); |
| 76 | return c; |
| 77 | case 1: /* UARTCR */ |
| 78 | return 0; |
| 79 | case 6: /* UARTFR */ |
| 80 | return s->flags; |
| 81 | case 8: /* UARTILPR */ |
| 82 | return s->ilpr; |
| 83 | case 9: /* UARTIBRD */ |
| 84 | return s->ibrd; |
| 85 | case 10: /* UARTFBRD */ |
| 86 | return s->fbrd; |
| 87 | case 11: /* UARTLCR_H */ |
| 88 | return s->lcr; |
| 89 | case 12: /* UARTCR */ |
| 90 | return s->cr; |
| 91 | case 13: /* UARTIFLS */ |
| 92 | return s->ifl; |
| 93 | case 14: /* UARTIMSC */ |
| 94 | return s->int_enabled; |
| 95 | case 15: /* UARTRIS */ |
| 96 | return s->int_level; |
| 97 | case 16: /* UARTMIS */ |
| 98 | return s->int_level & s->int_enabled; |
| 99 | case 18: /* UARTDMACR */ |
| 100 | return s->dmacr; |
| 101 | default: |
| 102 | cpu_abort (cpu_single_env, "pl011_read: Bad offset %x\n", offset); |
| 103 | return 0; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | static void pl011_set_read_trigger(pl011_state *s) |
| 108 | { |
| 109 | #if 0 |
| 110 | /* The docs say the RX interrupt is triggered when the FIFO exceeds |
| 111 | the threshold. However linux only reads the FIFO in response to an |
| 112 | interrupt. Triggering the interrupt when the FIFO is non-empty seems |
| 113 | to make things work. */ |
| 114 | if (s->lcr & 0x10) |
| 115 | s->read_trigger = (s->ifl >> 1) & 0x1c; |
| 116 | else |
| 117 | #endif |
| 118 | s->read_trigger = 1; |
| 119 | } |
| 120 | |
| 121 | static void pl011_write(void *opaque, target_phys_addr_t offset, |
| 122 | uint32_t value) |
| 123 | { |
| 124 | pl011_state *s = (pl011_state *)opaque; |
| 125 | unsigned char ch; |
| 126 | |
| 127 | offset -= s->base; |
| 128 | switch (offset >> 2) { |
| 129 | case 0: /* UARTDR */ |
| 130 | /* ??? Check if transmitter is enabled. */ |
| 131 | ch = value; |
| 132 | if (s->chr) |
| 133 | qemu_chr_write(s->chr, &ch, 1); |
| 134 | s->int_level |= PL011_INT_TX; |
| 135 | pl011_update(s); |
| 136 | break; |
| 137 | case 1: /* UARTCR */ |
| 138 | s->cr = value; |
| 139 | break; |
| 140 | case 8: /* UARTUARTILPR */ |
| 141 | s->ilpr = value; |
| 142 | break; |
| 143 | case 9: /* UARTIBRD */ |
| 144 | s->ibrd = value; |
| 145 | break; |
| 146 | case 10: /* UARTFBRD */ |
| 147 | s->fbrd = value; |
| 148 | break; |
| 149 | case 11: /* UARTLCR_H */ |
| 150 | s->lcr = value; |
| 151 | pl011_set_read_trigger(s); |
| 152 | break; |
| 153 | case 12: /* UARTCR */ |
| 154 | /* ??? Need to implement the enable and loopback bits. */ |
| 155 | s->cr = value; |
| 156 | break; |
| 157 | case 13: /* UARTIFS */ |
| 158 | s->ifl = value; |
| 159 | pl011_set_read_trigger(s); |
| 160 | break; |
| 161 | case 14: /* UARTIMSC */ |
| 162 | s->int_enabled = value; |
| 163 | pl011_update(s); |
| 164 | break; |
| 165 | case 17: /* UARTICR */ |
| 166 | s->int_level &= ~value; |
| 167 | pl011_update(s); |
| 168 | break; |
| 169 | case 18: /* UARTDMACR */ |
| 170 | s->dmacr = value; |
| 171 | if (value & 3) |
| 172 | cpu_abort(cpu_single_env, "PL011: DMA not implemented\n"); |
| 173 | break; |
| 174 | default: |
| 175 | cpu_abort (cpu_single_env, "pl011_write: Bad offset %x\n", offset); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | static int pl011_can_recieve(void *opaque) |
| 180 | { |
| 181 | pl011_state *s = (pl011_state *)opaque; |
| 182 | |
| 183 | if (s->lcr & 0x10) |
| 184 | return s->read_count < 16; |
| 185 | else |
| 186 | return s->read_count < 1; |
| 187 | } |
| 188 | |
| 189 | static void pl011_recieve(void *opaque, const uint8_t *buf, int size) |
| 190 | { |
| 191 | pl011_state *s = (pl011_state *)opaque; |
| 192 | int slot; |
| 193 | |
| 194 | slot = s->read_pos + s->read_count; |
| 195 | if (slot >= 16) |
| 196 | slot -= 16; |
| 197 | s->read_fifo[slot] = *buf; |
| 198 | s->read_count++; |
| 199 | s->flags &= ~PL011_FLAG_RXFE; |
| 200 | if (s->cr & 0x10 || s->read_count == 16) { |
| 201 | s->flags |= PL011_FLAG_RXFF; |
| 202 | } |
| 203 | if (s->read_count == s->read_trigger) { |
| 204 | s->int_level |= PL011_INT_RX; |
| 205 | pl011_update(s); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | static void pl011_event(void *opaque, int event) |
| 210 | { |
| 211 | /* ??? Should probably implement break. */ |
| 212 | } |
| 213 | |
| 214 | static CPUReadMemoryFunc *pl011_readfn[] = { |
| 215 | pl011_read, |
| 216 | pl011_read, |
| 217 | pl011_read |
| 218 | }; |
| 219 | |
| 220 | static CPUWriteMemoryFunc *pl011_writefn[] = { |
| 221 | pl011_write, |
| 222 | pl011_write, |
| 223 | pl011_write |
| 224 | }; |
| 225 | |
pbrook | d537cf6 | 2007-04-07 18:14:41 +0000 | [diff] [blame] | 226 | void pl011_init(uint32_t base, qemu_irq irq, |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 227 | CharDriverState *chr) |
| 228 | { |
| 229 | int iomemtype; |
| 230 | pl011_state *s; |
| 231 | |
| 232 | s = (pl011_state *)qemu_mallocz(sizeof(pl011_state)); |
| 233 | iomemtype = cpu_register_io_memory(0, pl011_readfn, |
| 234 | pl011_writefn, s); |
pbrook | 187337f | 2007-06-03 15:19:33 +0000 | [diff] [blame] | 235 | cpu_register_physical_memory(base, 0x00001000, iomemtype); |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 236 | s->base = base; |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 237 | s->irq = irq; |
| 238 | s->chr = chr; |
| 239 | s->read_trigger = 1; |
| 240 | s->ifl = 0x12; |
| 241 | s->cr = 0x300; |
| 242 | s->flags = 0x90; |
| 243 | if (chr){ |
pbrook | e5b0bc4 | 2007-01-27 23:46:43 +0000 | [diff] [blame] | 244 | qemu_chr_add_handlers(chr, pl011_can_recieve, pl011_recieve, |
| 245 | pl011_event, s); |
pbrook | cdbdb64 | 2006-04-09 01:32:52 +0000 | [diff] [blame] | 246 | } |
| 247 | /* ??? Save/restore. */ |
| 248 | } |
| 249 | |