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