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 288 la $v0, IPC2 289 li $v1, 0b11111 290 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 291 292 la $v0, IEC0 293 li $v1, (1 << 9) 294 sw $v1, SET($v0) /* T2IE = 1 */ 295 296 /* Start timer. */ 297 298 la $v0, T2CON 299 li $v1, (1 << 15) 300 sw $v1, SET($v0) /* ON = 1 */ 301 302 jr $ra 303 nop 304 305 306 307 /* 308 Output compare initialisation. 309 310 Timer2 will be used to trigger two events using OC1: one initiating the hsync 311 pulse, and one terminating the pulse. The pulse should appear after the line 312 data has been transferred using DMA, but this is achieved by just choosing 313 suitable start and end values. 314 315 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 316 reverse the level at a later point. In this way, the vsync pulse is generated 317 and is synchronised to the display lines. 318 */ 319 320 init_oc: 321 /* Disable OC1 interrupts. */ 322 323 la $v0, IEC0 324 li $v1, (1 << 7) /* IEC0<7> = OC1IE = 0 */ 325 sw $v1, CLR($v0) 326 327 la $v0, IFS0 328 li $v1, (1 << 7) /* IFS0<7> = OC1IF = 0 */ 329 sw $v1, CLR($v0) 330 331 /* Initialise OC1. */ 332 333 la $v0, OC1CON 334 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 335 sw $v1, 0($v0) 336 337 /* Pulse start and end. */ 338 339 la $v0, OC1R 340 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 341 sw $v1, 0($v0) 342 343 la $v0, OC1RS 344 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 345 sw $v1, 0($v0) 346 347 /* OC1 is enabled. */ 348 349 la $v0, OC1CON 350 li $v1, (1 << 15) 351 sw $v1, SET($v0) 352 353 /* Disable OC2 interrupts. */ 354 355 la $v0, IEC0 356 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 357 sw $v1, CLR($v0) 358 359 la $v0, IFS0 360 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 361 sw $v1, CLR($v0) 362 363 /* Initialise OC2. */ 364 365 la $v0, OC2CON 366 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 367 sw $v1, 0($v0) 368 369 /* Set pulse position. */ 370 371 la $v0, OC2R 372 sw $zero, 0($v0) 373 374 /* Enable OC2 later. */ 375 376 jr $ra 377 nop 378 379 init_oc_pins: 380 /* Unlock the configuration register bits. */ 381 382 la $v0, SYSKEY 383 sw $zero, 0($v0) 384 li $v1, 0xAA996655 385 sw $v1, 0($v0) 386 li $v1, 0x556699AA 387 sw $v1, 0($v0) 388 389 la $v0, CFGCON 390 lw $t8, 0($v0) 391 li $v1, (1 << 13) /* IOLOCK = 0 */ 392 sw $v1, CLR($v0) 393 394 /* Map OC1 to RPA0. */ 395 396 la $v0, RPA0R 397 li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ 398 sw $v1, 0($v0) 399 400 /* Map OC2 to RPA1. */ 401 402 la $v0, RPA1R 403 li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ 404 sw $v1, 0($v0) 405 406 la $v0, CFGCON 407 sw $t8, 0($v0) 408 409 /* Lock the oscillator control register again. */ 410 411 la $v0, SYSKEY 412 li $v1, 0x33333333 413 sw $v1, 0($v0) 414 415 jr $ra 416 nop 417 418 419 420 /* 421 Direct Memory Access initialisation. 422 423 Write 160 pixels to PORTB for the line data. This is initiated by a timer 424 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 425 address update routine, changing the source address of the DMA channel. 426 */ 427 428 init_dma: 429 /* Disable DMA interrupts. */ 430 431 la $v0, IEC1 432 li $v1, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 433 sw $v1, CLR($v0) 434 435 /* Clear DMA interrupt flags. */ 436 437 la $v0, IFS1 438 li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 439 sw $v1, CLR($v0) 440 441 /* Enable DMA. */ 442 443 la $v0, DMACON 444 li $v1, (1 << 15) 445 sw $v1, SET($v0) 446 447 /* 448 Initialise a line channel. 449 The line channel will be channel 1 (x = 1). 450 451 Specify a priority of 3: 452 DCHxCON<1:0> = CHPRI<1:0> = 3 453 454 Auto-enable the channel: 455 DCHxCON<4> = CHAEN = 1 456 */ 457 458 la $v0, DCH1CON 459 li $v1, 0b10011 460 sw $v1, 0($v0) 461 462 /* 463 Initialise a level reset channel. 464 The reset channel will be channel 2 (x = 2). 465 466 Specify a priority of 3: 467 DCHxCON<1:0> = CHPRI<1:0> = 3 468 469 Chain the channel to channel 0: 470 DCHxCON<5> = CHCHN = 1 471 472 Allow the channel to receive events when disabled: 473 DCHxCON<6> = CHAED = 1 474 */ 475 476 la $v0, DCH2CON 477 li $v1, 0b1100011 478 sw $v1, 0($v0) 479 480 /* 481 Initiate channel transfers when the initiating interrupt condition 482 occurs: 483 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 484 DCHxECON<4> = SIRQEN = 1 485 486 For now, however, prevent initiation by not setting SIRQEN. 487 */ 488 489 la $v0, DCH1ECON 490 li $v1, (9 << 8) 491 sw $v1, 0($v0) 492 493 /* 494 Initiate reset channel transfer when channel 1 is finished: 495 DCHxECON<15:8> = CHSIRQ<7:0> = channel 1 interrupt 496 DCHxECON<4> = SIRQEN = 1 497 */ 498 499 la $v0, DCH2ECON 500 li $v1, (61 << 8) | (1 << 4) 501 sw $v1, 0($v0) 502 503 /* 504 The line channel has a cell size of the number bytes in a line: 505 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 506 */ 507 508 la $v0, DCH1CSIZ 509 li $v1, LINE_LENGTH 510 sw $v1, 0($v0) 511 512 /* 513 The reset channel has a cell size of a single zero byte: 514 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 515 */ 516 517 la $v0, DCH2CSIZ 518 li $v1, 1 519 sw $v1, 0($v0) 520 521 /* 522 The source has a size identical to the cell size: 523 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 524 */ 525 526 la $v0, DCH1SSIZ 527 li $v1, LINE_LENGTH 528 sw $v1, 0($v0) 529 530 la $v0, DCH2SSIZ 531 li $v1, 1 532 sw $v1, 0($v0) 533 534 /* 535 The source address is the physical address of the line data: 536 DCHxSSA = physical(line data address) 537 */ 538 539 la $v0, DCH1SSA 540 li $v1, SCREEN_BASE 541 sw $v1, 0($v0) 542 543 /* 544 For the reset channel, a single byte of zero is transferred: 545 DCHxSSA = physical(zero data address) 546 */ 547 548 la $v0, DCH2SSA 549 la $v1, zerodata 550 li $t8, KSEG0_BASE 551 subu $v1, $v1, $t8 552 sw $v1, 0($v0) 553 554 /* 555 The destination has a size of 1 byte: 556 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 557 */ 558 559 la $v0, DCH1DSIZ 560 li $v1, 1 561 sw $v1, 0($v0) 562 563 la $v0, DCH2DSIZ 564 sw $v1, 0($v0) 565 566 /* 567 The destination address is the physical address of PORTB: 568 DCHxDSA = physical(PORTB) 569 */ 570 571 la $v0, DCH1DSA 572 li $v1, PORTB 573 li $t8, KSEG1_BASE 574 subu $v1, $v1, $t8 575 sw $v1, 0($v0) 576 577 la $v0, DCH2DSA 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, DCH1INT 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, 0b1111100000000 /* DMA1IP, DMA1IS = 0 */ 593 sw $v1, CLR($v0) 594 595 la $v0, IPC10 596 li $v1, 0b1111100000000 /* DMA1IP = 7, DMA1IS = 3 */ 597 sw $v1, SET($v0) 598 599 la $v0, IEC1 600 li $v1, (1 << 29) /* IEC1<29> = DMA1IE = 1 */ 601 sw $v1, SET($v0) 602 603 /* Enable line channel. */ 604 605 la $v0, DCH1CON 606 li $v1, 0b10000000 607 sw $v1, SET($v0) 608 609 jr $ra 610 nop 611 612 zerodata: 613 .word 0 614 615 616 617 /* Utilities. */ 618 619 handle_error_level: 620 mfc0 $t3, CP0_STATUS 621 li $t4, ~(STATUS_ERL | STATUS_EXL) 622 and $t3, $t3, $t4 623 mtc0 $t3, CP0_STATUS 624 jr $ra 625 nop 626 627 enable_interrupts: 628 mfc0 $t3, CP0_STATUS 629 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 630 and $t3, $t3, $t4 631 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 632 and $t3, $t3, $t4 633 ori $t3, $t3, STATUS_IE 634 mtc0 $t3, CP0_STATUS 635 jr $ra 636 nop 637 638 init_interrupts: 639 mfc0 $t3, CP0_DEBUG 640 li $t4, ~DEBUG_DM 641 and $t3, $t3, $t4 642 mtc0 $t3, CP0_DEBUG 643 644 mfc0 $t3, CP0_STATUS 645 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 646 or $t3, $t3, $t4 647 mtc0 $t3, CP0_STATUS 648 649 la $t3, exception_handler 650 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 651 652 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 653 mtc0 $t3, CP0_INTCTL 654 655 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 656 mtc0 $t3, CP0_CAUSE 657 658 jr $ra 659 nop 660 661 662 663 /* Exception servicing. */ 664 665 .section .flash, "a" 666 667 /* TLB error servicing. */ 668 669 tlb_handler: 670 j exception_handler 671 nop 672 673 674 675 /* General exception servicing. */ 676 677 .org 0x180 678 679 exception_handler: 680 j exc_handler 681 nop 682 683 684 685 /* Interrupt servicing. */ 686 687 .org 0x200 688 689 interrupt_handler: 690 691 /* Store affected registers. */ 692 693 li $k0, IRQ_STACK_LIMIT 694 sw $v0, -4($k0) 695 sw $v1, -8($k0) 696 sw $s0, -12($k0) 697 sw $s1, -16($k0) 698 sw $s2, -20($k0) 699 sw $s3, -24($k0) 700 sw $t8, -28($k0) 701 sw $ra, -32($k0) 702 sw $sp, -36($k0) 703 704 /* Load state. */ 705 706 lw $s0, -44($k0) 707 lw $s1, -48($k0) 708 lw $s2, -52($k0) 709 lw $s3, -56($k0) 710 711 li $sp, IRQ_STACK_TOP 712 713 /* Check for a timer interrupt condition. */ 714 715 la $v0, IFS0 716 lw $v1, 0($v0) 717 andi $v1, $v1, (1 << 9) /* T2IF */ 718 beqz $v1, irq_dma 719 nop 720 721 /* Clear the timer interrupt condition. */ 722 723 sw $v1, CLR($v0) 724 725 /* 726 The timer interrupt will only occur outside the visible region, but the 727 interrupt condition will still occur as the timer wraps around. 728 Therefore, the handling of other interrupts may find the timer interrupt 729 condition set. 730 731 For the visible region, the event handler is invoked when handling the 732 DMA interrupt. Otherwise, the event handler is invoked in response to 733 the timer interrupt. 734 */ 735 736 la $t8, visible_active 737 beq $s1, $t8, irq_dma 738 nop 739 740 /* Increment the line counter (only outside the visible region). */ 741 742 addiu $s0, $s0, 1 743 744 /* Jump to the event handler (only outside the visible region). */ 745 746 jalr $s1 747 nop 748 749 irq_dma: 750 /* Check for a DMA interrupt condition. */ 751 752 la $v0, IFS1 753 lw $v1, 0($v0) 754 li $t8, (1 << 29) /* DMA1IF */ 755 and $v1, $v1, $t8 756 beqz $v1, irq_exit 757 nop 758 759 /* Clear the DMA interrupt condition. */ 760 761 sw $v1, CLR($v0) 762 763 /* Test the block transfer completion interrupt flag. */ 764 765 la $v0, DCH1INT 766 lw $v1, 0($v0) 767 andi $v1, $v1, (1 << 3) /* CHBCIF */ 768 beqz $v1, irq_exit 769 nop 770 771 /* Clear the block transfer completion interrupt flag. */ 772 773 sw $v1, CLR($v0) 774 775 /* 776 The DMA interrupt should only be active within the visible region. 777 The event handler is invoked here instead of in response to a timer 778 interrupt within that region. 779 */ 780 781 /* Increment the line counter (only within the visible region). */ 782 783 addiu $s0, $s0, 1 784 785 /* Jump to the event handler (only within the visible region). */ 786 787 jalr $s1 788 nop 789 790 /* Jump to the DMA update routine. */ 791 792 j visible_update_address 793 nop 794 795 irq_exit: 796 /* Save state. */ 797 798 li $k0, IRQ_STACK_LIMIT 799 sw $s0, -44($k0) 800 sw $s1, -48($k0) 801 sw $s2, -52($k0) 802 sw $s3, -56($k0) 803 804 /* Restore affected registers. */ 805 806 lw $v0, -4($k0) 807 lw $v1, -8($k0) 808 lw $s0, -12($k0) 809 lw $s1, -16($k0) 810 lw $s2, -20($k0) 811 lw $s3, -24($k0) 812 lw $t8, -28($k0) 813 lw $ra, -32($k0) 814 lw $sp, -36($k0) 815 816 eret 817 nop 818 819 820 821 exc_handler: 822 li $t9, 0x80000000 823 mfc0 $t6, CP0_ERROREPC 824 nop 825 exc_loop: 826 and $t7, $t9, $t6 827 beqz $t7, exc_errorepc_zero 828 nop 829 exc_errorepc_one: 830 la $v0, PORTA 831 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 832 sw $v1, SET($v0) 833 j exc_loop_wait 834 nop 835 exc_errorepc_zero: 836 la $v0, PORTA 837 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 838 sw $v1, SET($v0) 839 exc_loop_wait: 840 li $t8, 5000000 841 exc_loop_delay: 842 addiu $t8, $t8, -1 843 bnez $t8, exc_loop_delay 844 nop 845 la $v0, PORTA 846 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 847 sw $v1, CLR($v0) 848 exc_loop_wait_again: 849 li $t8, 2500000 850 exc_loop_delay_again: 851 addiu $t8, $t8, -1 852 bnez $t8, exc_loop_delay_again 853 nop 854 exc_errorepc_next: 855 srl $t9, $t9, 1 856 bnez $t9, exc_loop 857 nop 858 j exc_handler 859 nop 860 861 862 863 /* Event routines. */ 864 865 /* The vertical back porch. */ 866 867 vbp_active: 868 /* Test for visible region. */ 869 870 sltiu $v0, $s0, VISIBLE_START 871 bnez $v0, _vbp_active_ret 872 nop 873 874 /* Start the visible region. */ 875 876 la $s1, visible_active 877 878 /* Reset the line address. */ 879 880 move $s2, $s3 881 882 /* Update the source address. */ 883 884 la $v0, DCH1SSA 885 sw $s2, 0($v0) 886 887 /* Enable the line channel for timer event transfer initiation. */ 888 889 la $v0, DCH1ECON 890 li $v1, (1 << 4) /* DCH1ECON<4> = SIRQEN = 1 */ 891 sw $v1, SET($v0) 892 893 /* Disable the timer interrupt during the visible period. */ 894 895 la $v0, IEC0 896 li $v1, (1 << 9) 897 sw $v1, CLR($v0) /* T2IE = 0 */ 898 899 _vbp_active_ret: 900 jr $ra 901 nop 902 903 904 905 /* The visible region. */ 906 907 visible_active: 908 /* Test for front porch. */ 909 910 sltiu $v0, $s0, VFP_START 911 bnez $v0, _visible_active_ret 912 nop 913 914 /* Start the front porch region. */ 915 916 la $s1, vfp_active 917 918 /* Re-enable the timer interrupt after the visible period. */ 919 920 la $v0, IEC0 921 li $v1, (1 << 9) 922 sw $v1, SET($v0) /* T2IE = 1 */ 923 924 _visible_active_ret: 925 jr $ra 926 nop 927 928 929 930 /* DMA update routine. */ 931 932 visible_update_address: 933 934 /* Test for the last visible line. */ 935 936 la $v0, vfp_active 937 bne $s1, $v0, _visible_update_address 938 nop 939 940 /* Disable the line channel. */ 941 942 la $v0, DCH1ECON 943 li $v1, (1 << 4) /* DCH1ECON<4> = SIRQEN = 0 */ 944 sw $v1, CLR($v0) 945 946 j _visible_update_ret 947 nop 948 949 _visible_update_address: 950 951 /* 952 Update the line data address if the line counter (referring to the 953 next line) is even. 954 */ 955 956 andi $t8, $s0, 1 957 bnez $t8, _visible_update_ret 958 nop 959 960 /* Reference the next line and update the DMA source address. */ 961 962 addiu $s2, $s2, LINE_LENGTH 963 964 /* Test for wraparound. */ 965 966 li $t8, (SCREEN_BASE + SCREEN_SIZE) 967 sltu $t8, $s2, $t8 968 bnez $t8, _visible_dma_update 969 nop 970 971 /* Reset the source address. */ 972 973 li $s2, SCREEN_BASE 974 975 _visible_dma_update: 976 977 /* Update the source address. */ 978 979 la $v0, DCH1SSA 980 sw $s2, 0($v0) 981 982 _visible_update_ret: 983 j irq_exit 984 nop 985 986 987 988 /* Within the vertical front porch. */ 989 990 vfp_active: 991 /* Test for vsync. */ 992 993 sltiu $v0, $s0, VSYNC_START 994 bnez $v0, _vfp_active_ret 995 nop 996 997 /* Start the vsync. */ 998 999 la $s1, vsync_active 1000 1001 /* Bring vsync low when the next line starts. */ 1002 1003 la $v0, OC2CON 1004 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1005 sw $v1, 0($v0) 1006 1007 _vfp_active_ret: 1008 jr $ra 1009 nop 1010 1011 1012 1013 /* The vsync period. */ 1014 1015 vsync_active: 1016 /* Test for front porch. */ 1017 1018 sltiu $v0, $s0, VSYNC_END 1019 bnez $v0, _vsync_active_ret 1020 nop 1021 1022 /* Start the back porch. */ 1023 1024 move $s0, $zero 1025 la $s1, vbp_active 1026 1027 /* Bring vsync high when the next line starts. */ 1028 1029 la $v0, OC2CON 1030 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1031 sw $v1, 0($v0) 1032 1033 _vsync_active_ret: 1034 jr $ra 1035 nop