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