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