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 23 #define LINE_LENGTH 160 /* pixels */ 24 25 #define HFREQ_LIMIT 1254 /* 40MHz cycles */ 26 #define HSYNC_START 800 /* 40MHz cycles */ 27 #define HSYNC_LIMIT 96 /* 40MHz cycles */ 28 #define HSYNC_END (HSYNC_START + HSYNC_LIMIT) 29 30 #define VISIBLE_START 15 /* horizontal lines, back porch end */ 31 #define VFP_START 527 /* horizontal lines, front porch start */ 32 #define VSYNC_START 529 /* horizontal lines, front porch end */ 33 #define VSYNC_END 531 /* horizontal lines, back porch start */ 34 35 #define SCREEN_SIZE (40 * 1024) 36 37 /* Disable JTAG functionality on pins. */ 38 39 .section .devcfg0, "a" 40 .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ 41 42 /* 43 Set the oscillator to be the FRC oscillator with PLL, with peripheral clock 44 divided by 1, and FRCDIV+PLL selected. 45 46 The system clock and peripheral clock are therefore the same. 47 48 The watchdog timer (FWDTEN) is also disabled. 49 50 The secondary oscillator pin (FSOSCEN) is disabled to avoid pin conflicts with 51 RPB4. 52 */ 53 54 .section .devcfg1, "a" 55 .word 0xff7fcfd9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<2:0> = 0; 56 DEVCFG1<5> = FSOSCEN = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ 57 58 /* 59 Set the FRC oscillator PLL function with an input division of 4, an output 60 division of 2, a multiplication of 20, yielding a multiplication of 2.5. 61 62 The FRC is apparently at 16MHz and this produces a system clock of 40MHz. 63 */ 64 65 .section .devcfg2, "a" 66 .word 0xfff9ffdb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 001; 67 DEVCFG2<6:4> = FPLLMUL<2:0> = 101; 68 DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ 69 70 .text 71 .globl _start 72 73 _start: 74 /* 75 Configure RAM. 76 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 77 */ 78 79 la $v0, BMXCON 80 lw $v1, 0($v0) 81 li $t8, ~(1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 82 and $v1, $v1, $t8 83 li $t8, ~0b111 /* BMXCON<2:0> = BMXARB<2:0> = 0 */ 84 ori $t8, $t8, 0b010 /* BMXCON<2:0> = BMXARB<2:0> = 2 */ 85 and $v1, $v1, $t8 86 sw $v1, 0($v0) 87 88 /* Enable caching. */ 89 90 li $v0, CONFIG_CM_CACHABLE_NONCOHERENT 91 mtc0 $v0, CP0_CONFIG 92 nop 93 94 /* Get the RAM size. */ 95 96 la $v0, BMXDRMSZ 97 lw $v0, 0($v0) 98 99 /* Initialise the stack pointer. */ 100 101 li $v1, KSEG0_BASE 102 addu $sp, $v0, $v1 /* sp = KSEG0_BASE + RAM size */ 103 104 /* Initialise framebuffer. */ 105 106 jal init_framebuffer 107 nop 108 109 /* Initialise the globals pointer. */ 110 111 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 112 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 113 114 /* Set pins for output. */ 115 116 jal init_pins 117 nop 118 119 jal init_oc_pins 120 nop 121 122 /* Initialise the status register. */ 123 124 jal init_interrupts 125 nop 126 127 /* Initialise timer. */ 128 129 jal init_timer2 130 nop 131 132 /* Initialise PMP. */ 133 134 jal init_pmp 135 nop 136 137 /* Initialise DMA. */ 138 139 jal init_dma 140 nop 141 142 /* Initialise OC3 and OC2. */ 143 144 jal init_oc 145 nop 146 147 /* Enable interrupts and loop. */ 148 149 jal enable_interrupts 150 nop 151 152 jal handle_error_level 153 nop 154 155 /* Set initial sync conditions. */ 156 157 la $t0, PORTB 158 li $t1, (1 << 5) /* PORTB<5> = RB5 */ 159 sw $t1, SET($t0) 160 161 la $t0, PORTB 162 li $t1, (1 << 10) /* PORTB<10> = RB10 */ 163 sw $t1, SET($t0) 164 165 /* Main program. */ 166 167 li $a1, 5000000 /* counter = 5000000 */ 168 169 /* Initialise the display state. */ 170 171 li $s0, 0 /* line counter */ 172 la $s1, vbp_active /* current event */ 173 move $s2, $zero /* line address */ 174 175 /* Monitoring loop. */ 176 loop: 177 addiu $a1, $a1, -1 /* counter -= 1 */ 178 bnez $a1, loop /* until counter == 0 */ 179 nop 180 181 li $a1, 5000000 /* counter = 5000000 */ 182 183 la $t0, PORTB 184 li $t1, (1 << 11) /* PORTB<11> = RB11 */ 185 sw $t1, INV($t0) 186 187 _next: 188 j loop 189 nop 190 191 192 193 init_pins: 194 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 195 196 la $v0, CFGCON 197 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 198 sw $v1, CLR($v0) 199 200 init_outputs: 201 /* Remove analogue features from pins. */ 202 203 la $v0, ANSELA 204 sw $zero, 0($v0) /* ANSELA = 0 */ 205 la $v0, ANSELB 206 sw $zero, 0($v0) /* ANSELB = 0 */ 207 208 la $v0, TRISA 209 sw $zero, 0($v0) 210 la $v0, TRISB 211 sw $zero, 0($v0) 212 213 la $v0, PORTA 214 sw $zero, 0($v0) 215 la $v0, PORTB 216 sw $zero, 0($v0) 217 218 jr $ra 219 nop 220 221 222 223 /* Initialisation routines. */ 224 225 init_timer2: 226 227 /* Initialise Timer2 interrupt. */ 228 229 la $v0, T2CON 230 sw $zero, 0($v0) /* T2CON = 0 */ 231 nop 232 233 la $v0, TMR2 234 sw $zero, 0($v0) /* TMR2 = 0 */ 235 236 la $v0, PR2 237 li $v1, HFREQ_LIMIT 238 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 239 240 /* Initialise Timer2 interrupt. */ 241 242 la $v0, IFS0 243 li $v1, (1 << 9) 244 sw $v1, CLR($v0) /* T2IF = 0 */ 245 246 la $v0, IPC2 247 li $v1, 0b11111 248 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 249 250 la $v0, IPC2 251 li $v1, 0b11111 252 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 253 254 la $v0, IEC0 255 li $v1, (1 << 9) 256 sw $v1, SET($v0) /* T2IE = 1 */ 257 258 /* Start timer. */ 259 260 la $v0, T2CON 261 li $v1, (1 << 15) 262 sw $v1, SET($v0) /* ON = 1 */ 263 264 jr $ra 265 nop 266 267 268 269 /* 270 Output compare initialisation. 271 272 Timer2 will be used to trigger two events using OC3: one initiating the hsync 273 pulse, and one terminating the pulse. The pulse should appear after the line 274 data has been transferred using DMA, but this is achieved by just choosing 275 suitable start and end values. 276 277 Using OC2, Timer 2 triggers a level shifting event and OC2 is reconfigured to 278 reverse the level at a later point. 279 */ 280 281 init_oc: 282 /* Disable OC3 interrupts. */ 283 284 la $v0, IEC0 285 li $v1, (1 << 17) /* IEC0<17> = OC3IE = 0 */ 286 sw $v1, CLR($v0) 287 288 la $v0, IFS0 289 li $v1, (1 << 17) /* IFS0<17> = OC3IF = 0 */ 290 sw $v1, CLR($v0) 291 292 /* Initialise OC3. */ 293 294 la $v0, OC3CON 295 li $v1, 0b101 /* OC3CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 296 sw $v1, 0($v0) 297 298 /* Pulse start and end. */ 299 300 la $v0, OC3R 301 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 302 sw $v1, 0($v0) 303 304 la $v0, OC3RS 305 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 306 sw $v1, 0($v0) 307 308 /* OC3 is enabled. */ 309 310 la $v0, OC3CON 311 li $v1, (1 << 15) 312 sw $v1, SET($v0) 313 314 /* Disable OC2 interrupts. */ 315 316 la $v0, IEC0 317 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 318 sw $v1, CLR($v0) 319 320 la $v0, IFS0 321 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 322 sw $v1, CLR($v0) 323 324 /* Initialise OC2. */ 325 326 la $v0, OC2CON 327 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 328 sw $v1, 0($v0) 329 330 /* Set pulse position. */ 331 332 la $v0, OC2R 333 sw $zero, 0($v0) 334 335 /* Enable OC2 later. */ 336 337 jr $ra 338 nop 339 340 341 init_oc_pins: 342 /* Unlock the configuration register bits. */ 343 344 la $v0, SYSKEY 345 sw $zero, 0($v0) 346 li $v1, 0xAA996655 347 sw $v1, 0($v0) 348 li $v1, 0x556699AA 349 sw $v1, 0($v0) 350 351 la $v0, CFGCON 352 lw $t8, 0($v0) 353 li $v1, (1 << 13) /* IOLOCK = 0 */ 354 sw $v1, CLR($v0) 355 356 /* Map OC3 to RPB10. */ 357 358 la $v0, RPB10R 359 li $v1, 0b0101 /* RPB10R<3:0> = 0101 (OC3) */ 360 sw $v1, 0($v0) 361 362 /* Map OC2 to RPB5. */ 363 364 la $v0, RPB5R 365 li $v1, 0b0101 /* RPB5R<3:0> = 0101 (OC2) */ 366 sw $v1, 0($v0) 367 368 la $v0, CFGCON 369 sw $t8, 0($v0) 370 371 /* Lock the oscillator control register again. */ 372 373 la $v0, SYSKEY 374 li $v1, 0x33333333 375 sw $v1, 0($v0) 376 377 jr $ra 378 nop 379 380 381 382 /* Parallel Master Port initialisation. */ 383 384 init_pmp: 385 /* Disable PMP interrupts. */ 386 387 la $v0, IEC1 388 li $v1, (1 << 16) /* IEC1<16> = PMPIE = 0 */ 389 sw $v1, CLR($v0) 390 391 /* Initialise PMP. 392 393 PMCON<12:11> = ADDRMUX<1:0> = 0; demultiplexed address and data 394 PMCON<9> = PTWREN<0> = 0; no write pin 395 PMCON<8> = PTRDEN<0> = 0; no read pin 396 PMCON<7:6> = CSF<1:0> = 0; no chip select pins 397 */ 398 399 la $v0, PMCON 400 sw $zero, 0($v0) 401 402 /* 403 PMMODE<14:13> = IRQM<1:0> = 1; interrupt after every read/write 404 PMMODE<12:11> = INCM<1:0> = 0; no increment on every read/write 405 PMMODE<10> = MODE16<0> = 0; 8-bit transfers 406 PMMODE<9:8> = MODE<1:0> = 10; master mode 2 407 PMMODE<5:2> = WAITM<3:0> = 00; single cycle read/write, no chip select wait cycles 408 */ 409 410 la $v0, PMMODE 411 li $v1, 0x2200 412 sw $v1, 0($v0) 413 414 /* Free non-essential pins for general I/O. */ 415 416 la $v0, PMAEN 417 sw $zero, 0($v0) 418 419 la $v0, PMADDR 420 sw $zero, 0($v0) 421 422 la $v0, IFS1 423 li $v1, (3 << 16) /* IFS1<17:16> = PMPEIF, PMPIF = 0 */ 424 sw $v1, CLR($v0) 425 426 /* Start PMP mode. */ 427 428 la $v0, PMCON 429 li $v1, (1 << 15) /* PMCON<15> = ON = 1 */ 430 sw $v1, SET($v0) 431 432 jr $ra 433 nop 434 435 436 437 /* 438 Direct Memory Access initialisation. 439 440 Write 160 pixels to the PMP for the line data. This is initiated by a timer 441 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 442 address update routine, changing the source address of the DMA channel. 443 */ 444 445 init_dma: 446 /* Disable DMA interrupts. */ 447 448 la $v0, IEC1 449 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 0 */ 450 sw $v1, CLR($v0) 451 452 /* Clear DMA interrupt flags. */ 453 454 la $v0, IFS1 455 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 456 sw $v1, CLR($v0) 457 458 /* Enable DMA. */ 459 460 la $v0, DMACON 461 li $v1, (1 << 15) 462 sw $v1, SET($v0) 463 464 /* 465 Initialise a line channel. 466 The line channel will be channel 0 (x = 0). 467 468 Once the hsync channel has completed a transfer, the line channel 469 transfer is initiated. 470 471 Specify a priority of 3: 472 DCHxCON<1:0> = CHPRI<1:0> = 3 473 474 Auto-enable the channels: 475 DCHxCON<4> = CHAEN = 1 476 */ 477 478 la $v0, DCH0CON 479 li $v1, 0b10011 480 sw $v1, 0($v0) 481 482 /* 483 Initiate channel transfers when the initiating interrupt condition 484 occurs: 485 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 486 */ 487 488 la $v0, DCH0ECON 489 li $v1, (9 << 8) | (1 << 4) 490 sw $v1, 0($v0) 491 492 /* 493 The line channel has a cell size of 160 bytes: 494 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 495 */ 496 497 la $v0, DCH0CSIZ 498 li $v1, LINE_LENGTH 499 sw $v1, 0($v0) 500 501 /* 502 Each source has a size identical to the cell size: 503 DCHxSSIZ<15:0> = CHSSIZ<15:0> = n 504 */ 505 506 la $v0, DCH0SSIZ 507 li $v1, LINE_LENGTH 508 sw $v1, 0($v0) 509 510 /* 511 The source address is the physical address of the line data: 512 DCHxSSA = line data physical address 513 */ 514 515 la $v0, DCH0SSA 516 sw $zero, 0($v0) 517 518 /* 519 Each destination has a size of 1 byte: 520 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 521 */ 522 523 la $v0, DCH0DSIZ 524 li $v1, 1 525 sw $v1, 0($v0) 526 527 /* 528 For the line channel, the destination address is the physical address of 529 PMDIN: DCHxDSA = physical(PMDIN) 530 */ 531 532 la $v0, DCH0DSA 533 li $v1, PMDIN 534 li $t8, KSEG1_BASE 535 subu $v1, $v1, $t8 536 sw $v1, 0($v0) 537 538 /* 539 Use the block transfer completion interrupt to indicate when the source 540 address can be updated. 541 */ 542 543 la $v0, DCH0INT 544 li $v1, (1 << 19) /* CHBCIE = 1 */ 545 sw $v1, 0($v0) 546 547 /* Enable interrupt for address updating. */ 548 549 la $v0, IPC10 550 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 551 sw $v1, CLR($v0) 552 553 la $v0, IPC10 554 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 555 sw $v1, SET($v0) 556 557 la $v0, IEC1 558 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 559 sw $v1, SET($v0) 560 561 /* Enable channels. */ 562 563 la $v0, DCH0CON 564 li $v1, 0b10000000 565 sw $v1, SET($v0) 566 567 jr $ra 568 nop 569 570 571 572 /* Framebuffer initialisation. */ 573 574 init_framebuffer: 575 li $v0, KSEG0_BASE 576 li $t8, SCREEN_SIZE 577 li $v1, 0xff031ce0 578 579 _init_fb_loop: 580 sw $v1, 0($v0) 581 addiu $v0, $v0, 4 582 addiu $t8, $t8, -4 583 bnez $t8, _init_fb_loop 584 nop 585 586 jr $ra 587 nop 588 589 590 591 /* Utilities. */ 592 593 handle_error_level: 594 mfc0 $t3, CP0_STATUS 595 li $t4, ~(STATUS_ERL | STATUS_EXL) 596 and $t3, $t3, $t4 597 mtc0 $t3, CP0_STATUS 598 jr $ra 599 nop 600 601 enable_interrupts: 602 mfc0 $t3, CP0_STATUS 603 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 604 and $t3, $t3, $t4 605 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 606 and $t3, $t3, $t4 607 ori $t3, $t3, STATUS_IE 608 mtc0 $t3, CP0_STATUS 609 jr $ra 610 nop 611 612 init_interrupts: 613 mfc0 $t3, CP0_DEBUG 614 li $t4, ~DEBUG_DM 615 and $t3, $t3, $t4 616 mtc0 $t3, CP0_DEBUG 617 618 mfc0 $t3, CP0_STATUS 619 li $t4, STATUS_BEV 620 or $t3, $t3, $t4 621 mtc0 $t3, CP0_STATUS 622 623 la $t3, exception_handler 624 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 625 626 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 627 mtc0 $t3, CP0_INTCTL 628 629 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 630 mtc0 $t3, CP0_CAUSE 631 632 /* Multiple vector... 633 la $t3, INTCON 634 li $t4, (1 << 12) 635 sw $t4, SET($t3) */ 636 637 jr $ra 638 nop 639 640 641 642 /* Exception servicing. */ 643 644 .section .flash, "a" 645 646 /* Exception servicing. */ 647 648 exception_handler: 649 li $t8, 2500000 650 exc_loop: 651 addiu $t8, $t8, -1 652 bnez $t8, exc_loop 653 nop 654 la $v0, PORTB 655 li $v1, (1 << 11) /* PORTB<11> = RB11 */ 656 sw $v1, INV($v0) 657 j exception_handler 658 nop 659 660 661 662 /* Interrupt servicing. */ 663 664 .org 0x200 665 666 interrupt_handler: 667 668 /* Check for a timer interrupt condition. */ 669 670 la $v0, IFS0 671 lw $v1, 0($v0) 672 andi $v1, $v1, (1 << 9) /* T2IF */ 673 beqz $v1, irq_dma 674 nop 675 676 /* Increment the line counter. */ 677 678 addiu $s0, $s0, 1 679 680 /* Jump to the event handler. */ 681 682 jalr $s1 683 nop 684 685 irq_clear_timer: 686 687 /* Clear the timer interrupt condition. */ 688 689 la $v0, IFS0 690 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 691 sw $v1, CLR($v0) 692 693 irq_dma: 694 695 /* Check for a DMA interrupt condition. */ 696 697 la $v0, IFS1 698 lw $v1, 0($v0) 699 li $t8, (1 << 28) /* DMA0IF */ 700 and $v1, $v1, $t8 701 beqz $v1, irq_exit 702 nop 703 704 /* Test the block transfer completion interrupt flag. */ 705 706 la $v0, DCH0INT 707 lw $v1, 0($v0) 708 andi $v1, $v1, (1 << 3) /* CHBCIF */ 709 beqz $v1, irq_clear_dma 710 nop 711 712 /* Clear the block transfer completion interrupt flag. */ 713 714 li $v1, (1 << 3) /* CHBCIF = 0 */ 715 sw $v1, CLR($v0) 716 717 /* 718 Update the line data address if the line counter (referring to the 719 next line) is even. 720 */ 721 722 andi $t8, $s0, 1 723 bnez $t8, irq_clear_dma 724 nop 725 726 /* Reference the next line and update the DMA source address. */ 727 728 addiu $s2, $s2, LINE_LENGTH 729 730 /* Test for wraparound. */ 731 732 li $t8, SCREEN_SIZE 733 sltu $t8, $s2, $t8 734 bnez $t8, irq_dma_update 735 nop 736 737 /* Reset the source address. */ 738 739 move $s2, $zero 740 741 irq_dma_update: 742 743 la $v0, DCH0SSA 744 sw $s2, 0($v0) 745 746 irq_clear_dma: 747 748 /* Clear the DMA interrupt condition. */ 749 750 la $v0, IFS1 751 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 752 sw $v1, CLR($v0) 753 754 irq_exit: 755 eret 756 nop 757 758 759 760 /* Event routines. */ 761 762 /* The vertical back porch. */ 763 764 vbp_active: 765 /* Test for visible region. */ 766 767 sltiu $v0, $s0, VISIBLE_START 768 bnez $v0, _vbp_active_ret 769 nop 770 771 /* Start the visible region. */ 772 773 la $s1, visible_active 774 775 _vbp_active_ret: 776 jr $ra 777 nop 778 779 780 781 /* The visible region. */ 782 783 visible_active: 784 /* Test for front porch. */ 785 786 sltiu $v0, $s0, VFP_START 787 bnez $v0, _visible_active_ret 788 nop 789 790 /* Start the front porch region. */ 791 792 la $s1, vfp_active 793 794 _visible_active_ret: 795 jr $ra 796 nop 797 798 799 800 /* Within the vertical front porch. */ 801 802 vfp_active: 803 /* Test for vsync. */ 804 805 sltiu $v0, $s0, VSYNC_START 806 bnez $v0, _vfp_active_ret 807 nop 808 809 /* Start the vsync. */ 810 811 la $s1, vsync_active 812 813 /* Bring vsync low when the next line starts. */ 814 815 la $v0, OC2CON 816 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 817 sw $v1, 0($v0) 818 819 _vfp_active_ret: 820 jr $ra 821 nop 822 823 824 825 /* The vsync period. */ 826 827 vsync_active: 828 /* Test for front porch. */ 829 830 sltiu $v0, $s0, VSYNC_END 831 bnez $v0, _vsync_active_ret 832 nop 833 834 /* Start the back porch. */ 835 836 move $s0, $zero 837 la $s1, vbp_active 838 839 /* Bring vsync high when the next line starts. */ 840 841 la $v0, OC2CON 842 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 843 sw $v1, 0($v0) 844 845 _vsync_active_ret: 846 jr $ra 847 nop