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