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