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