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