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