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 679 /* Interrupt servicing. */ 680 681 .org 0x200 682 683 interrupt_handler: 684 685 /* Store affected registers. */ 686 687 li $k0, IRQ_STACK_LIMIT 688 sw $v0, -4($k0) 689 sw $v1, -8($k0) 690 sw $s0, -12($k0) 691 sw $s1, -16($k0) 692 sw $s2, -20($k0) 693 sw $s3, -24($k0) 694 sw $t8, -28($k0) 695 sw $ra, -32($k0) 696 sw $sp, -36($k0) 697 698 /* Load state. */ 699 700 lw $s0, -44($k0) 701 lw $s1, -48($k0) 702 lw $s2, -52($k0) 703 lw $s3, -56($k0) 704 705 li $sp, IRQ_STACK_TOP 706 707 /* Check for a timer interrupt condition. */ 708 709 la $v0, IFS0 710 lw $v1, 0($v0) 711 andi $v1, $v1, (1 << 9) /* T2IF */ 712 beqz $v1, irq_dma 713 nop 714 715 /* Clear the timer interrupt condition. */ 716 717 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 718 sw $v1, CLR($v0) 719 720 /* 721 The timer interrupt will only occur active outside the visible region, 722 but the interrupt condition will still occur as the timer wraps around. 723 Therefore, the handling of other interrupts may find the timer interrupt 724 condition set. 725 726 For the visible region, the event handler is invoked when handling the 727 DMA interrupt. Otherwise, the event handler is invoked in response to 728 the timer interrupt. 729 */ 730 731 la $t8, visible_active 732 beq $s1, $t8, irq_dma 733 nop 734 735 /* Increment the line counter (only outside the visible region). */ 736 737 addiu $s0, $s0, 1 738 739 /* Jump to the event handler (only outside the visible region). */ 740 741 jalr $s1 742 nop 743 744 irq_dma: 745 /* Check for a DMA interrupt condition. */ 746 747 la $v0, IFS1 748 lw $v1, 0($v0) 749 li $t8, (1 << 28) /* DMA0IF */ 750 and $v1, $v1, $t8 751 beqz $v1, irq_exit 752 nop 753 754 /* Clear the DMA interrupt condition. */ 755 756 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 757 sw $v1, CLR($v0) 758 759 /* Test the block transfer completion interrupt flag. */ 760 761 la $v0, DCH0INT 762 lw $v1, 0($v0) 763 andi $v1, $v1, (1 << 3) /* CHBCIF */ 764 beqz $v1, irq_exit 765 nop 766 767 /* Clear the block transfer completion interrupt flag. */ 768 769 li $v1, (1 << 3) /* CHBCIF = 0 */ 770 sw $v1, CLR($v0) 771 772 /* 773 The DMA interrupt should only be active within the visible region. 774 The event handler is invoked here instead of in response to a timer 775 interrupt within that region. 776 */ 777 778 /* Increment the line counter (only within the visible region). */ 779 780 addiu $s0, $s0, 1 781 782 /* Jump to the event handler (only within the visible region). */ 783 784 jalr $s1 785 nop 786 787 /* Jump to the DMA update routine. */ 788 789 j visible_update_address 790 nop 791 792 irq_exit: 793 /* Save state. */ 794 795 li $k0, IRQ_STACK_LIMIT 796 sw $s0, -44($k0) 797 sw $s1, -48($k0) 798 sw $s2, -52($k0) 799 sw $s3, -56($k0) 800 801 /* Restore affected registers. */ 802 803 lw $v0, -4($k0) 804 lw $v1, -8($k0) 805 lw $s0, -12($k0) 806 lw $s1, -16($k0) 807 lw $s2, -20($k0) 808 lw $s3, -24($k0) 809 lw $t8, -28($k0) 810 lw $ra, -32($k0) 811 lw $sp, -36($k0) 812 813 eret 814 nop 815 816 817 818 exc_handler: 819 li $t9, 0x80000000 820 mfc0 $t6, CP0_ERROREPC 821 nop 822 exc_loop: 823 and $t7, $t9, $t6 824 beqz $t7, exc_errorepc_zero 825 nop 826 exc_errorepc_one: 827 la $v0, PORTA 828 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 829 sw $v1, SET($v0) 830 j exc_loop_wait 831 nop 832 exc_errorepc_zero: 833 la $v0, PORTA 834 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 835 sw $v1, SET($v0) 836 exc_loop_wait: 837 li $t8, 5000000 838 exc_loop_delay: 839 addiu $t8, $t8, -1 840 bnez $t8, exc_loop_delay 841 nop 842 la $v0, PORTA 843 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 844 sw $v1, CLR($v0) 845 exc_loop_wait_again: 846 li $t8, 2500000 847 exc_loop_delay_again: 848 addiu $t8, $t8, -1 849 bnez $t8, exc_loop_delay_again 850 nop 851 exc_errorepc_next: 852 srl $t9, $t9, 1 853 bnez $t9, exc_loop 854 nop 855 j exc_handler 856 nop 857 858 859 860 /* Event routines. */ 861 862 /* The vertical back porch. */ 863 864 vbp_active: 865 /* Test for visible region. */ 866 867 sltiu $v0, $s0, VISIBLE_START 868 bnez $v0, _vbp_active_ret 869 nop 870 871 /* Start the visible region. */ 872 873 la $s1, visible_active 874 875 /* Reset the line address. */ 876 877 move $s2, $s3 878 879 /* Update the source address. */ 880 881 la $v0, DCH0SSA 882 sw $s2, 0($v0) 883 884 /* Enable the line channel. */ 885 886 la $v0, DCH0ECON 887 li $v1, (1 << 4) 888 sw $v1, SET($v0) 889 890 /* Disable the timer interrupt during the visible period. */ 891 892 la $v0, IEC0 893 li $v1, (1 << 9) 894 sw $v1, CLR($v0) /* T2IE = 0 */ 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 /* Clear the timer interrupt condition. */ 916 917 la $v0, IFS0 918 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 919 sw $v1, CLR($v0) 920 921 /* Re-enable the timer interrupt after the visible period. */ 922 923 la $v0, IEC0 924 li $v1, (1 << 9) 925 sw $v1, SET($v0) /* T2IE = 1 */ 926 927 _visible_active_ret: 928 jr $ra 929 nop 930 931 932 933 /* DMA update routine. */ 934 935 visible_update_address: 936 937 /* Test for the last visible line. */ 938 939 la $v0, vfp_active 940 bne $s1, $v0, _visible_update_address 941 nop 942 943 /* Disable the line channel. */ 944 945 la $v0, DCH0ECON 946 li $v1, (1 << 4) 947 sw $v1, CLR($v0) 948 949 j _visible_update_ret 950 nop 951 952 _visible_update_address: 953 954 /* 955 Update the line data address if the line counter (referring to the 956 next line) is even. 957 */ 958 959 andi $t8, $s0, 1 960 bnez $t8, _visible_update_ret 961 nop 962 963 /* Reference the next line and update the DMA source address. */ 964 965 addiu $s2, $s2, LINE_LENGTH 966 967 /* Test for wraparound. */ 968 969 li $t8, (SCREEN_BASE + SCREEN_SIZE) 970 sltu $t8, $s2, $t8 971 bnez $t8, _visible_dma_update 972 nop 973 974 /* Reset the source address. */ 975 976 li $s2, SCREEN_BASE 977 978 _visible_dma_update: 979 980 /* Disable line channel. */ 981 982 la $v0, DCH0CON 983 li $v1, 0b10000000 984 sw $v1, CLR($v0) 985 986 /* Update the source address. */ 987 988 la $v0, DCH0SSA 989 sw $s2, 0($v0) 990 991 /* Enable line channel. */ 992 993 la $v0, DCH0CON 994 sw $v1, SET($v0) 995 996 _visible_update_ret: 997 j irq_exit 998 nop 999 1000 1001 1002 /* Within the vertical front porch. */ 1003 1004 vfp_active: 1005 /* Test for vsync. */ 1006 1007 sltiu $v0, $s0, VSYNC_START 1008 bnez $v0, _vfp_active_ret 1009 nop 1010 1011 /* Start the vsync. */ 1012 1013 la $s1, vsync_active 1014 1015 /* Bring vsync low when the next line starts. */ 1016 1017 la $v0, OC2CON 1018 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1019 sw $v1, 0($v0) 1020 1021 _vfp_active_ret: 1022 jr $ra 1023 nop 1024 1025 1026 1027 /* The vsync period. */ 1028 1029 vsync_active: 1030 /* Test for front porch. */ 1031 1032 sltiu $v0, $s0, VSYNC_END 1033 bnez $v0, _vsync_active_ret 1034 nop 1035 1036 /* Start the back porch. */ 1037 1038 move $s0, $zero 1039 la $s1, vbp_active 1040 1041 /* Bring vsync high when the next line starts. */ 1042 1043 la $v0, OC2CON 1044 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1045 sw $v1, 0($v0) 1046 1047 _vsync_active_ret: 1048 jr $ra 1049 nop