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