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, (3 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 0 */ 433 sw $v1, CLR($v0) 434 435 /* Clear DMA interrupt flags. */ 436 437 la $v0, IFS1 438 li $v1, (3 << 28) /* IFS1<29:28> = 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 0 (x = 0). 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, DCH0CON 459 li $v1, 0b10011 460 sw $v1, 0($v0) 461 462 /* 463 Initialise a level reset channel. 464 The reset channel will be channel 1 (x = 1). 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, DCH1CON 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, DCH0ECON 490 li $v1, (9 << 8) 491 sw $v1, 0($v0) 492 493 /* 494 Initiate reset channel transfer when channel 0 is finished: 495 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 496 DCHxECON<4> = SIRQEN = 1 497 */ 498 499 la $v0, DCH1ECON 500 li $v1, (60 << 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, DCH0CSIZ 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, DCH1CSIZ 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, DCH0SSIZ 527 li $v1, LINE_LENGTH 528 sw $v1, 0($v0) 529 530 la $v0, DCH1SSIZ 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, DCH0SSA 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, DCH1SSA 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, DCH0DSIZ 560 li $v1, 1 561 sw $v1, 0($v0) 562 563 la $v0, DCH1DSIZ 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, DCH0DSA 572 li $v1, PORTB 573 li $t8, KSEG1_BASE 574 subu $v1, $v1, $t8 575 sw $v1, 0($v0) 576 577 la $v0, DCH1DSA 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, DCH0INT 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, 0b11111 /* DMA0IP, DMA0IS = 0 */ 593 sw $v1, CLR($v0) 594 595 la $v0, IPC10 596 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 597 sw $v1, SET($v0) 598 599 la $v0, IEC1 600 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 601 sw $v1, SET($v0) 602 603 /* Enable line channel. */ 604 605 la $v0, DCH0CON 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 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 724 sw $v1, CLR($v0) 725 726 /* 727 The timer interrupt will only occur outside the visible region, but the 728 interrupt condition will still occur as the timer wraps around. 729 Therefore, the handling of other interrupts may find the timer interrupt 730 condition set. 731 732 For the visible region, the event handler is invoked when handling the 733 DMA interrupt. Otherwise, the event handler is invoked in response to 734 the timer interrupt. 735 */ 736 737 la $t8, visible_active 738 beq $s1, $t8, irq_dma 739 nop 740 741 /* Increment the line counter (only outside the visible region). */ 742 743 addiu $s0, $s0, 1 744 745 /* Jump to the event handler (only outside the visible region). */ 746 747 jalr $s1 748 nop 749 750 irq_dma: 751 /* Check for a DMA interrupt condition. */ 752 753 la $v0, IFS1 754 lw $v1, 0($v0) 755 li $t8, (1 << 28) /* DMA0IF */ 756 and $v1, $v1, $t8 757 beqz $v1, irq_exit 758 nop 759 760 /* Clear the DMA interrupt condition. */ 761 762 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 763 sw $v1, CLR($v0) 764 765 /* Test the block transfer completion interrupt flag. */ 766 767 la $v0, DCH0INT 768 lw $v1, 0($v0) 769 andi $v1, $v1, (1 << 3) /* CHBCIF */ 770 beqz $v1, irq_exit 771 nop 772 773 /* Clear the block transfer completion interrupt flag. */ 774 775 li $v1, (1 << 3) /* CHBCIF = 0 */ 776 sw $v1, CLR($v0) 777 778 /* 779 The DMA interrupt should only be active within the visible region. 780 The event handler is invoked here instead of in response to a timer 781 interrupt within that region. 782 */ 783 784 /* Increment the line counter (only within the visible region). */ 785 786 addiu $s0, $s0, 1 787 788 /* Jump to the event handler (only within the visible region). */ 789 790 jalr $s1 791 nop 792 793 /* Jump to the DMA update routine. */ 794 795 j visible_update_address 796 nop 797 798 irq_exit: 799 /* Save state. */ 800 801 li $k0, IRQ_STACK_LIMIT 802 sw $s0, -44($k0) 803 sw $s1, -48($k0) 804 sw $s2, -52($k0) 805 sw $s3, -56($k0) 806 807 /* Restore affected registers. */ 808 809 lw $v0, -4($k0) 810 lw $v1, -8($k0) 811 lw $s0, -12($k0) 812 lw $s1, -16($k0) 813 lw $s2, -20($k0) 814 lw $s3, -24($k0) 815 lw $t8, -28($k0) 816 lw $ra, -32($k0) 817 lw $sp, -36($k0) 818 819 eret 820 nop 821 822 823 824 exc_handler: 825 li $t9, 0x80000000 826 mfc0 $t6, CP0_ERROREPC 827 nop 828 exc_loop: 829 and $t7, $t9, $t6 830 beqz $t7, exc_errorepc_zero 831 nop 832 exc_errorepc_one: 833 la $v0, PORTA 834 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 835 sw $v1, SET($v0) 836 j exc_loop_wait 837 nop 838 exc_errorepc_zero: 839 la $v0, PORTA 840 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 841 sw $v1, SET($v0) 842 exc_loop_wait: 843 li $t8, 5000000 844 exc_loop_delay: 845 addiu $t8, $t8, -1 846 bnez $t8, exc_loop_delay 847 nop 848 la $v0, PORTA 849 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 850 sw $v1, CLR($v0) 851 exc_loop_wait_again: 852 li $t8, 2500000 853 exc_loop_delay_again: 854 addiu $t8, $t8, -1 855 bnez $t8, exc_loop_delay_again 856 nop 857 exc_errorepc_next: 858 srl $t9, $t9, 1 859 bnez $t9, exc_loop 860 nop 861 j exc_handler 862 nop 863 864 865 866 /* Event routines. */ 867 868 /* The vertical back porch. */ 869 870 vbp_active: 871 /* Test for visible region. */ 872 873 sltiu $v0, $s0, VISIBLE_START 874 bnez $v0, _vbp_active_ret 875 nop 876 877 /* Start the visible region. */ 878 879 la $s1, visible_active 880 881 /* Reset the line address. */ 882 883 move $s2, $s3 884 885 /* Update the source address. */ 886 887 la $v0, DCH0SSA 888 sw $s2, 0($v0) 889 890 /* Enable the line channel for timer event transfer initiation. */ 891 892 la $v0, DCH0ECON 893 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 894 sw $v1, SET($v0) 895 896 /* Disable the timer interrupt during the visible period. */ 897 898 la $v0, IEC0 899 li $v1, (1 << 9) 900 sw $v1, CLR($v0) /* T2IE = 0 */ 901 902 _vbp_active_ret: 903 jr $ra 904 nop 905 906 907 908 /* The visible region. */ 909 910 visible_active: 911 /* Test for front porch. */ 912 913 sltiu $v0, $s0, VFP_START 914 bnez $v0, _visible_active_ret 915 nop 916 917 /* Start the front porch region. */ 918 919 la $s1, vfp_active 920 921 /* Clear the timer interrupt condition. */ 922 923 la $v0, IFS0 924 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 925 sw $v1, CLR($v0) 926 927 /* Re-enable the timer interrupt after the visible period. */ 928 929 la $v0, IEC0 930 li $v1, (1 << 9) 931 sw $v1, SET($v0) /* T2IE = 1 */ 932 933 _visible_active_ret: 934 jr $ra 935 nop 936 937 938 939 /* DMA update routine. */ 940 941 visible_update_address: 942 943 /* Test for the last visible line. */ 944 945 la $v0, vfp_active 946 bne $s1, $v0, _visible_update_address 947 nop 948 949 /* Disable the line channel. */ 950 951 la $v0, DCH0ECON 952 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 953 sw $v1, CLR($v0) 954 955 j _visible_update_ret 956 nop 957 958 _visible_update_address: 959 960 /* 961 Update the line data address if the line counter (referring to the 962 next line) is even. 963 */ 964 965 andi $t8, $s0, 1 966 bnez $t8, _visible_update_ret 967 nop 968 969 /* Reference the next line and update the DMA source address. */ 970 971 addiu $s2, $s2, LINE_LENGTH 972 973 /* Test for wraparound. */ 974 975 li $t8, (SCREEN_BASE + SCREEN_SIZE) 976 sltu $t8, $s2, $t8 977 bnez $t8, _visible_dma_update 978 nop 979 980 /* Reset the source address. */ 981 982 li $s2, SCREEN_BASE 983 984 _visible_dma_update: 985 986 /* Disable line channel. */ 987 988 la $v0, DCH0CON 989 li $v1, 0b10000000 990 sw $v1, CLR($v0) 991 992 /* Update the source address. */ 993 994 la $v0, DCH0SSA 995 sw $s2, 0($v0) 996 997 /* Enable line channel. */ 998 999 la $v0, DCH0CON 1000 sw $v1, SET($v0) 1001 1002 _visible_update_ret: 1003 j irq_exit 1004 nop 1005 1006 1007 1008 /* Within the vertical front porch. */ 1009 1010 vfp_active: 1011 /* Test for vsync. */ 1012 1013 sltiu $v0, $s0, VSYNC_START 1014 bnez $v0, _vfp_active_ret 1015 nop 1016 1017 /* Start the vsync. */ 1018 1019 la $s1, vsync_active 1020 1021 /* Bring vsync low when the next line starts. */ 1022 1023 la $v0, OC2CON 1024 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1025 sw $v1, 0($v0) 1026 1027 _vfp_active_ret: 1028 jr $ra 1029 nop 1030 1031 1032 1033 /* The vsync period. */ 1034 1035 vsync_active: 1036 /* Test for front porch. */ 1037 1038 sltiu $v0, $s0, VSYNC_END 1039 bnez $v0, _vsync_active_ret 1040 nop 1041 1042 /* Start the back porch. */ 1043 1044 move $s0, $zero 1045 la $s1, vbp_active 1046 1047 /* Bring vsync high when the next line starts. */ 1048 1049 la $v0, OC2CON 1050 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1051 sw $v1, 0($v0) 1052 1053 _vsync_active_ret: 1054 jr $ra 1055 nop