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 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 start 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 646 647 /* Utilities. */ 648 649 handle_error_level: 650 mfc0 $t3, CP0_STATUS 651 li $t4, ~(STATUS_ERL | STATUS_EXL) 652 and $t3, $t3, $t4 653 mtc0 $t3, CP0_STATUS 654 jr $ra 655 nop 656 657 enable_interrupts: 658 mfc0 $t3, CP0_STATUS 659 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 660 and $t3, $t3, $t4 661 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 662 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 663 and $t3, $t3, $t4 664 ori $t3, $t3, STATUS_IE 665 mtc0 $t3, CP0_STATUS 666 jr $ra 667 nop 668 669 init_interrupts: 670 mfc0 $t3, CP0_DEBUG 671 li $t4, ~DEBUG_DM 672 and $t3, $t3, $t4 673 mtc0 $t3, CP0_DEBUG 674 675 mfc0 $t3, CP0_STATUS 676 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 677 or $t3, $t3, $t4 678 mtc0 $t3, CP0_STATUS 679 680 la $t3, exception_handler 681 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 682 683 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 684 mtc0 $t3, CP0_INTCTL 685 686 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 687 mtc0 $t3, CP0_CAUSE 688 689 jr $ra 690 nop 691 692 693 694 /* Exception servicing. */ 695 696 .section .flash, "a" 697 698 /* TLB error servicing. */ 699 700 tlb_handler: 701 j exception_handler 702 nop 703 704 705 706 /* General exception servicing. */ 707 708 .org 0x180 709 710 exception_handler: 711 j exc_handler 712 nop 713 714 715 716 /* Interrupt servicing. */ 717 718 .org 0x200 719 720 interrupt_handler: 721 722 /* Store affected registers. */ 723 724 li $k0, IRQ_STACK_LIMIT 725 sw $v0, -4($k0) 726 sw $v1, -8($k0) 727 sw $s0, -12($k0) 728 sw $s1, -16($k0) 729 sw $s2, -20($k0) 730 sw $s3, -24($k0) 731 sw $t8, -28($k0) 732 sw $ra, -32($k0) 733 sw $sp, -36($k0) 734 735 /* Load state. */ 736 737 lw $s0, -44($k0) 738 lw $s1, -48($k0) 739 lw $s2, -52($k0) 740 lw $s3, -56($k0) 741 742 li $sp, IRQ_STACK_TOP 743 744 /* Check for a timer interrupt condition. */ 745 746 la $v0, IFS0 747 lw $v1, 0($v0) 748 andi $v1, $v1, (1 << 9) /* T2IF */ 749 beqz $v1, irq_dma 750 nop 751 752 /* Clear the timer interrupt condition. */ 753 754 sw $v1, CLR($v0) 755 756 /* 757 The timer interrupt will only occur outside the visible region, but the 758 interrupt condition will still occur as the timer wraps around. 759 Therefore, the handling of other interrupts may find the timer interrupt 760 condition set. 761 762 For the visible region, the event handler is invoked when handling the 763 DMA interrupt. Otherwise, the event handler is invoked in response to 764 the timer interrupt. 765 */ 766 767 la $t8, visible_active 768 beq $s1, $t8, irq_dma 769 nop 770 771 /* Increment the line counter (only outside the visible region). */ 772 773 addiu $s0, $s0, 1 774 775 /* Jump to the event handler (only outside the visible region). */ 776 777 jalr $s1 778 nop 779 780 irq_dma: 781 /* Check for a DMA interrupt condition. */ 782 783 la $v0, IFS1 784 lw $v1, 0($v0) 785 li $t8, (0b11 << 28) /* DMA1IF, DMA0IF */ 786 and $v1, $v1, $t8 787 beqz $v1, irq_exit 788 nop 789 790 /* Clear the DMA interrupt condition. */ 791 792 sw $v1, CLR($v0) 793 794 /* Test the block transfer completion interrupt flag. */ 795 796 la $v0, DCH0INT 797 lw $v1, 0($v0) 798 andi $v1, $v1, (1 << 3) /* CHBCIF */ 799 beqz $v1, irq_dma_next 800 nop 801 802 /* Clear the block transfer completion interrupt flag. */ 803 804 sw $v1, CLR($v0) 805 806 irq_dma_next: 807 /* Test the block transfer completion interrupt flag. */ 808 809 la $v0, DCH1INT 810 lw $v1, 0($v0) 811 andi $v1, $v1, (1 << 3) /* CHBCIF */ 812 beqz $v1, irq_exit 813 nop 814 815 /* Clear the block transfer completion interrupt flag. */ 816 817 sw $v1, CLR($v0) 818 819 /* 820 The DMA interrupt should only be active within the visible region. 821 The event handler is invoked here instead of in response to a timer 822 interrupt within that region. 823 */ 824 825 /* Increment the line counter (only within the visible region). */ 826 827 addiu $s0, $s0, 1 828 829 /* Jump to the event handler (only within the visible region). */ 830 831 jalr $s1 832 nop 833 834 /* Jump to the DMA update routine. */ 835 836 j visible_update_address 837 nop 838 839 irq_exit: 840 /* Save state. */ 841 842 li $k0, IRQ_STACK_LIMIT 843 sw $s0, -44($k0) 844 sw $s1, -48($k0) 845 sw $s2, -52($k0) 846 sw $s3, -56($k0) 847 848 /* Restore affected registers. */ 849 850 lw $v0, -4($k0) 851 lw $v1, -8($k0) 852 lw $s0, -12($k0) 853 lw $s1, -16($k0) 854 lw $s2, -20($k0) 855 lw $s3, -24($k0) 856 lw $t8, -28($k0) 857 lw $ra, -32($k0) 858 lw $sp, -36($k0) 859 860 eret 861 nop 862 863 864 865 exc_handler: 866 li $t9, 0x80000000 867 mfc0 $t6, CP0_ERROREPC 868 nop 869 exc_loop: 870 and $t7, $t9, $t6 871 beqz $t7, exc_errorepc_zero 872 nop 873 exc_errorepc_one: 874 la $v0, PORTA 875 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 876 sw $v1, SET($v0) 877 j exc_loop_wait 878 nop 879 exc_errorepc_zero: 880 la $v0, PORTA 881 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 882 sw $v1, SET($v0) 883 exc_loop_wait: 884 li $t8, 5000000 885 exc_loop_delay: 886 addiu $t8, $t8, -1 887 bnez $t8, exc_loop_delay 888 nop 889 la $v0, PORTA 890 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 891 sw $v1, CLR($v0) 892 exc_loop_wait_again: 893 li $t8, 2500000 894 exc_loop_delay_again: 895 addiu $t8, $t8, -1 896 bnez $t8, exc_loop_delay_again 897 nop 898 exc_errorepc_next: 899 srl $t9, $t9, 1 900 bnez $t9, exc_loop 901 nop 902 j exc_handler 903 nop 904 905 906 907 /* Event routines. */ 908 909 /* The vertical back porch. */ 910 911 vbp_active: 912 /* Test for visible region. */ 913 914 sltiu $v0, $s0, VISIBLE_START 915 bnez $v0, _vbp_active_ret 916 nop 917 918 /* Start the visible region. */ 919 920 la $s1, visible_active 921 922 /* Reset the line address. */ 923 924 move $s2, $s3 925 926 /* Update the source address. */ 927 928 la $v0, DCH1SSA 929 sw $s2, 0($v0) 930 931 /* 932 Suspend delivery of the timer interrupt during the visible period. 933 The condition still occurs, however. 934 */ 935 936 la $v0, IEC0 937 li $v1, (1 << 9) 938 sw $v1, CLR($v0) /* T2IE = 0 */ 939 940 la $v0, IPC2 941 li $v1, 0b11111 942 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 943 li $v1, 0b00111 944 sw $v1, SET($v0) /* T2IP = 1; T2IS = 3 */ 945 946 la $v0, IEC0 947 li $v1, (1 << 9) 948 sw $v1, SET($v0) /* T2IE = 0 */ 949 950 /* Enable the start channel for timer event transfer initiation. */ 951 952 la $v0, DCH0ECON 953 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 954 sw $v1, SET($v0) 955 956 _vbp_active_ret: 957 jr $ra 958 nop 959 960 961 962 /* The visible region. */ 963 964 visible_active: 965 /* Test for front porch. */ 966 967 sltiu $v0, $s0, VFP_START 968 bnez $v0, _visible_active_ret 969 nop 970 971 /* Start the front porch region. */ 972 973 la $s1, vfp_active 974 975 /* Restore delivery of the timer interrupt after the visible period. */ 976 977 la $v0, IEC0 978 li $v1, (1 << 9) 979 sw $v1, CLR($v0) /* T2IE = 0 */ 980 981 la $v0, IPC2 982 li $v1, 0b11111 983 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 984 li $v1, 0b11111 985 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 986 987 la $v0, IEC0 988 li $v1, (1 << 9) 989 sw $v1, SET($v0) /* T2IE = 1 */ 990 991 _visible_active_ret: 992 jr $ra 993 nop 994 995 996 997 /* DMA update routine. */ 998 999 visible_update_address: 1000 1001 /* Test for the last visible line. */ 1002 1003 la $v0, vfp_active 1004 bne $s1, $v0, _visible_update_address 1005 nop 1006 1007 /* Disable the start channel. */ 1008 1009 la $v0, DCH0ECON 1010 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 1011 sw $v1, CLR($v0) 1012 1013 j _visible_update_ret 1014 nop 1015 1016 _visible_update_address: 1017 1018 /* 1019 Update the line data address if the line counter (referring to the 1020 next line) is even. 1021 */ 1022 1023 andi $t8, $s0, 1 1024 bnez $t8, _visible_update_ret 1025 nop 1026 1027 /* Reference the next line and update the DMA source address. */ 1028 1029 addiu $s2, $s2, LINE_LENGTH 1030 1031 /* Test for wraparound. */ 1032 1033 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1034 sltu $t8, $s2, $t8 1035 bnez $t8, _visible_dma_update 1036 nop 1037 1038 /* Reset the source address. */ 1039 1040 li $s2, SCREEN_BASE 1041 1042 _visible_dma_update: 1043 1044 /* Update the source address. */ 1045 1046 la $v0, DCH1SSA 1047 sw $s2, 0($v0) 1048 1049 _visible_update_ret: 1050 j irq_exit 1051 nop 1052 1053 1054 1055 /* Within the vertical front porch. */ 1056 1057 vfp_active: 1058 /* Test for vsync. */ 1059 1060 sltiu $v0, $s0, VSYNC_START 1061 bnez $v0, _vfp_active_ret 1062 nop 1063 1064 /* Start the vsync. */ 1065 1066 la $s1, vsync_active 1067 1068 /* Bring vsync low when the next line starts. */ 1069 1070 la $v0, OC2CON 1071 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1072 sw $v1, 0($v0) 1073 1074 _vfp_active_ret: 1075 jr $ra 1076 nop 1077 1078 1079 1080 /* The vsync period. */ 1081 1082 vsync_active: 1083 /* Test for front porch. */ 1084 1085 sltiu $v0, $s0, VSYNC_END 1086 bnez $v0, _vsync_active_ret 1087 nop 1088 1089 /* Start the back porch. */ 1090 1091 move $s0, $zero 1092 la $s1, vbp_active 1093 1094 /* Bring vsync high when the next line starts. */ 1095 1096 la $v0, OC2CON 1097 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1098 sw $v1, 0($v0) 1099 1100 _vsync_active_ret: 1101 jr $ra 1102 nop