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 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 jr $ra 301 nop 302 303 304 305 /* 306 Output compare initialisation. 307 308 Timer2 will be used to trigger two events using OC1: one initiating the hsync 309 pulse, and one terminating the pulse. The pulse should appear after the line 310 data has been transferred using DMA, but this is achieved by just choosing 311 suitable start and end values. 312 313 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 314 reverse the level at a later point. In this way, the vsync pulse is generated 315 and is synchronised to the display lines. 316 */ 317 318 init_oc: 319 /* Disable OC1 interrupts. */ 320 321 la $v0, IEC0 322 li $v1, (1 << 7) /* IEC0<7> = OC1IE = 0 */ 323 sw $v1, CLR($v0) 324 325 la $v0, IFS0 326 li $v1, (1 << 7) /* IFS0<7> = OC1IF = 0 */ 327 sw $v1, CLR($v0) 328 329 /* Initialise OC1. */ 330 331 la $v0, OC1CON 332 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 333 sw $v1, 0($v0) 334 335 /* Pulse start and end. */ 336 337 la $v0, OC1R 338 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 339 sw $v1, 0($v0) 340 341 la $v0, OC1RS 342 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 343 sw $v1, 0($v0) 344 345 /* OC1 is enabled. */ 346 347 la $v0, OC1CON 348 li $v1, (1 << 15) 349 sw $v1, SET($v0) 350 351 /* Disable OC2 interrupts. */ 352 353 la $v0, IEC0 354 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 355 sw $v1, CLR($v0) 356 357 la $v0, IFS0 358 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 359 sw $v1, CLR($v0) 360 361 /* Initialise OC2. */ 362 363 la $v0, OC2CON 364 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 365 sw $v1, 0($v0) 366 367 /* Set pulse position. */ 368 369 la $v0, OC2R 370 sw $zero, 0($v0) 371 372 /* Enable OC2 later. */ 373 374 jr $ra 375 nop 376 377 init_oc_pins: 378 /* Unlock the configuration register bits. */ 379 380 la $v0, SYSKEY 381 sw $zero, 0($v0) 382 li $v1, 0xAA996655 383 sw $v1, 0($v0) 384 li $v1, 0x556699AA 385 sw $v1, 0($v0) 386 387 la $v0, CFGCON 388 lw $t8, 0($v0) 389 li $v1, (1 << 13) /* IOLOCK = 0 */ 390 sw $v1, CLR($v0) 391 392 /* Map OC1 to RPA0. */ 393 394 la $v0, RPA0R 395 li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ 396 sw $v1, 0($v0) 397 398 /* Map OC2 to RPA1. */ 399 400 la $v0, RPA1R 401 li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ 402 sw $v1, 0($v0) 403 404 la $v0, CFGCON 405 sw $t8, 0($v0) 406 407 /* Lock the oscillator control register again. */ 408 409 la $v0, SYSKEY 410 li $v1, 0x33333333 411 sw $v1, 0($v0) 412 413 jr $ra 414 nop 415 416 417 418 /* 419 Direct Memory Access initialisation. 420 421 Write 160 pixels to PORTB for the line data. This is initiated by a timer 422 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 423 address update routine, changing the source address of the DMA channel. 424 */ 425 426 init_dma: 427 /* Disable DMA interrupts. */ 428 429 la $v0, IEC1 430 li $v1, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 431 sw $v1, CLR($v0) 432 433 /* Clear DMA interrupt flags. */ 434 435 la $v0, IFS1 436 li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 437 sw $v1, CLR($v0) 438 439 /* Enable DMA. */ 440 441 la $v0, DMACON 442 li $v1, (1 << 15) 443 sw $v1, SET($v0) 444 445 /* 446 Initialise a start channel. 447 The start channel will be channel 0 (x = 0). 448 449 Specify a priority of 3: 450 DCHxCON<1:0> = CHPRI<1:0> = 3 451 452 Auto-enable the channel: 453 DCHxCON<4> = CHAEN = 1 454 */ 455 456 la $v0, DCH0CON 457 li $v1, 0b10011 458 sw $v1, 0($v0) 459 460 /* 461 Initialise line and level reset channels. 462 The line channel will be channel 1 (x = 1). 463 The reset channel will be channel 2 (x = 2). 464 465 Specify a priority of 3: 466 DCHxCON<1:0> = CHPRI<1:0> = 3 467 468 Chain the channel to channel 0: 469 DCHxCON<5> = CHCHN = 1 470 471 Allow the channel to receive events when disabled: 472 DCHxCON<6> = CHAED = 1 473 */ 474 475 la $v0, DCH1CON 476 li $v1, 0b1100011 477 sw $v1, 0($v0) 478 479 la $v0, DCH2CON 480 li $v1, 0b1100011 481 sw $v1, 0($v0) 482 483 /* 484 Initiate channel transfers when the initiating interrupt condition 485 occurs: 486 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 487 DCHxECON<4> = SIRQEN = 1 488 489 For now, however, prevent initiation by not setting SIRQEN. 490 */ 491 492 la $v0, DCH0ECON 493 li $v1, (9 << 8) 494 sw $v1, 0($v0) 495 496 /* 497 Initiate line channel transfer when channel 0 is finished: 498 Initiate reset channel transfer when channel 1 is finished: 499 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 or 1 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, DCH1CSIZ 517 li $v1, LINE_LENGTH 518 sw $v1, 0($v0) 519 520 /* 521 The start and reset channels have a cell size of a single zero byte: 522 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 523 */ 524 525 li $v1, 1 526 527 la $v0, DCH0CSIZ 528 sw $v1, 0($v0) 529 530 la $v0, DCH2CSIZ 531 sw $v1, 0($v0) 532 533 /* 534 The source has a size identical to the cell size: 535 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 536 */ 537 538 la $v0, DCH1SSIZ 539 li $v1, LINE_LENGTH 540 sw $v1, 0($v0) 541 542 li $v1, 1 543 544 la $v0, DCH0SSIZ 545 sw $v1, 0($v0) 546 547 la $v0, DCH2SSIZ 548 sw $v1, 0($v0) 549 550 /* 551 The source address is the physical address of the line data: 552 DCHxSSA = physical(line data address) 553 */ 554 555 la $v0, DCH1SSA 556 li $v1, SCREEN_BASE 557 sw $v1, 0($v0) 558 559 /* 560 For the start and reset channels, a single byte of zero is transferred: 561 DCHxSSA = physical(zero data address) 562 */ 563 564 la $v1, zerodata 565 li $t8, KSEG0_BASE 566 subu $v1, $v1, $t8 567 568 la $v0, DCH0SSA 569 sw $v1, 0($v0) 570 571 la $v0, DCH2SSA 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 li $v1, 1 580 581 la $v0, DCH0DSIZ 582 sw $v1, 0($v0) 583 584 la $v0, DCH1DSIZ 585 sw $v1, 0($v0) 586 587 la $v0, DCH2DSIZ 588 sw $v1, 0($v0) 589 590 /* 591 The destination address is the physical address of PORTB: 592 DCHxDSA = physical(PORTB) 593 */ 594 595 li $v1, PORTB 596 li $t8, KSEG1_BASE 597 subu $v1, $v1, $t8 598 599 la $v0, DCH0DSA 600 sw $v1, 0($v0) 601 602 la $v0, DCH1DSA 603 sw $v1, 0($v0) 604 605 la $v0, DCH2DSA 606 sw $v1, 0($v0) 607 608 /* 609 Use the block transfer completion interrupt to indicate when the source 610 address can be updated. 611 */ 612 613 la $v0, DCH0INT 614 li $v1, (1 << 19) /* CHBCIE = 1 */ 615 sw $v1, 0($v0) 616 617 la $v0, DCH1INT 618 li $v1, (1 << 19) /* CHBCIE = 1 */ 619 sw $v1, 0($v0) 620 621 /* Enable interrupt for address updating. */ 622 623 la $v0, IPC10 624 li $v1, 0b1111100011111 /* DMA1IP, DMA1IS, DMA0IP, DMA0IS = 0 */ 625 sw $v1, CLR($v0) 626 627 la $v0, IPC10 628 li $v1, 0b1111100011111 /* DMA1IP, DMA0IP = 7, DMA1IS, DMA0IS = 3 */ 629 sw $v1, SET($v0) 630 631 la $v0, IEC1 632 li $v1, (0b11 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 1 */ 633 sw $v1, SET($v0) 634 635 /* Enable start channel. */ 636 637 la $v0, DCH0CON 638 li $v1, 0b10000000 639 sw $v1, SET($v0) 640 641 jr $ra 642 nop 643 644 zerodata: 645 .word 0 646 647 648 649 /* Utilities. */ 650 651 handle_error_level: 652 mfc0 $t3, CP0_STATUS 653 li $t4, ~(STATUS_ERL | STATUS_EXL) 654 and $t3, $t3, $t4 655 mtc0 $t3, CP0_STATUS 656 jr $ra 657 nop 658 659 enable_interrupts: 660 mfc0 $t3, CP0_STATUS 661 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 662 and $t3, $t3, $t4 663 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 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, DCH1SSA 931 sw $s2, 0($v0) 932 933 /* Enable the start 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 /* 940 Suspend delivery of the timer interrupt during the visible period. 941 The condition still occurs, however. 942 */ 943 944 la $v0, IEC0 945 li $v1, (1 << 9) 946 sw $v1, CLR($v0) /* T2IE = 0 */ 947 948 la $v0, IPC2 949 li $v1, 0b11111 950 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 951 li $v1, 0b00111 952 sw $v1, SET($v0) /* T2IP = 1; T2IS = 3 */ 953 954 la $v0, IEC0 955 li $v1, (1 << 9) 956 sw $v1, SET($v0) /* T2IE = 0 */ 957 958 _vbp_active_ret: 959 jr $ra 960 nop 961 962 963 964 /* The visible region. */ 965 966 visible_active: 967 /* Test for front porch. */ 968 969 sltiu $v0, $s0, VFP_START 970 bnez $v0, _visible_active_ret 971 nop 972 973 /* Start the front porch region. */ 974 975 la $s1, vfp_active 976 977 /* Restore delivery of the timer interrupt after the visible period. */ 978 979 la $v0, IEC0 980 li $v1, (1 << 9) 981 sw $v1, CLR($v0) /* T2IE = 0 */ 982 983 la $v0, IPC2 984 li $v1, 0b11111 985 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 986 li $v1, 0b11111 987 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 988 989 la $v0, IEC0 990 li $v1, (1 << 9) 991 sw $v1, SET($v0) /* T2IE = 1 */ 992 993 _visible_active_ret: 994 jr $ra 995 nop 996 997 998 999 /* DMA update routine. */ 1000 1001 visible_update_address: 1002 1003 /* Test for the last visible line. */ 1004 1005 la $v0, vfp_active 1006 bne $s1, $v0, _visible_update_address 1007 nop 1008 1009 /* Disable the start channel. */ 1010 1011 la $v0, DCH0ECON 1012 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 1013 sw $v1, CLR($v0) 1014 1015 j _visible_update_ret 1016 nop 1017 1018 _visible_update_address: 1019 1020 /* 1021 Update the line data address if the line counter (referring to the 1022 next line) is even. 1023 */ 1024 1025 andi $t8, $s0, 1 1026 bnez $t8, _visible_update_ret 1027 nop 1028 1029 /* Reference the next line and update the DMA source address. */ 1030 1031 addiu $s2, $s2, LINE_LENGTH 1032 1033 /* Test for wraparound. */ 1034 1035 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1036 sltu $t8, $s2, $t8 1037 bnez $t8, _visible_dma_update 1038 nop 1039 1040 /* Reset the source address. */ 1041 1042 li $s2, SCREEN_BASE 1043 1044 _visible_dma_update: 1045 1046 /* Update the source address. */ 1047 1048 la $v0, DCH1SSA 1049 sw $s2, 0($v0) 1050 1051 _visible_update_ret: 1052 j irq_exit 1053 nop 1054 1055 1056 1057 /* Within the vertical front porch. */ 1058 1059 vfp_active: 1060 /* Test for vsync. */ 1061 1062 sltiu $v0, $s0, VSYNC_START 1063 bnez $v0, _vfp_active_ret 1064 nop 1065 1066 /* Start the vsync. */ 1067 1068 la $s1, vsync_active 1069 1070 /* Bring vsync low when the next line starts. */ 1071 1072 la $v0, OC2CON 1073 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1074 sw $v1, 0($v0) 1075 1076 _vfp_active_ret: 1077 jr $ra 1078 nop 1079 1080 1081 1082 /* The vsync period. */ 1083 1084 vsync_active: 1085 /* Test for front porch. */ 1086 1087 sltiu $v0, $s0, VSYNC_END 1088 bnez $v0, _vsync_active_ret 1089 nop 1090 1091 /* Start the back porch. */ 1092 1093 move $s0, $zero 1094 la $s1, vbp_active 1095 1096 /* Bring vsync high when the next line starts. */ 1097 1098 la $v0, OC2CON 1099 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1100 sw $v1, 0($v0) 1101 1102 _vsync_active_ret: 1103 jr $ra 1104 nop