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, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 433 sw $v1, CLR($v0) 434 435 /* Clear DMA interrupt flags. */ 436 437 la $v0, IFS1 438 li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, 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 la $v0, DCH2CON 481 li $v1, 0b1100011 482 sw $v1, 0($v0) 483 484 /* 485 Initiate channel transfers when the initiating interrupt condition 486 occurs: 487 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 488 DCHxECON<4> = SIRQEN = 1 489 490 For now, however, prevent initiation by not setting SIRQEN. 491 */ 492 493 la $v0, DCH0ECON 494 li $v1, (9 << 8) 495 sw $v1, 0($v0) 496 497 /* 498 Initiate reset channel transfer when channel 0 is finished: 499 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 500 DCHxECON<4> = SIRQEN = 1 501 */ 502 503 la $v0, DCH1ECON 504 li $v1, (60 << 8) | (1 << 4) 505 sw $v1, 0($v0) 506 507 la $v0, DCH2ECON 508 li $v1, (61 << 8) | (1 << 4) 509 sw $v1, 0($v0) 510 511 /* 512 The line channel has a cell size of the number bytes in a line: 513 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 514 */ 515 516 la $v0, DCH0CSIZ 517 li $v1, LINE_LENGTH 518 sw $v1, 0($v0) 519 520 /* 521 The reset channel has a cell size of a single zero byte: 522 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 523 */ 524 525 la $v0, DCH1CSIZ 526 li $v1, 1 527 sw $v1, 0($v0) 528 529 la $v0, DCH2CSIZ 530 sw $v1, 0($v0) 531 532 /* 533 The source has a size identical to the cell size: 534 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 535 */ 536 537 la $v0, DCH0SSIZ 538 li $v1, LINE_LENGTH 539 sw $v1, 0($v0) 540 541 la $v0, DCH1SSIZ 542 li $v1, 1 543 sw $v1, 0($v0) 544 545 la $v0, DCH2SSIZ 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, fulldata 564 li $t8, KSEG0_BASE 565 subu $v1, $v1, $t8 566 sw $v1, 0($v0) 567 568 la $v0, DCH2SSA 569 la $v1, zerodata 570 li $t8, KSEG0_BASE 571 subu $v1, $v1, $t8 572 sw $v1, 0($v0) 573 574 /* 575 The destination has a size of 1 byte: 576 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 577 */ 578 579 la $v0, DCH0DSIZ 580 li $v1, 1 581 sw $v1, 0($v0) 582 583 la $v0, DCH1DSIZ 584 sw $v1, 0($v0) 585 586 la $v0, DCH2DSIZ 587 sw $v1, 0($v0) 588 589 /* 590 The destination address is the physical address of PORTB: 591 DCHxDSA = physical(PORTB) 592 */ 593 594 la $v0, DCH0DSA 595 li $v1, PORTB 596 li $t8, KSEG1_BASE 597 subu $v1, $v1, $t8 598 sw $v1, 0($v0) 599 600 la $v0, DCH1DSA 601 sw $v1, 0($v0) 602 603 la $v0, DCH2DSA 604 sw $v1, 0($v0) 605 606 /* 607 Use the block transfer completion interrupt to indicate when the source 608 address can be updated. 609 */ 610 611 la $v0, DCH0INT 612 li $v1, (1 << 19) /* CHBCIE = 1 */ 613 sw $v1, 0($v0) 614 615 la $v0, DCH1INT 616 li $v1, (1 << 19) /* CHBCIE = 1 */ 617 sw $v1, 0($v0) 618 619 /* Enable interrupt for address updating. */ 620 621 la $v0, IPC10 622 li $v1, 0b1111100011111 /* DMA1IP, DMA1IS, DMA0IP, DMA0IS = 0 */ 623 sw $v1, CLR($v0) 624 625 la $v0, IPC10 626 li $v1, 0b1111100011111 /* DMA1IP, DMA0IP = 7, DMA1IS, DMA0IS = 3 */ 627 sw $v1, SET($v0) 628 629 la $v0, IEC1 630 li $v1, (0b11 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 1 */ 631 sw $v1, SET($v0) 632 633 /* Enable line channel. */ 634 635 la $v0, DCH0CON 636 li $v1, 0b10000000 637 sw $v1, SET($v0) 638 639 jr $ra 640 nop 641 642 zerodata: 643 .word 0 644 645 fulldata: 646 .word 255 647 648 649 650 /* Utilities. */ 651 652 handle_error_level: 653 mfc0 $t3, CP0_STATUS 654 li $t4, ~(STATUS_ERL | STATUS_EXL) 655 and $t3, $t3, $t4 656 mtc0 $t3, CP0_STATUS 657 jr $ra 658 nop 659 660 enable_interrupts: 661 mfc0 $t3, CP0_STATUS 662 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 663 and $t3, $t3, $t4 664 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 665 and $t3, $t3, $t4 666 ori $t3, $t3, STATUS_IE 667 mtc0 $t3, CP0_STATUS 668 jr $ra 669 nop 670 671 init_interrupts: 672 mfc0 $t3, CP0_DEBUG 673 li $t4, ~DEBUG_DM 674 and $t3, $t3, $t4 675 mtc0 $t3, CP0_DEBUG 676 677 mfc0 $t3, CP0_STATUS 678 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 679 or $t3, $t3, $t4 680 mtc0 $t3, CP0_STATUS 681 682 la $t3, exception_handler 683 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 684 685 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 686 mtc0 $t3, CP0_INTCTL 687 688 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 689 mtc0 $t3, CP0_CAUSE 690 691 jr $ra 692 nop 693 694 695 696 /* Exception servicing. */ 697 698 .section .flash, "a" 699 700 /* TLB error servicing. */ 701 702 tlb_handler: 703 j exception_handler 704 nop 705 706 707 708 /* General exception servicing. */ 709 710 .org 0x180 711 712 exception_handler: 713 j exc_handler 714 nop 715 716 717 718 /* Interrupt servicing. */ 719 720 .org 0x200 721 722 interrupt_handler: 723 724 /* Store affected registers. */ 725 726 li $k0, IRQ_STACK_LIMIT 727 sw $v0, -4($k0) 728 sw $v1, -8($k0) 729 sw $s0, -12($k0) 730 sw $s1, -16($k0) 731 sw $s2, -20($k0) 732 sw $s3, -24($k0) 733 sw $t8, -28($k0) 734 sw $ra, -32($k0) 735 sw $sp, -36($k0) 736 737 /* Load state. */ 738 739 lw $s0, -44($k0) 740 lw $s1, -48($k0) 741 lw $s2, -52($k0) 742 lw $s3, -56($k0) 743 744 li $sp, IRQ_STACK_TOP 745 746 /* Check for a timer interrupt condition. */ 747 748 la $v0, IFS0 749 lw $v1, 0($v0) 750 andi $v1, $v1, (1 << 9) /* T2IF */ 751 beqz $v1, irq_dma 752 nop 753 754 /* Clear the timer interrupt condition. */ 755 756 sw $v1, CLR($v0) 757 758 /* 759 The timer interrupt will only occur outside the visible region, but the 760 interrupt condition will still occur as the timer wraps around. 761 Therefore, the handling of other interrupts may find the timer interrupt 762 condition set. 763 764 For the visible region, the event handler is invoked when handling the 765 DMA interrupt. Otherwise, the event handler is invoked in response to 766 the timer interrupt. 767 */ 768 769 la $t8, visible_active 770 beq $s1, $t8, irq_dma 771 nop 772 773 /* Increment the line counter (only outside the visible region). */ 774 775 addiu $s0, $s0, 1 776 777 /* Jump to the event handler (only outside the visible region). */ 778 779 jalr $s1 780 nop 781 782 irq_dma: 783 /* Check for a DMA interrupt condition. */ 784 785 la $v0, IFS1 786 lw $v1, 0($v0) 787 li $t8, (0b11 << 28) /* DMA1IF, DMA0IF */ 788 and $v1, $v1, $t8 789 beqz $v1, irq_exit 790 nop 791 792 /* Clear the DMA interrupt condition. */ 793 794 sw $v1, CLR($v0) 795 796 /* Test the block transfer completion interrupt flag. */ 797 798 la $v0, DCH0INT 799 lw $v1, 0($v0) 800 andi $v1, $v1, (1 << 3) /* CHBCIF */ 801 beqz $v1, irq_dma_next 802 nop 803 804 /* Clear the block transfer completion interrupt flag. */ 805 806 sw $v1, CLR($v0) 807 808 irq_dma_next: 809 /* Test the block transfer completion interrupt flag. */ 810 811 la $v0, DCH1INT 812 lw $v1, 0($v0) 813 andi $v1, $v1, (1 << 3) /* CHBCIF */ 814 beqz $v1, irq_exit 815 nop 816 817 /* Clear the block transfer completion interrupt flag. */ 818 819 sw $v1, CLR($v0) 820 821 /* 822 The DMA interrupt should only be active within the visible region. 823 The event handler is invoked here instead of in response to a timer 824 interrupt within that region. 825 */ 826 827 /* Increment the line counter (only within the visible region). */ 828 829 addiu $s0, $s0, 1 830 831 /* Jump to the event handler (only within the visible region). */ 832 833 jalr $s1 834 nop 835 836 /* Jump to the DMA update routine. */ 837 838 j visible_update_address 839 nop 840 841 irq_exit: 842 /* Save state. */ 843 844 li $k0, IRQ_STACK_LIMIT 845 sw $s0, -44($k0) 846 sw $s1, -48($k0) 847 sw $s2, -52($k0) 848 sw $s3, -56($k0) 849 850 /* Restore affected registers. */ 851 852 lw $v0, -4($k0) 853 lw $v1, -8($k0) 854 lw $s0, -12($k0) 855 lw $s1, -16($k0) 856 lw $s2, -20($k0) 857 lw $s3, -24($k0) 858 lw $t8, -28($k0) 859 lw $ra, -32($k0) 860 lw $sp, -36($k0) 861 862 eret 863 nop 864 865 866 867 exc_handler: 868 li $t9, 0x80000000 869 mfc0 $t6, CP0_ERROREPC 870 nop 871 exc_loop: 872 and $t7, $t9, $t6 873 beqz $t7, exc_errorepc_zero 874 nop 875 exc_errorepc_one: 876 la $v0, PORTA 877 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 878 sw $v1, SET($v0) 879 j exc_loop_wait 880 nop 881 exc_errorepc_zero: 882 la $v0, PORTA 883 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 884 sw $v1, SET($v0) 885 exc_loop_wait: 886 li $t8, 5000000 887 exc_loop_delay: 888 addiu $t8, $t8, -1 889 bnez $t8, exc_loop_delay 890 nop 891 la $v0, PORTA 892 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 893 sw $v1, CLR($v0) 894 exc_loop_wait_again: 895 li $t8, 2500000 896 exc_loop_delay_again: 897 addiu $t8, $t8, -1 898 bnez $t8, exc_loop_delay_again 899 nop 900 exc_errorepc_next: 901 srl $t9, $t9, 1 902 bnez $t9, exc_loop 903 nop 904 j exc_handler 905 nop 906 907 908 909 /* Event routines. */ 910 911 /* The vertical back porch. */ 912 913 vbp_active: 914 /* Test for visible region. */ 915 916 sltiu $v0, $s0, VISIBLE_START 917 bnez $v0, _vbp_active_ret 918 nop 919 920 /* Start the visible region. */ 921 922 la $s1, visible_active 923 924 /* Reset the line address. */ 925 926 move $s2, $s3 927 928 /* Update the source address. */ 929 930 la $v0, DCH0SSA 931 sw $s2, 0($v0) 932 933 /* Enable the line channel for timer event transfer initiation. */ 934 935 la $v0, DCH0ECON 936 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 937 sw $v1, SET($v0) 938 939 /* Disable the timer interrupt during the visible period. */ 940 941 la $v0, IEC0 942 li $v1, (1 << 9) 943 sw $v1, CLR($v0) /* T2IE = 0 */ 944 945 _vbp_active_ret: 946 jr $ra 947 nop 948 949 950 951 /* The visible region. */ 952 953 visible_active: 954 /* Test for front porch. */ 955 956 sltiu $v0, $s0, VFP_START 957 bnez $v0, _visible_active_ret 958 nop 959 960 /* Start the front porch region. */ 961 962 la $s1, vfp_active 963 964 /* Re-enable the timer interrupt after the visible period. */ 965 966 la $v0, IEC0 967 li $v1, (1 << 9) 968 sw $v1, SET($v0) /* T2IE = 1 */ 969 970 _visible_active_ret: 971 jr $ra 972 nop 973 974 975 976 /* DMA update routine. */ 977 978 visible_update_address: 979 980 /* Test for the last visible line. */ 981 982 la $v0, vfp_active 983 bne $s1, $v0, _visible_update_address 984 nop 985 986 /* Disable the line channel. */ 987 988 la $v0, DCH0ECON 989 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 990 sw $v1, CLR($v0) 991 992 j _visible_update_ret 993 nop 994 995 _visible_update_address: 996 997 /* 998 Update the line data address if the line counter (referring to the 999 next line) is even. 1000 */ 1001 1002 andi $t8, $s0, 1 1003 bnez $t8, _visible_update_ret 1004 nop 1005 1006 /* Reference the next line and update the DMA source address. */ 1007 1008 addiu $s2, $s2, LINE_LENGTH 1009 1010 /* Test for wraparound. */ 1011 1012 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1013 sltu $t8, $s2, $t8 1014 bnez $t8, _visible_dma_update 1015 nop 1016 1017 /* Reset the source address. */ 1018 1019 li $s2, SCREEN_BASE 1020 1021 _visible_dma_update: 1022 1023 /* Update the source address. */ 1024 1025 la $v0, DCH0SSA 1026 sw $s2, 0($v0) 1027 1028 _visible_update_ret: 1029 j irq_exit 1030 nop 1031 1032 1033 1034 /* Within the vertical front porch. */ 1035 1036 vfp_active: 1037 /* Test for vsync. */ 1038 1039 sltiu $v0, $s0, VSYNC_START 1040 bnez $v0, _vfp_active_ret 1041 nop 1042 1043 /* Start the vsync. */ 1044 1045 la $s1, vsync_active 1046 1047 /* Bring vsync low when the next line starts. */ 1048 1049 la $v0, OC2CON 1050 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1051 sw $v1, 0($v0) 1052 1053 _vfp_active_ret: 1054 jr $ra 1055 nop 1056 1057 1058 1059 /* The vsync period. */ 1060 1061 vsync_active: 1062 /* Test for front porch. */ 1063 1064 sltiu $v0, $s0, VSYNC_END 1065 bnez $v0, _vsync_active_ret 1066 nop 1067 1068 /* Start the back porch. */ 1069 1070 move $s0, $zero 1071 la $s1, vbp_active 1072 1073 /* Bring vsync high when the next line starts. */ 1074 1075 la $v0, OC2CON 1076 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1077 sw $v1, 0($v0) 1078 1079 _vsync_active_ret: 1080 jr $ra 1081 nop