paul@0 | 1 | /* |
paul@0 | 2 | * Generate a VGA signal using a PIC32 microcontroller. |
paul@0 | 3 | * |
paul@0 | 4 | * Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk> |
paul@0 | 5 | * |
paul@0 | 6 | * This program is free software: you can redistribute it and/or modify |
paul@0 | 7 | * it under the terms of the GNU General Public License as published by |
paul@0 | 8 | * the Free Software Foundation, either version 3 of the License, or |
paul@0 | 9 | * (at your option) any later version. |
paul@0 | 10 | * |
paul@0 | 11 | * This program is distributed in the hope that it will be useful, |
paul@0 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@0 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@0 | 14 | * GNU General Public License for more details. |
paul@0 | 15 | * |
paul@0 | 16 | * You should have received a copy of the GNU General Public License |
paul@0 | 17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
paul@0 | 18 | */ |
paul@0 | 19 | |
paul@0 | 20 | #include "mips.h" |
paul@0 | 21 | #include "pic32.h" |
paul@35 | 22 | #include "vga.h" |
paul@0 | 23 | |
paul@0 | 24 | /* Disable JTAG functionality on pins. */ |
paul@0 | 25 | |
paul@0 | 26 | .section .devcfg0, "a" |
paul@0 | 27 | .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ |
paul@0 | 28 | |
paul@0 | 29 | /* |
paul@0 | 30 | Set the oscillator to be the FRC oscillator with PLL, with peripheral clock |
paul@21 | 31 | divided by 2, and FRCDIV+PLL selected. |
paul@0 | 32 | |
paul@0 | 33 | The watchdog timer (FWDTEN) is also disabled. |
paul@9 | 34 | |
paul@9 | 35 | The secondary oscillator pin (FSOSCEN) is disabled to avoid pin conflicts with |
paul@9 | 36 | RPB4. |
paul@0 | 37 | */ |
paul@0 | 38 | |
paul@0 | 39 | .section .devcfg1, "a" |
paul@21 | 40 | .word 0xff7fdfd9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<1:0> = 1; |
paul@9 | 41 | DEVCFG1<5> = FSOSCEN = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ |
paul@0 | 42 | |
paul@0 | 43 | /* |
paul@0 | 44 | Set the FRC oscillator PLL function with an input division of 4, an output |
paul@21 | 45 | division of 2, a multiplication of 24, yielding a multiplication of 3. |
paul@21 | 46 | |
paul@21 | 47 | The FRC is apparently at 16MHz and this produces a system clock of 48MHz. |
paul@0 | 48 | */ |
paul@0 | 49 | |
paul@0 | 50 | .section .devcfg2, "a" |
paul@21 | 51 | .word 0xfff9fffb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 001; |
paul@21 | 52 | DEVCFG2<6:4> = FPLLMUL<2:0> = 111; |
paul@9 | 53 | DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ |
paul@0 | 54 | |
paul@0 | 55 | .text |
paul@0 | 56 | .globl _start |
paul@36 | 57 | .extern init_framebuffer |
paul@35 | 58 | .extern init_framebuffer_with_pattern |
paul@41 | 59 | .extern screendata |
paul@41 | 60 | .extern blit_string |
paul@80 | 61 | .extern message0 |
paul@80 | 62 | .extern message1 |
paul@0 | 63 | |
paul@78 | 64 | .macro load_affected |
paul@78 | 65 | lw $v0, -4($k0) |
paul@78 | 66 | lw $v1, -8($k0) |
paul@78 | 67 | lw $s0, -12($k0) |
paul@78 | 68 | lw $s1, -16($k0) |
paul@78 | 69 | lw $s2, -20($k0) |
paul@78 | 70 | lw $s3, -24($k0) |
paul@78 | 71 | lw $t8, -28($k0) |
paul@78 | 72 | lw $ra, -32($k0) |
paul@78 | 73 | lw $sp, -36($k0) |
paul@81 | 74 | lw $gp, -40($k0) |
paul@78 | 75 | .endm |
paul@78 | 76 | |
paul@78 | 77 | .macro load_state |
paul@78 | 78 | lw $s0, -44($k0) |
paul@78 | 79 | lw $s1, -48($k0) |
paul@78 | 80 | lw $s2, -52($k0) |
paul@78 | 81 | lw $s3, -56($k0) |
paul@81 | 82 | lw $gp, -60($k0) |
paul@78 | 83 | .endm |
paul@78 | 84 | |
paul@78 | 85 | .macro save_affected |
paul@78 | 86 | sw $v0, -4($k0) |
paul@78 | 87 | sw $v1, -8($k0) |
paul@78 | 88 | sw $s0, -12($k0) |
paul@78 | 89 | sw $s1, -16($k0) |
paul@78 | 90 | sw $s2, -20($k0) |
paul@78 | 91 | sw $s3, -24($k0) |
paul@78 | 92 | sw $t8, -28($k0) |
paul@78 | 93 | sw $ra, -32($k0) |
paul@78 | 94 | sw $sp, -36($k0) |
paul@81 | 95 | sw $gp, -40($k0) |
paul@78 | 96 | .endm |
paul@78 | 97 | |
paul@78 | 98 | .macro save_state |
paul@78 | 99 | sw $s0, -44($k0) |
paul@78 | 100 | sw $s1, -48($k0) |
paul@78 | 101 | sw $s2, -52($k0) |
paul@78 | 102 | sw $s3, -56($k0) |
paul@81 | 103 | sw $gp, -60($k0) |
paul@78 | 104 | .endm |
paul@78 | 105 | |
paul@0 | 106 | _start: |
paul@0 | 107 | /* |
paul@0 | 108 | Configure RAM. |
paul@0 | 109 | See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization |
paul@0 | 110 | */ |
paul@0 | 111 | |
paul@0 | 112 | la $v0, BMXCON |
paul@10 | 113 | lw $v1, 0($v0) |
paul@48 | 114 | |
paul@48 | 115 | /* Set zero wait states for address setup. */ |
paul@48 | 116 | |
paul@10 | 117 | li $t8, ~(1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ |
paul@10 | 118 | and $v1, $v1, $t8 |
paul@48 | 119 | |
paul@48 | 120 | /* Set bus arbitration mode. */ |
paul@48 | 121 | |
paul@10 | 122 | li $t8, ~0b111 /* BMXCON<2:0> = BMXARB<2:0> = 0 */ |
paul@10 | 123 | ori $t8, $t8, 0b010 /* BMXCON<2:0> = BMXARB<2:0> = 2 */ |
paul@10 | 124 | and $v1, $v1, $t8 |
paul@10 | 125 | sw $v1, 0($v0) |
paul@0 | 126 | |
paul@0 | 127 | /* Enable caching. */ |
paul@0 | 128 | |
paul@14 | 129 | mfc0 $v1, CP0_CONFIG |
paul@14 | 130 | li $t8, ~CONFIG_K0 |
paul@14 | 131 | and $v1, $v1, $t8 |
paul@14 | 132 | ori $v1, $v1, CONFIG_K0_CACHABLE_NONCOHERENT |
paul@14 | 133 | mtc0 $v1, CP0_CONFIG |
paul@0 | 134 | nop |
paul@0 | 135 | |
paul@0 | 136 | /* Get the RAM size. */ |
paul@0 | 137 | |
paul@3 | 138 | la $v0, BMXDRMSZ |
paul@18 | 139 | lw $t0, 0($v0) |
paul@0 | 140 | |
paul@0 | 141 | /* Initialise the stack pointer. */ |
paul@0 | 142 | |
paul@3 | 143 | li $v1, KSEG0_BASE |
paul@18 | 144 | addu $sp, $t0, $v1 /* sp = KSEG0_BASE + RAM size */ |
paul@9 | 145 | |
paul@0 | 146 | /* Initialise the globals pointer. */ |
paul@0 | 147 | |
paul@0 | 148 | lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) |
paul@0 | 149 | ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) |
paul@0 | 150 | |
paul@5 | 151 | /* Set pins for output. */ |
paul@0 | 152 | |
paul@0 | 153 | jal init_pins |
paul@0 | 154 | nop |
paul@0 | 155 | |
paul@15 | 156 | la $t0, PORTA |
paul@15 | 157 | li $t1, (1 << 3) /* PORTA<3> = RA3 */ |
paul@15 | 158 | sw $t1, CLR($t0) |
paul@15 | 159 | |
paul@77 | 160 | jal init_io_pins |
paul@5 | 161 | nop |
paul@5 | 162 | |
paul@0 | 163 | /* Initialise the status register. */ |
paul@0 | 164 | |
paul@0 | 165 | jal init_interrupts |
paul@0 | 166 | nop |
paul@0 | 167 | |
paul@18 | 168 | /* Initialise framebuffer. */ |
paul@18 | 169 | |
paul@36 | 170 | la $a0, screendata |
paul@36 | 171 | jal init_framebuffer |
paul@18 | 172 | nop |
paul@18 | 173 | |
paul@18 | 174 | sync |
paul@18 | 175 | |
paul@0 | 176 | /* Initialise timer. */ |
paul@0 | 177 | |
paul@64 | 178 | jal init_timers |
paul@0 | 179 | nop |
paul@0 | 180 | |
paul@3 | 181 | /* Initialise DMA. */ |
paul@3 | 182 | |
paul@3 | 183 | jal init_dma |
paul@3 | 184 | nop |
paul@3 | 185 | |
paul@15 | 186 | /* Initialise OC1 and OC2. */ |
paul@5 | 187 | |
paul@9 | 188 | jal init_oc |
paul@3 | 189 | nop |
paul@3 | 190 | |
paul@77 | 191 | /* Initialise UART for debugging. */ |
paul@77 | 192 | |
paul@77 | 193 | jal init_uart |
paul@77 | 194 | nop |
paul@77 | 195 | |
paul@34 | 196 | /* Initialise the display state. */ |
paul@34 | 197 | |
paul@34 | 198 | li $s0, 0 /* line counter */ |
paul@34 | 199 | la $s1, vbp_active /* current event */ |
paul@38 | 200 | li $s2, SCREEN_BASE /* line address */ |
paul@38 | 201 | li $s3, SCREEN_BASE /* screen address */ |
paul@38 | 202 | |
paul@38 | 203 | /* Save the state for retrieval in the interrupt handler. */ |
paul@38 | 204 | |
paul@38 | 205 | li $k0, IRQ_STACK_LIMIT |
paul@78 | 206 | save_state |
paul@34 | 207 | |
paul@0 | 208 | /* Enable interrupts and loop. */ |
paul@0 | 209 | |
paul@0 | 210 | jal enable_interrupts |
paul@0 | 211 | nop |
paul@0 | 212 | |
paul@0 | 213 | jal handle_error_level |
paul@0 | 214 | nop |
paul@0 | 215 | |
paul@3 | 216 | /* Main program. */ |
paul@3 | 217 | |
paul@39 | 218 | li $a1, (3 << 24) /* counter ~= 50000000 */ |
paul@39 | 219 | li $a2, 0xffffff /* test counter at every 1/4 of range */ |
paul@39 | 220 | move $t2, $zero /* picture to show */ |
paul@3 | 221 | |
paul@3 | 222 | /* Monitoring loop. */ |
paul@0 | 223 | loop: |
paul@3 | 224 | addiu $a1, $a1, -1 /* counter -= 1 */ |
paul@39 | 225 | and $t1, $a2, $a1 |
paul@39 | 226 | bnez $t1, loop |
paul@0 | 227 | nop |
paul@0 | 228 | |
paul@15 | 229 | la $t0, PORTA |
paul@15 | 230 | li $t1, (1 << 3) /* PORTA<3> = RA3 */ |
paul@3 | 231 | sw $t1, INV($t0) |
paul@0 | 232 | |
paul@85 | 233 | la $v0, U1TXREG |
paul@77 | 234 | li $v1, '.' |
paul@85 | 235 | sw $v1, 0($v0) |
paul@77 | 236 | |
paul@39 | 237 | bnez $a1, loop /* until counter == 0 */ |
paul@39 | 238 | nop |
paul@39 | 239 | |
paul@39 | 240 | bnez $t2, _picture1 |
paul@39 | 241 | nop |
paul@39 | 242 | |
paul@39 | 243 | /* Show picture 0. */ |
paul@39 | 244 | |
paul@39 | 245 | la $a0, screendata |
paul@39 | 246 | jal init_framebuffer |
paul@39 | 247 | nop |
paul@41 | 248 | |
paul@41 | 249 | la $a0, message0 |
paul@41 | 250 | li $a1, SCREEN_BASE_KSEG0 |
paul@41 | 251 | jal blit_string |
paul@41 | 252 | nop |
paul@41 | 253 | |
paul@39 | 254 | li $t2, 1 |
paul@39 | 255 | j _next |
paul@39 | 256 | nop |
paul@39 | 257 | |
paul@39 | 258 | _picture1: |
paul@39 | 259 | /* Show picture 1. */ |
paul@39 | 260 | |
paul@39 | 261 | jal init_framebuffer_with_pattern |
paul@39 | 262 | nop |
paul@41 | 263 | |
paul@41 | 264 | la $a0, message1 |
paul@41 | 265 | li $a1, SCREEN_BASE_KSEG0 |
paul@41 | 266 | jal blit_string |
paul@41 | 267 | nop |
paul@41 | 268 | |
paul@39 | 269 | move $t2, $zero |
paul@39 | 270 | |
paul@0 | 271 | _next: |
paul@39 | 272 | li $a1, (3 << 24) /* counter ~= 50000000 */ |
paul@39 | 273 | li $a2, 0xffffff /* test counter at every 1/4 of range */ |
paul@0 | 274 | j loop |
paul@0 | 275 | nop |
paul@0 | 276 | |
paul@0 | 277 | |
paul@0 | 278 | |
paul@0 | 279 | init_pins: |
paul@1 | 280 | /* DEVCFG0<2> needs setting to 0 before the program is run. */ |
paul@0 | 281 | |
paul@3 | 282 | la $v0, CFGCON |
paul@0 | 283 | li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ |
paul@0 | 284 | sw $v1, CLR($v0) |
paul@0 | 285 | |
paul@3 | 286 | init_outputs: |
paul@3 | 287 | /* Remove analogue features from pins. */ |
paul@3 | 288 | |
paul@3 | 289 | la $v0, ANSELA |
paul@3 | 290 | sw $zero, 0($v0) /* ANSELA = 0 */ |
paul@3 | 291 | la $v0, ANSELB |
paul@3 | 292 | sw $zero, 0($v0) /* ANSELB = 0 */ |
paul@3 | 293 | |
paul@3 | 294 | la $v0, TRISA |
paul@3 | 295 | sw $zero, 0($v0) |
paul@0 | 296 | la $v0, TRISB |
paul@3 | 297 | sw $zero, 0($v0) |
paul@3 | 298 | |
paul@9 | 299 | la $v0, PORTA |
paul@9 | 300 | sw $zero, 0($v0) |
paul@9 | 301 | la $v0, PORTB |
paul@9 | 302 | sw $zero, 0($v0) |
paul@0 | 303 | |
paul@3 | 304 | jr $ra |
paul@0 | 305 | nop |
paul@0 | 306 | |
paul@0 | 307 | |
paul@0 | 308 | |
paul@85 | 309 | /* |
paul@85 | 310 | Timer initialisation. |
paul@85 | 311 | |
paul@85 | 312 | Timer2 is used to drive the output compare and DMA peripherals. |
paul@85 | 313 | */ |
paul@0 | 314 | |
paul@64 | 315 | init_timers: |
paul@0 | 316 | |
paul@49 | 317 | /* Initialise Timer2 for sync pulses. */ |
paul@0 | 318 | |
paul@4 | 319 | la $v0, T2CON |
paul@4 | 320 | sw $zero, 0($v0) /* T2CON = 0 */ |
paul@0 | 321 | nop |
paul@0 | 322 | |
paul@4 | 323 | la $v0, TMR2 |
paul@4 | 324 | sw $zero, 0($v0) /* TMR2 = 0 */ |
paul@5 | 325 | |
paul@4 | 326 | la $v0, PR2 |
paul@0 | 327 | li $v1, HFREQ_LIMIT |
paul@4 | 328 | sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ |
paul@0 | 329 | |
paul@0 | 330 | /* Start timer. */ |
paul@0 | 331 | |
paul@4 | 332 | la $v0, T2CON |
paul@5 | 333 | li $v1, (1 << 15) |
paul@5 | 334 | sw $v1, SET($v0) /* ON = 1 */ |
paul@5 | 335 | |
paul@64 | 336 | /* Initialise Timer3 for line DMA cell transfer. */ |
paul@64 | 337 | |
paul@64 | 338 | la $v0, T3CON |
paul@64 | 339 | sw $zero, 0($v0) /* T3CON = 0 */ |
paul@64 | 340 | nop |
paul@64 | 341 | |
paul@64 | 342 | la $v0, TMR3 |
paul@64 | 343 | sw $zero, 0($v0) /* TMR3 = 0 */ |
paul@64 | 344 | |
paul@64 | 345 | la $v0, PR3 |
paul@64 | 346 | li $v1, 1 |
paul@64 | 347 | sw $v1, 0($v0) /* PR3 = 1 */ |
paul@64 | 348 | |
paul@84 | 349 | /* Initialise Timer3 interrupt. */ |
paul@84 | 350 | |
paul@84 | 351 | la $v0, IFS0 |
paul@84 | 352 | li $v1, (1 << 14) |
paul@84 | 353 | sw $v1, CLR($v0) /* T3IF = 0 */ |
paul@84 | 354 | |
paul@84 | 355 | la $v0, IPC3 |
paul@84 | 356 | li $v1, 0b11111 |
paul@84 | 357 | sw $v1, CLR($v0) /* T3IP, T3IS = 0 */ |
paul@84 | 358 | li $v1, 0b00111 |
paul@84 | 359 | sw $v1, SET($v0) /* T3IP = 1; T3IS = 3 */ |
paul@84 | 360 | |
paul@84 | 361 | la $v0, IEC0 |
paul@84 | 362 | li $v1, (1 << 14) |
paul@84 | 363 | sw $v1, SET($v0) /* T3IE = 1 */ |
paul@84 | 364 | |
paul@64 | 365 | /* Start timer. */ |
paul@64 | 366 | |
paul@64 | 367 | la $v0, T3CON |
paul@64 | 368 | li $v1, (1 << 15) |
paul@64 | 369 | sw $v1, SET($v0) /* ON = 1 */ |
paul@64 | 370 | |
paul@5 | 371 | jr $ra |
paul@5 | 372 | nop |
paul@5 | 373 | |
paul@5 | 374 | |
paul@5 | 375 | |
paul@5 | 376 | /* |
paul@5 | 377 | Output compare initialisation. |
paul@5 | 378 | |
paul@15 | 379 | Timer2 will be used to trigger two events using OC1: one initiating the hsync |
paul@9 | 380 | pulse, and one terminating the pulse. The pulse should appear after the line |
paul@9 | 381 | data has been transferred using DMA, but this is achieved by just choosing |
paul@9 | 382 | suitable start and end values. |
paul@5 | 383 | |
paul@49 | 384 | Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to |
paul@48 | 385 | reverse the level at a later point. In this way, the vsync pulse is generated |
paul@48 | 386 | and is synchronised to the display lines. |
paul@85 | 387 | |
paul@85 | 388 | The OC1 interrupt is used to update the state machine handling the display, |
paul@85 | 389 | also invoking the address update routine at the end of each visible display |
paul@85 | 390 | line, changing the source address of the DMA channel. |
paul@5 | 391 | */ |
paul@5 | 392 | |
paul@9 | 393 | init_oc: |
paul@15 | 394 | /* Disable OC1 interrupts. */ |
paul@9 | 395 | |
paul@85 | 396 | li $v1, (1 << 7) |
paul@9 | 397 | |
paul@85 | 398 | la $v0, IEC0 |
paul@85 | 399 | sw $v1, CLR($v0) /* IEC0<7> = OC1IE = 0 */ |
paul@9 | 400 | la $v0, IFS0 |
paul@85 | 401 | sw $v1, CLR($v0) /* IFS0<7> = OC1IF = 0 */ |
paul@9 | 402 | |
paul@15 | 403 | /* Initialise OC1. */ |
paul@9 | 404 | |
paul@15 | 405 | la $v0, OC1CON |
paul@15 | 406 | li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ |
paul@9 | 407 | sw $v1, 0($v0) |
paul@9 | 408 | |
paul@9 | 409 | /* Pulse start and end. */ |
paul@9 | 410 | |
paul@15 | 411 | la $v0, OC1R |
paul@9 | 412 | li $v1, HSYNC_END /* HSYNC_START for positive polarity */ |
paul@9 | 413 | sw $v1, 0($v0) |
paul@9 | 414 | |
paul@15 | 415 | la $v0, OC1RS |
paul@9 | 416 | li $v1, HSYNC_START /* HSYNC_END for positive polarity */ |
paul@9 | 417 | sw $v1, 0($v0) |
paul@9 | 418 | |
paul@85 | 419 | /* Enable interrupt for address updating. */ |
paul@85 | 420 | |
paul@85 | 421 | la $v0, IPC1 |
paul@85 | 422 | li $v1, (0b11111 << 16) |
paul@85 | 423 | sw $v1, SET($v0) /* OC1IP = 7; OC1IS = 3 */ |
paul@85 | 424 | |
paul@85 | 425 | la $v0, IEC0 |
paul@85 | 426 | li $v1, (1 << 7) |
paul@85 | 427 | sw $v1, SET($v0) /* IEC0<7> = OC1IE = 1 */ |
paul@85 | 428 | |
paul@15 | 429 | /* OC1 is enabled. */ |
paul@9 | 430 | |
paul@15 | 431 | la $v0, OC1CON |
paul@9 | 432 | li $v1, (1 << 15) |
paul@9 | 433 | sw $v1, SET($v0) |
paul@9 | 434 | |
paul@9 | 435 | /* Disable OC2 interrupts. */ |
paul@5 | 436 | |
paul@85 | 437 | li $v1, (1 << 12) |
paul@5 | 438 | |
paul@85 | 439 | la $v0, IEC0 |
paul@85 | 440 | sw $v1, CLR($v0) /* IEC0<12> = OC2IE = 0 */ |
paul@5 | 441 | la $v0, IFS0 |
paul@85 | 442 | sw $v1, CLR($v0) /* IFS0<12> = OC2IF = 0 */ |
paul@5 | 443 | |
paul@5 | 444 | /* Initialise OC2. */ |
paul@5 | 445 | |
paul@5 | 446 | la $v0, OC2CON |
paul@9 | 447 | li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ |
paul@5 | 448 | sw $v1, 0($v0) |
paul@5 | 449 | |
paul@9 | 450 | /* Set pulse position. */ |
paul@5 | 451 | |
paul@5 | 452 | la $v0, OC2R |
paul@9 | 453 | sw $zero, 0($v0) |
paul@5 | 454 | |
paul@9 | 455 | /* Enable OC2 later. */ |
paul@5 | 456 | |
paul@5 | 457 | jr $ra |
paul@5 | 458 | nop |
paul@5 | 459 | |
paul@77 | 460 | init_io_pins: |
paul@5 | 461 | /* Unlock the configuration register bits. */ |
paul@5 | 462 | |
paul@5 | 463 | la $v0, SYSKEY |
paul@5 | 464 | sw $zero, 0($v0) |
paul@5 | 465 | li $v1, 0xAA996655 |
paul@5 | 466 | sw $v1, 0($v0) |
paul@5 | 467 | li $v1, 0x556699AA |
paul@5 | 468 | sw $v1, 0($v0) |
paul@5 | 469 | |
paul@5 | 470 | la $v0, CFGCON |
paul@5 | 471 | lw $t8, 0($v0) |
paul@5 | 472 | li $v1, (1 << 13) /* IOLOCK = 0 */ |
paul@5 | 473 | sw $v1, CLR($v0) |
paul@5 | 474 | |
paul@15 | 475 | /* Map OC1 to RPA0. */ |
paul@9 | 476 | |
paul@15 | 477 | la $v0, RPA0R |
paul@15 | 478 | li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ |
paul@9 | 479 | sw $v1, 0($v0) |
paul@9 | 480 | |
paul@15 | 481 | /* Map OC2 to RPA1. */ |
paul@5 | 482 | |
paul@15 | 483 | la $v0, RPA1R |
paul@15 | 484 | li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ |
paul@5 | 485 | sw $v1, 0($v0) |
paul@5 | 486 | |
paul@77 | 487 | /* Map U1TX to RPB15. */ |
paul@77 | 488 | |
paul@77 | 489 | la $v0, RPB15R |
paul@77 | 490 | li $v1, 0b0001 /* RPB15R<3:0> = 0001 (U1TX) */ |
paul@77 | 491 | sw $v1, 0($v0) |
paul@77 | 492 | |
paul@5 | 493 | la $v0, CFGCON |
paul@5 | 494 | sw $t8, 0($v0) |
paul@5 | 495 | |
paul@5 | 496 | /* Lock the oscillator control register again. */ |
paul@5 | 497 | |
paul@5 | 498 | la $v0, SYSKEY |
paul@5 | 499 | li $v1, 0x33333333 |
paul@5 | 500 | sw $v1, 0($v0) |
paul@0 | 501 | |
paul@0 | 502 | jr $ra |
paul@0 | 503 | nop |
paul@1 | 504 | |
paul@1 | 505 | |
paul@1 | 506 | |
paul@5 | 507 | /* |
paul@5 | 508 | Direct Memory Access initialisation. |
paul@3 | 509 | |
paul@15 | 510 | Write 160 pixels to PORTB for the line data. This is initiated by a timer |
paul@85 | 511 | interrupt. |
paul@3 | 512 | */ |
paul@3 | 513 | |
paul@3 | 514 | init_dma: |
paul@3 | 515 | /* Disable DMA interrupts. */ |
paul@1 | 516 | |
paul@3 | 517 | la $v0, IEC1 |
paul@59 | 518 | li $v1, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ |
paul@3 | 519 | sw $v1, CLR($v0) |
paul@3 | 520 | |
paul@3 | 521 | /* Clear DMA interrupt flags. */ |
paul@1 | 522 | |
paul@3 | 523 | la $v0, IFS1 |
paul@59 | 524 | li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ |
paul@3 | 525 | sw $v1, CLR($v0) |
paul@3 | 526 | |
paul@3 | 527 | /* Enable DMA. */ |
paul@3 | 528 | |
paul@3 | 529 | la $v0, DMACON |
paul@3 | 530 | li $v1, (1 << 15) |
paul@1 | 531 | sw $v1, SET($v0) |
paul@1 | 532 | |
paul@3 | 533 | /* |
paul@5 | 534 | Initialise a line channel. |
paul@5 | 535 | The line channel will be channel 0 (x = 0). |
paul@3 | 536 | |
paul@3 | 537 | Specify a priority of 3: |
paul@3 | 538 | DCHxCON<1:0> = CHPRI<1:0> = 3 |
paul@3 | 539 | |
paul@15 | 540 | Auto-enable the channel: |
paul@3 | 541 | DCHxCON<4> = CHAEN = 1 |
paul@3 | 542 | */ |
paul@3 | 543 | |
paul@3 | 544 | la $v0, DCH0CON |
paul@3 | 545 | li $v1, 0b10011 |
paul@3 | 546 | sw $v1, 0($v0) |
paul@3 | 547 | |
paul@5 | 548 | /* |
paul@15 | 549 | Initialise a level reset channel. |
paul@15 | 550 | The reset channel will be channel 1 (x = 1). |
paul@15 | 551 | |
paul@15 | 552 | Specify a priority of 3: |
paul@15 | 553 | DCHxCON<1:0> = CHPRI<1:0> = 3 |
paul@15 | 554 | |
paul@15 | 555 | Chain the channel to channel 0: |
paul@15 | 556 | DCHxCON<5> = CHCHN = 1 |
paul@15 | 557 | |
paul@15 | 558 | Allow the channel to receive events when disabled: |
paul@15 | 559 | DCHxCON<6> = CHAED = 1 |
paul@15 | 560 | */ |
paul@15 | 561 | |
paul@15 | 562 | la $v0, DCH1CON |
paul@15 | 563 | li $v1, 0b1100011 |
paul@15 | 564 | sw $v1, 0($v0) |
paul@15 | 565 | |
paul@59 | 566 | la $v0, DCH2CON |
paul@59 | 567 | li $v1, 0b1100011 |
paul@59 | 568 | sw $v1, 0($v0) |
paul@59 | 569 | |
paul@15 | 570 | /* |
paul@5 | 571 | Initiate channel transfers when the initiating interrupt condition |
paul@5 | 572 | occurs: |
paul@9 | 573 | DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt |
paul@11 | 574 | DCHxECON<4> = SIRQEN = 1 |
paul@20 | 575 | |
paul@20 | 576 | For now, however, prevent initiation by not setting SIRQEN. |
paul@5 | 577 | */ |
paul@3 | 578 | |
paul@3 | 579 | la $v0, DCH0ECON |
paul@20 | 580 | li $v1, (9 << 8) |
paul@3 | 581 | sw $v1, 0($v0) |
paul@1 | 582 | |
paul@3 | 583 | /* |
paul@64 | 584 | Initiate reset channel transfer when channel 0 is finished; |
paul@64 | 585 | timer 3 causes cell transfers: |
paul@64 | 586 | DCHxECON<15:8> = CHSIRQ<7:0> = timer 3 interrupt |
paul@15 | 587 | DCHxECON<4> = SIRQEN = 1 |
paul@15 | 588 | */ |
paul@15 | 589 | |
paul@15 | 590 | la $v0, DCH1ECON |
paul@64 | 591 | li $v1, (14 << 8) | (1 << 4) |
paul@15 | 592 | sw $v1, 0($v0) |
paul@15 | 593 | |
paul@59 | 594 | la $v0, DCH2ECON |
paul@64 | 595 | li $v1, (14 << 8) | (1 << 4) |
paul@15 | 596 | sw $v1, 0($v0) |
paul@15 | 597 | |
paul@15 | 598 | /* |
paul@15 | 599 | The line channel has a cell size of the number bytes in a line: |
paul@9 | 600 | DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH |
paul@3 | 601 | */ |
paul@3 | 602 | |
paul@3 | 603 | la $v0, DCH0CSIZ |
paul@9 | 604 | li $v1, LINE_LENGTH |
paul@3 | 605 | sw $v1, 0($v0) |
paul@1 | 606 | |
paul@3 | 607 | /* |
paul@15 | 608 | The reset channel has a cell size of a single zero byte: |
paul@49 | 609 | DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 |
paul@15 | 610 | */ |
paul@15 | 611 | |
paul@15 | 612 | la $v0, DCH1CSIZ |
paul@15 | 613 | li $v1, 1 |
paul@15 | 614 | sw $v1, 0($v0) |
paul@15 | 615 | |
paul@59 | 616 | la $v0, DCH2CSIZ |
paul@59 | 617 | sw $v1, 0($v0) |
paul@59 | 618 | |
paul@15 | 619 | /* |
paul@11 | 620 | The source has a size identical to the cell size: |
paul@49 | 621 | DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 |
paul@3 | 622 | */ |
paul@3 | 623 | |
paul@3 | 624 | la $v0, DCH0SSIZ |
paul@9 | 625 | li $v1, LINE_LENGTH |
paul@3 | 626 | sw $v1, 0($v0) |
paul@3 | 627 | |
paul@15 | 628 | la $v0, DCH1SSIZ |
paul@15 | 629 | li $v1, 1 |
paul@15 | 630 | sw $v1, 0($v0) |
paul@15 | 631 | |
paul@59 | 632 | la $v0, DCH2SSIZ |
paul@59 | 633 | sw $v1, 0($v0) |
paul@59 | 634 | |
paul@3 | 635 | /* |
paul@5 | 636 | The source address is the physical address of the line data: |
paul@11 | 637 | DCHxSSA = physical(line data address) |
paul@3 | 638 | */ |
paul@1 | 639 | |
paul@3 | 640 | la $v0, DCH0SSA |
paul@38 | 641 | li $v1, SCREEN_BASE |
paul@38 | 642 | sw $v1, 0($v0) |
paul@3 | 643 | |
paul@3 | 644 | /* |
paul@15 | 645 | For the reset channel, a single byte of zero is transferred: |
paul@15 | 646 | DCHxSSA = physical(zero data address) |
paul@15 | 647 | */ |
paul@15 | 648 | |
paul@15 | 649 | la $v0, DCH1SSA |
paul@59 | 650 | la $v1, fulldata |
paul@59 | 651 | li $t8, KSEG0_BASE |
paul@59 | 652 | subu $v1, $v1, $t8 |
paul@59 | 653 | sw $v1, 0($v0) |
paul@59 | 654 | |
paul@59 | 655 | la $v0, DCH2SSA |
paul@15 | 656 | la $v1, zerodata |
paul@15 | 657 | li $t8, KSEG0_BASE |
paul@15 | 658 | subu $v1, $v1, $t8 |
paul@15 | 659 | sw $v1, 0($v0) |
paul@15 | 660 | |
paul@15 | 661 | /* |
paul@11 | 662 | The destination has a size of 1 byte: |
paul@3 | 663 | DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 |
paul@3 | 664 | */ |
paul@3 | 665 | |
paul@71 | 666 | li $v1, 1 |
paul@71 | 667 | |
paul@3 | 668 | la $v0, DCH0DSIZ |
paul@3 | 669 | sw $v1, 0($v0) |
paul@3 | 670 | |
paul@15 | 671 | la $v0, DCH1DSIZ |
paul@15 | 672 | sw $v1, 0($v0) |
paul@15 | 673 | |
paul@59 | 674 | la $v0, DCH2DSIZ |
paul@59 | 675 | sw $v1, 0($v0) |
paul@59 | 676 | |
paul@3 | 677 | /* |
paul@15 | 678 | The destination address is the physical address of PORTB: |
paul@15 | 679 | DCHxDSA = physical(PORTB) |
paul@3 | 680 | */ |
paul@3 | 681 | |
paul@15 | 682 | li $v1, PORTB |
paul@3 | 683 | li $t8, KSEG1_BASE |
paul@3 | 684 | subu $v1, $v1, $t8 |
paul@71 | 685 | |
paul@71 | 686 | la $v0, DCH0DSA |
paul@3 | 687 | sw $v1, 0($v0) |
paul@3 | 688 | |
paul@15 | 689 | la $v0, DCH1DSA |
paul@15 | 690 | sw $v1, 0($v0) |
paul@15 | 691 | |
paul@59 | 692 | la $v0, DCH2DSA |
paul@59 | 693 | sw $v1, 0($v0) |
paul@59 | 694 | |
paul@85 | 695 | /* Enable interrupt for channel chaining. */ |
paul@7 | 696 | |
paul@3 | 697 | la $v0, DCH0INT |
paul@7 | 698 | li $v1, (1 << 19) /* CHBCIE = 1 */ |
paul@7 | 699 | sw $v1, 0($v0) |
paul@7 | 700 | |
paul@7 | 701 | la $v0, IPC10 |
paul@7 | 702 | li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ |
paul@7 | 703 | sw $v1, CLR($v0) |
paul@85 | 704 | li $v1, 0b10011 /* DMA0IP = 4, DMA0IS = 3 */ |
paul@7 | 705 | sw $v1, SET($v0) |
paul@7 | 706 | |
paul@7 | 707 | la $v0, IEC1 |
paul@7 | 708 | li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ |
paul@9 | 709 | sw $v1, SET($v0) |
paul@1 | 710 | |
paul@15 | 711 | /* Enable line channel. */ |
paul@3 | 712 | |
paul@3 | 713 | la $v0, DCH0CON |
paul@3 | 714 | li $v1, 0b10000000 |
paul@3 | 715 | sw $v1, SET($v0) |
paul@3 | 716 | |
paul@1 | 717 | jr $ra |
paul@1 | 718 | nop |
paul@1 | 719 | |
paul@15 | 720 | zerodata: |
paul@15 | 721 | .word 0 |
paul@15 | 722 | |
paul@59 | 723 | fulldata: |
paul@59 | 724 | .word 255 |
paul@59 | 725 | |
paul@1 | 726 | |
paul@1 | 727 | |
paul@85 | 728 | /* |
paul@85 | 729 | UART initialisation. |
paul@85 | 730 | |
paul@85 | 731 | Initialise UART transmission at 115200 baud. This is sensitive to the peripheral |
paul@85 | 732 | clock frequency. |
paul@85 | 733 | */ |
paul@77 | 734 | |
paul@77 | 735 | init_uart: |
paul@77 | 736 | /* Initialise UART. */ |
paul@77 | 737 | |
paul@77 | 738 | la $v0, U1BRG |
paul@77 | 739 | li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ |
paul@77 | 740 | sw $v1, 0($v0) |
paul@77 | 741 | |
paul@77 | 742 | la $v0, U1MODE |
paul@77 | 743 | li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ |
paul@77 | 744 | sw $v1, CLR($v0) |
paul@77 | 745 | |
paul@77 | 746 | /* Start UART. */ |
paul@77 | 747 | |
paul@77 | 748 | la $v0, U1STA |
paul@77 | 749 | li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ |
paul@77 | 750 | sw $v1, SET($v0) |
paul@77 | 751 | |
paul@77 | 752 | la $v0, U1MODE |
paul@77 | 753 | li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ |
paul@77 | 754 | sw $v1, SET($v0) |
paul@77 | 755 | |
paul@77 | 756 | jr $ra |
paul@77 | 757 | nop |
paul@77 | 758 | |
paul@77 | 759 | |
paul@77 | 760 | |
paul@9 | 761 | /* Utilities. */ |
paul@9 | 762 | |
paul@9 | 763 | handle_error_level: |
paul@9 | 764 | mfc0 $t3, CP0_STATUS |
paul@9 | 765 | li $t4, ~(STATUS_ERL | STATUS_EXL) |
paul@9 | 766 | and $t3, $t3, $t4 |
paul@9 | 767 | mtc0 $t3, CP0_STATUS |
paul@9 | 768 | jr $ra |
paul@9 | 769 | nop |
paul@9 | 770 | |
paul@9 | 771 | enable_interrupts: |
paul@9 | 772 | mfc0 $t3, CP0_STATUS |
paul@9 | 773 | li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ |
paul@9 | 774 | and $t3, $t3, $t4 |
paul@69 | 775 | ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) |
paul@9 | 776 | li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ |
paul@9 | 777 | and $t3, $t3, $t4 |
paul@9 | 778 | ori $t3, $t3, STATUS_IE |
paul@9 | 779 | mtc0 $t3, CP0_STATUS |
paul@9 | 780 | jr $ra |
paul@9 | 781 | nop |
paul@9 | 782 | |
paul@9 | 783 | init_interrupts: |
paul@9 | 784 | mfc0 $t3, CP0_DEBUG |
paul@9 | 785 | li $t4, ~DEBUG_DM |
paul@9 | 786 | and $t3, $t3, $t4 |
paul@9 | 787 | mtc0 $t3, CP0_DEBUG |
paul@9 | 788 | |
paul@9 | 789 | mfc0 $t3, CP0_STATUS |
paul@11 | 790 | li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ |
paul@9 | 791 | or $t3, $t3, $t4 |
paul@9 | 792 | mtc0 $t3, CP0_STATUS |
paul@9 | 793 | |
paul@9 | 794 | la $t3, exception_handler |
paul@9 | 795 | mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ |
paul@9 | 796 | |
paul@9 | 797 | li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ |
paul@9 | 798 | mtc0 $t3, CP0_INTCTL |
paul@9 | 799 | |
paul@9 | 800 | li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ |
paul@9 | 801 | mtc0 $t3, CP0_CAUSE |
paul@9 | 802 | |
paul@9 | 803 | jr $ra |
paul@9 | 804 | nop |
paul@9 | 805 | |
paul@9 | 806 | |
paul@9 | 807 | |
paul@9 | 808 | /* Exception servicing. */ |
paul@9 | 809 | |
paul@9 | 810 | .section .flash, "a" |
paul@9 | 811 | |
paul@33 | 812 | /* TLB error servicing. */ |
paul@33 | 813 | |
paul@33 | 814 | tlb_handler: |
paul@9 | 815 | j exception_handler |
paul@9 | 816 | nop |
paul@9 | 817 | |
paul@9 | 818 | |
paul@9 | 819 | |
paul@33 | 820 | /* General exception servicing. */ |
paul@33 | 821 | |
paul@33 | 822 | .org 0x180 |
paul@33 | 823 | |
paul@33 | 824 | exception_handler: |
paul@33 | 825 | j exc_handler |
paul@33 | 826 | nop |
paul@33 | 827 | |
paul@33 | 828 | |
paul@45 | 829 | |
paul@9 | 830 | /* Interrupt servicing. */ |
paul@9 | 831 | |
paul@9 | 832 | .org 0x200 |
paul@9 | 833 | |
paul@9 | 834 | interrupt_handler: |
paul@9 | 835 | |
paul@78 | 836 | /* |
paul@78 | 837 | Save affected registers, restoring IRQ state and switching to the IRQ |
paul@78 | 838 | stack. |
paul@78 | 839 | */ |
paul@38 | 840 | |
paul@38 | 841 | li $k0, IRQ_STACK_LIMIT |
paul@78 | 842 | save_affected |
paul@78 | 843 | load_state |
paul@38 | 844 | li $sp, IRQ_STACK_TOP |
paul@38 | 845 | |
paul@85 | 846 | /* Check for the output compare interrupt condition. */ |
paul@9 | 847 | |
paul@75 | 848 | la $v0, IFS0 |
paul@75 | 849 | lw $v1, 0($v0) |
paul@85 | 850 | andi $v1, $v1, (1 << 7) /* OC1IF */ |
paul@85 | 851 | beqz $v1, irq_dma |
paul@9 | 852 | nop |
paul@9 | 853 | |
paul@75 | 854 | irq_handle: |
paul@75 | 855 | /* Clear the interrupt condition. */ |
paul@9 | 856 | |
paul@9 | 857 | sw $v1, CLR($v0) |
paul@9 | 858 | |
paul@75 | 859 | /* Increment the line counter. */ |
paul@47 | 860 | |
paul@47 | 861 | addiu $s0, $s0, 1 |
paul@47 | 862 | |
paul@75 | 863 | /* Jump to the event handler. */ |
paul@47 | 864 | |
paul@47 | 865 | jalr $s1 |
paul@47 | 866 | nop |
paul@47 | 867 | |
paul@85 | 868 | irq_dma: |
paul@85 | 869 | /* Clear the DMA channel completion condition. */ |
paul@85 | 870 | |
paul@85 | 871 | la $v0, IFS1 |
paul@85 | 872 | lw $v1, 0($v0) |
paul@85 | 873 | li $t8, (1 << 28) |
paul@85 | 874 | and $v1, $v1, $t8 |
paul@85 | 875 | beqz $v1, irq_exit |
paul@85 | 876 | nop |
paul@85 | 877 | |
paul@85 | 878 | sw $v1, CLR($v0) /* IFS1<28> = DMA0IF = 0 */ |
paul@85 | 879 | |
paul@85 | 880 | la $v0, DCH0INT |
paul@85 | 881 | lw $v1, 0($v0) |
paul@85 | 882 | andi $v1, $v1, (1 << 3) |
paul@85 | 883 | beqz $v1, irq_exit |
paul@85 | 884 | nop |
paul@85 | 885 | |
paul@85 | 886 | sw $v1, CLR($v0) /* CHBCIF = 0 */ |
paul@85 | 887 | |
paul@9 | 888 | irq_exit: |
paul@78 | 889 | /* |
paul@78 | 890 | Save IRQ state and restore the affected registers, switching back to the |
paul@78 | 891 | original stack. |
paul@78 | 892 | */ |
paul@38 | 893 | |
paul@38 | 894 | li $k0, IRQ_STACK_LIMIT |
paul@78 | 895 | save_state |
paul@78 | 896 | load_affected |
paul@38 | 897 | |
paul@9 | 898 | eret |
paul@9 | 899 | nop |
paul@9 | 900 | |
paul@9 | 901 | |
paul@9 | 902 | |
paul@9 | 903 | /* Event routines. */ |
paul@9 | 904 | |
paul@9 | 905 | /* The vertical back porch. */ |
paul@9 | 906 | |
paul@9 | 907 | vbp_active: |
paul@9 | 908 | /* Test for visible region. */ |
paul@9 | 909 | |
paul@9 | 910 | sltiu $v0, $s0, VISIBLE_START |
paul@9 | 911 | bnez $v0, _vbp_active_ret |
paul@9 | 912 | nop |
paul@9 | 913 | |
paul@9 | 914 | /* Start the visible region. */ |
paul@9 | 915 | |
paul@9 | 916 | la $s1, visible_active |
paul@9 | 917 | |
paul@17 | 918 | /* Reset the line address. */ |
paul@17 | 919 | |
paul@17 | 920 | move $s2, $s3 |
paul@17 | 921 | |
paul@43 | 922 | /* Update the source address. */ |
paul@43 | 923 | |
paul@43 | 924 | la $v0, DCH0SSA |
paul@43 | 925 | sw $s2, 0($v0) |
paul@43 | 926 | |
paul@71 | 927 | /* Enable the line channel for timer event transfer initiation. */ |
paul@71 | 928 | |
paul@71 | 929 | la $v0, DCH0ECON |
paul@71 | 930 | li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ |
paul@71 | 931 | sw $v1, SET($v0) |
paul@71 | 932 | |
paul@9 | 933 | _vbp_active_ret: |
paul@9 | 934 | jr $ra |
paul@9 | 935 | nop |
paul@9 | 936 | |
paul@9 | 937 | |
paul@9 | 938 | |
paul@9 | 939 | /* The visible region. */ |
paul@9 | 940 | |
paul@9 | 941 | visible_active: |
paul@9 | 942 | /* Test for front porch. */ |
paul@9 | 943 | |
paul@9 | 944 | sltiu $v0, $s0, VFP_START |
paul@75 | 945 | bnez $v0, visible_update_address |
paul@9 | 946 | nop |
paul@9 | 947 | |
paul@9 | 948 | /* Start the front porch region. */ |
paul@9 | 949 | |
paul@9 | 950 | la $s1, vfp_active |
paul@9 | 951 | |
paul@75 | 952 | /* Disable the line channel. */ |
paul@75 | 953 | |
paul@75 | 954 | la $v0, DCH0ECON |
paul@75 | 955 | li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ |
paul@75 | 956 | sw $v1, CLR($v0) |
paul@75 | 957 | |
paul@9 | 958 | _visible_active_ret: |
paul@9 | 959 | jr $ra |
paul@9 | 960 | nop |
paul@9 | 961 | |
paul@9 | 962 | |
paul@9 | 963 | |
paul@45 | 964 | /* DMA update routine. */ |
paul@45 | 965 | |
paul@45 | 966 | visible_update_address: |
paul@45 | 967 | |
paul@45 | 968 | /* |
paul@45 | 969 | Update the line data address if the line counter (referring to the |
paul@45 | 970 | next line) is even. |
paul@45 | 971 | */ |
paul@45 | 972 | |
paul@45 | 973 | andi $t8, $s0, 1 |
paul@45 | 974 | bnez $t8, _visible_update_ret |
paul@45 | 975 | nop |
paul@45 | 976 | |
paul@45 | 977 | /* Reference the next line and update the DMA source address. */ |
paul@45 | 978 | |
paul@45 | 979 | addiu $s2, $s2, LINE_LENGTH |
paul@45 | 980 | |
paul@45 | 981 | /* Test for wraparound. */ |
paul@45 | 982 | |
paul@45 | 983 | li $t8, (SCREEN_BASE + SCREEN_SIZE) |
paul@45 | 984 | sltu $t8, $s2, $t8 |
paul@45 | 985 | bnez $t8, _visible_dma_update |
paul@45 | 986 | nop |
paul@45 | 987 | |
paul@45 | 988 | /* Reset the source address. */ |
paul@45 | 989 | |
paul@45 | 990 | li $s2, SCREEN_BASE |
paul@45 | 991 | |
paul@45 | 992 | _visible_dma_update: |
paul@45 | 993 | |
paul@45 | 994 | /* Update the source address. */ |
paul@45 | 995 | |
paul@45 | 996 | la $v0, DCH0SSA |
paul@45 | 997 | sw $s2, 0($v0) |
paul@45 | 998 | |
paul@45 | 999 | _visible_update_ret: |
paul@75 | 1000 | jr $ra |
paul@45 | 1001 | nop |
paul@45 | 1002 | |
paul@45 | 1003 | |
paul@45 | 1004 | |
paul@9 | 1005 | /* Within the vertical front porch. */ |
paul@9 | 1006 | |
paul@9 | 1007 | vfp_active: |
paul@9 | 1008 | /* Test for vsync. */ |
paul@9 | 1009 | |
paul@9 | 1010 | sltiu $v0, $s0, VSYNC_START |
paul@9 | 1011 | bnez $v0, _vfp_active_ret |
paul@9 | 1012 | nop |
paul@9 | 1013 | |
paul@9 | 1014 | /* Start the vsync. */ |
paul@9 | 1015 | |
paul@9 | 1016 | la $s1, vsync_active |
paul@9 | 1017 | |
paul@9 | 1018 | /* Bring vsync low when the next line starts. */ |
paul@9 | 1019 | |
paul@9 | 1020 | la $v0, OC2CON |
paul@9 | 1021 | li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ |
paul@9 | 1022 | sw $v1, 0($v0) |
paul@9 | 1023 | |
paul@9 | 1024 | _vfp_active_ret: |
paul@9 | 1025 | jr $ra |
paul@9 | 1026 | nop |
paul@9 | 1027 | |
paul@9 | 1028 | |
paul@9 | 1029 | |
paul@9 | 1030 | /* The vsync period. */ |
paul@9 | 1031 | |
paul@9 | 1032 | vsync_active: |
paul@9 | 1033 | /* Test for front porch. */ |
paul@9 | 1034 | |
paul@9 | 1035 | sltiu $v0, $s0, VSYNC_END |
paul@9 | 1036 | bnez $v0, _vsync_active_ret |
paul@9 | 1037 | nop |
paul@9 | 1038 | |
paul@9 | 1039 | /* Start the back porch. */ |
paul@9 | 1040 | |
paul@9 | 1041 | move $s0, $zero |
paul@9 | 1042 | la $s1, vbp_active |
paul@9 | 1043 | |
paul@9 | 1044 | /* Bring vsync high when the next line starts. */ |
paul@9 | 1045 | |
paul@9 | 1046 | la $v0, OC2CON |
paul@9 | 1047 | li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ |
paul@9 | 1048 | sw $v1, 0($v0) |
paul@9 | 1049 | |
paul@9 | 1050 | _vsync_active_ret: |
paul@9 | 1051 | jr $ra |
paul@9 | 1052 | nop |
paul@79 | 1053 | |
paul@79 | 1054 | |
paul@79 | 1055 | |
paul@79 | 1056 | /* Exception handler. */ |
paul@79 | 1057 | |
paul@79 | 1058 | exc_handler: |
paul@79 | 1059 | mfc0 $t7, CP0_ERROREPC |
paul@79 | 1060 | nop |
paul@85 | 1061 | la $ra, exc_handler_end |
paul@79 | 1062 | |
paul@79 | 1063 | exc_write_word: |
paul@79 | 1064 | li $t8, 32 |
paul@79 | 1065 | la $v0, U1TXREG |
paul@79 | 1066 | exc_loop: |
paul@79 | 1067 | addiu $t8, $t8, -4 |
paul@79 | 1068 | srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ |
paul@79 | 1069 | andi $v1, $v1, 0xF |
paul@79 | 1070 | addiu $t9, $v1, -10 /* $t9 >= 10? */ |
paul@79 | 1071 | bgez $t9, exc_alpha |
paul@79 | 1072 | nop |
paul@79 | 1073 | exc_digit: |
paul@79 | 1074 | addiu $v1, $v1, 48 /* convert to digit: '0' */ |
paul@79 | 1075 | j exc_write |
paul@79 | 1076 | nop |
paul@79 | 1077 | exc_alpha: |
paul@79 | 1078 | addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ |
paul@79 | 1079 | exc_write: |
paul@79 | 1080 | sw $v1, 0($v0) |
paul@79 | 1081 | bnez $t8, exc_loop |
paul@79 | 1082 | nop |
paul@79 | 1083 | exc_loop_end: |
paul@85 | 1084 | li $v1, ' ' |
paul@79 | 1085 | sw $v1, 0($v0) |
paul@79 | 1086 | |
paul@79 | 1087 | exc_handler_end: |
paul@85 | 1088 | jr $ra |
paul@79 | 1089 | nop |