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