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