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, IPC2 871 lw $v1, 0($v0) 872 li $t8, ~0b11111 873 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 874 ori $v1, $v1, 0b00111 /* T2IP = 1; T2IS = 3 */ 875 sw $v1, 0($v0) 876 877 /* Enable the line channel for timer event transfer initiation. */ 878 879 la $v0, DCH0ECON 880 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 881 sw $v1, SET($v0) 882 883 _vbp_active_ret: 884 jr $ra 885 nop 886 887 888 889 /* The visible region. */ 890 891 visible_active: 892 /* Test for front porch. */ 893 894 sltiu $v0, $s0, VFP_START 895 bnez $v0, visible_update_address 896 nop 897 898 /* Start the front porch region. */ 899 900 la $s1, vfp_active 901 902 /* Restore delivery of the timer interrupt after the visible period. */ 903 904 la $v0, IPC2 905 lw $v1, 0($v0) 906 li $t8, ~0b11111 907 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 908 ori $v1, $v1, 0b11111 /* T2IP = 7; T2IS = 3 */ 909 sw $v1, 0($v0) 910 911 /* Disable the line channel. */ 912 913 la $v0, DCH0ECON 914 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 915 sw $v1, CLR($v0) 916 917 _visible_active_ret: 918 jr $ra 919 nop 920 921 922 923 /* DMA update routine. */ 924 925 visible_update_address: 926 927 /* 928 Update the line data address if the line counter (referring to the 929 next line) is even. 930 */ 931 932 andi $t8, $s0, 1 933 bnez $t8, _visible_update_ret 934 nop 935 936 /* Reference the next line and update the DMA source address. */ 937 938 addiu $s2, $s2, LINE_LENGTH 939 940 /* Test for wraparound. */ 941 942 li $t8, (SCREEN_BASE + SCREEN_SIZE) 943 sltu $t8, $s2, $t8 944 bnez $t8, _visible_dma_update 945 nop 946 947 /* Reset the source address. */ 948 949 li $s2, SCREEN_BASE 950 951 _visible_dma_update: 952 953 /* Update the source address. */ 954 955 la $v0, DCH0SSA 956 sw $s2, 0($v0) 957 958 _visible_update_ret: 959 jr $ra 960 nop 961 962 963 964 /* Within the vertical front porch. */ 965 966 vfp_active: 967 /* Test for vsync. */ 968 969 sltiu $v0, $s0, VSYNC_START 970 bnez $v0, _vfp_active_ret 971 nop 972 973 /* Start the vsync. */ 974 975 la $s1, vsync_active 976 977 /* Bring vsync low when the next line starts. */ 978 979 la $v0, OC2CON 980 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 981 sw $v1, 0($v0) 982 983 _vfp_active_ret: 984 jr $ra 985 nop 986 987 988 989 /* The vsync period. */ 990 991 vsync_active: 992 /* Test for front porch. */ 993 994 sltiu $v0, $s0, VSYNC_END 995 bnez $v0, _vsync_active_ret 996 nop 997 998 /* Start the back porch. */ 999 1000 move $s0, $zero 1001 la $s1, vbp_active 1002 1003 /* Bring vsync high when the next line starts. */ 1004 1005 la $v0, OC2CON 1006 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1007 sw $v1, 0($v0) 1008 1009 _vsync_active_ret: 1010 jr $ra 1011 nop