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 .macro load_affected 65 lw $v0, -4($k0) 66 lw $v1, -8($k0) 67 lw $s0, -12($k0) 68 lw $s1, -16($k0) 69 lw $s2, -20($k0) 70 lw $s3, -24($k0) 71 lw $t8, -28($k0) 72 lw $ra, -32($k0) 73 lw $sp, -36($k0) 74 .endm 75 76 .macro load_state 77 lw $s0, -44($k0) 78 lw $s1, -48($k0) 79 lw $s2, -52($k0) 80 lw $s3, -56($k0) 81 .endm 82 83 .macro save_affected 84 sw $v0, -4($k0) 85 sw $v1, -8($k0) 86 sw $s0, -12($k0) 87 sw $s1, -16($k0) 88 sw $s2, -20($k0) 89 sw $s3, -24($k0) 90 sw $t8, -28($k0) 91 sw $ra, -32($k0) 92 sw $sp, -36($k0) 93 .endm 94 95 .macro save_state 96 sw $s0, -44($k0) 97 sw $s1, -48($k0) 98 sw $s2, -52($k0) 99 sw $s3, -56($k0) 100 .endm 101 102 _start: 103 /* 104 Configure RAM. 105 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 106 */ 107 108 la $v0, BMXCON 109 lw $v1, 0($v0) 110 111 /* Set zero wait states for address setup. */ 112 113 li $t8, ~(1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 114 and $v1, $v1, $t8 115 116 /* Set bus arbitration mode. */ 117 118 li $t8, ~0b111 /* BMXCON<2:0> = BMXARB<2:0> = 0 */ 119 ori $t8, $t8, 0b010 /* BMXCON<2:0> = BMXARB<2:0> = 2 */ 120 and $v1, $v1, $t8 121 sw $v1, 0($v0) 122 123 /* Enable caching. */ 124 125 mfc0 $v1, CP0_CONFIG 126 li $t8, ~CONFIG_K0 127 and $v1, $v1, $t8 128 ori $v1, $v1, CONFIG_K0_CACHABLE_NONCOHERENT 129 mtc0 $v1, CP0_CONFIG 130 nop 131 132 /* Get the RAM size. */ 133 134 la $v0, BMXDRMSZ 135 lw $t0, 0($v0) 136 137 /* Initialise the stack pointer. */ 138 139 li $v1, KSEG0_BASE 140 addu $sp, $t0, $v1 /* sp = KSEG0_BASE + RAM size */ 141 142 /* Initialise the globals pointer. */ 143 144 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 145 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 146 147 /* Set pins for output. */ 148 149 jal init_pins 150 nop 151 152 la $t0, PORTA 153 li $t1, (1 << 3) /* PORTA<3> = RA3 */ 154 sw $t1, CLR($t0) 155 156 jal init_io_pins 157 nop 158 159 /* Initialise the status register. */ 160 161 jal init_interrupts 162 nop 163 164 /* Initialise framebuffer. */ 165 166 la $a0, screendata 167 jal init_framebuffer 168 nop 169 170 sync 171 172 /* Initialise timer. */ 173 174 jal init_timer2 175 nop 176 177 /* Initialise DMA. */ 178 179 jal init_dma 180 nop 181 182 /* Initialise OC1 and OC2. */ 183 184 jal init_oc 185 nop 186 187 /* Initialise UART for debugging. */ 188 189 jal init_uart 190 nop 191 192 /* Initialise the display state. */ 193 194 li $s0, 0 /* line counter */ 195 la $s1, vbp_active /* current event */ 196 li $s2, SCREEN_BASE /* line address */ 197 li $s3, SCREEN_BASE /* screen address */ 198 199 /* Save the state for retrieval in the interrupt handler. */ 200 201 li $k0, IRQ_STACK_LIMIT 202 save_state 203 204 /* Enable interrupts and loop. */ 205 206 jal enable_interrupts 207 nop 208 209 jal handle_error_level 210 nop 211 212 /* Main program. */ 213 214 li $a1, (3 << 24) /* counter ~= 50000000 */ 215 li $a2, 0xffffff /* test counter at every 1/4 of range */ 216 move $t2, $zero /* picture to show */ 217 218 /* Monitoring loop. */ 219 loop: 220 addiu $a1, $a1, -1 /* counter -= 1 */ 221 and $t1, $a2, $a1 222 bnez $t1, loop 223 nop 224 225 la $t0, PORTA 226 li $t1, (1 << 3) /* PORTA<3> = RA3 */ 227 sw $t1, INV($t0) 228 229 la $v0, U1TXREG 230 li $v1, '.' 231 sw $v1, 0($v0) 232 233 bnez $a1, loop /* until counter == 0 */ 234 nop 235 236 bnez $t2, _picture1 237 nop 238 239 /* Show picture 0. */ 240 241 la $a0, screendata 242 jal init_framebuffer 243 nop 244 245 la $a0, message0 246 li $a1, SCREEN_BASE_KSEG0 247 jal blit_string 248 nop 249 250 li $t2, 1 251 j _next 252 nop 253 254 _picture1: 255 /* Show picture 1. */ 256 257 jal init_framebuffer_with_pattern 258 nop 259 260 la $a0, message1 261 li $a1, SCREEN_BASE_KSEG0 262 jal blit_string 263 nop 264 265 move $t2, $zero 266 267 _next: 268 li $a1, (3 << 24) /* counter ~= 50000000 */ 269 li $a2, 0xffffff /* test counter at every 1/4 of range */ 270 j loop 271 nop 272 273 274 275 init_pins: 276 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 277 278 la $v0, CFGCON 279 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 280 sw $v1, CLR($v0) 281 282 init_outputs: 283 /* Remove analogue features from pins. */ 284 285 la $v0, ANSELA 286 sw $zero, 0($v0) /* ANSELA = 0 */ 287 la $v0, ANSELB 288 sw $zero, 0($v0) /* ANSELB = 0 */ 289 290 la $v0, TRISA 291 sw $zero, 0($v0) 292 la $v0, TRISB 293 sw $zero, 0($v0) 294 295 la $v0, PORTA 296 sw $zero, 0($v0) 297 la $v0, PORTB 298 sw $zero, 0($v0) 299 300 jr $ra 301 nop 302 303 304 305 /* Initialisation routines. */ 306 307 init_timer2: 308 309 /* Initialise Timer2 for sync pulses. */ 310 311 la $v0, T2CON 312 sw $zero, 0($v0) /* T2CON = 0 */ 313 nop 314 315 la $v0, TMR2 316 sw $zero, 0($v0) /* TMR2 = 0 */ 317 318 la $v0, PR2 319 li $v1, HFREQ_LIMIT 320 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 321 322 /* Initialise Timer2 interrupt. */ 323 324 la $v0, IFS0 325 li $v1, (1 << 9) 326 sw $v1, CLR($v0) /* T2IF = 0 */ 327 328 la $v0, IPC2 329 li $v1, 0b11111 330 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 331 li $v1, 0b11111 332 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 333 334 la $v0, IEC0 335 li $v1, (1 << 9) 336 sw $v1, SET($v0) /* T2IE = 1 */ 337 338 /* Start timer. */ 339 340 la $v0, T2CON 341 li $v1, (1 << 15) 342 sw $v1, SET($v0) /* ON = 1 */ 343 344 jr $ra 345 nop 346 347 348 349 /* 350 Output compare initialisation. 351 352 Timer2 will be used to trigger two events using OC1: one initiating the hsync 353 pulse, and one terminating the pulse. The pulse should appear after the line 354 data has been transferred using DMA, but this is achieved by just choosing 355 suitable start and end values. 356 357 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 358 reverse the level at a later point. In this way, the vsync pulse is generated 359 and is synchronised to the display lines. 360 */ 361 362 init_oc: 363 /* Disable OC1 interrupts. */ 364 365 la $v0, IEC0 366 li $v1, (1 << 7) /* IEC0<7> = OC1IE = 0 */ 367 sw $v1, CLR($v0) 368 369 la $v0, IFS0 370 li $v1, (1 << 7) /* IFS0<7> = OC1IF = 0 */ 371 sw $v1, CLR($v0) 372 373 /* Initialise OC1. */ 374 375 la $v0, OC1CON 376 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 377 sw $v1, 0($v0) 378 379 /* Pulse start and end. */ 380 381 la $v0, OC1R 382 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 383 sw $v1, 0($v0) 384 385 la $v0, OC1RS 386 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 387 sw $v1, 0($v0) 388 389 /* OC1 is enabled. */ 390 391 la $v0, OC1CON 392 li $v1, (1 << 15) 393 sw $v1, SET($v0) 394 395 /* Disable OC2 interrupts. */ 396 397 la $v0, IEC0 398 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 399 sw $v1, CLR($v0) 400 401 la $v0, IFS0 402 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 403 sw $v1, CLR($v0) 404 405 /* Initialise OC2. */ 406 407 la $v0, OC2CON 408 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 409 sw $v1, 0($v0) 410 411 /* Set pulse position. */ 412 413 la $v0, OC2R 414 sw $zero, 0($v0) 415 416 /* Enable OC2 later. */ 417 418 jr $ra 419 nop 420 421 init_io_pins: 422 /* Unlock the configuration register bits. */ 423 424 la $v0, SYSKEY 425 sw $zero, 0($v0) 426 li $v1, 0xAA996655 427 sw $v1, 0($v0) 428 li $v1, 0x556699AA 429 sw $v1, 0($v0) 430 431 la $v0, CFGCON 432 lw $t8, 0($v0) 433 li $v1, (1 << 13) /* IOLOCK = 0 */ 434 sw $v1, CLR($v0) 435 436 /* Map OC1 to RPA0. */ 437 438 la $v0, RPA0R 439 li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ 440 sw $v1, 0($v0) 441 442 /* Map OC2 to RPA1. */ 443 444 la $v0, RPA1R 445 li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ 446 sw $v1, 0($v0) 447 448 /* Map U1TX to RPB15. */ 449 450 la $v0, RPB15R 451 li $v1, 0b0001 /* RPB15R<3:0> = 0001 (U1TX) */ 452 sw $v1, 0($v0) 453 454 la $v0, CFGCON 455 sw $t8, 0($v0) 456 457 /* Lock the oscillator control register again. */ 458 459 la $v0, SYSKEY 460 li $v1, 0x33333333 461 sw $v1, 0($v0) 462 463 jr $ra 464 nop 465 466 467 468 /* 469 Direct Memory Access initialisation. 470 471 Write 160 pixels to PORTB for the line data. This is initiated by a timer 472 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 473 address update routine, changing the source address of the DMA channel. 474 */ 475 476 init_dma: 477 /* Disable DMA interrupts. */ 478 479 la $v0, IEC1 480 li $v1, (3 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 0 */ 481 sw $v1, CLR($v0) 482 483 /* Clear DMA interrupt flags. */ 484 485 la $v0, IFS1 486 li $v1, (3 << 28) /* IFS1<29:28> = DMA1IF, DMA0IF = 0 */ 487 sw $v1, CLR($v0) 488 489 /* Enable DMA. */ 490 491 la $v0, DMACON 492 li $v1, (1 << 15) 493 sw $v1, SET($v0) 494 495 /* 496 Initialise a line channel. 497 The line channel will be channel 0 (x = 0). 498 499 Specify a priority of 3: 500 DCHxCON<1:0> = CHPRI<1:0> = 3 501 502 Auto-enable the channel: 503 DCHxCON<4> = CHAEN = 1 504 */ 505 506 la $v0, DCH0CON 507 li $v1, 0b10011 508 sw $v1, 0($v0) 509 510 /* 511 Initialise a level reset channel. 512 The reset channel will be channel 1 (x = 1). 513 514 Specify a priority of 3: 515 DCHxCON<1:0> = CHPRI<1:0> = 3 516 517 Chain the channel to channel 0: 518 DCHxCON<5> = CHCHN = 1 519 520 Allow the channel to receive events when disabled: 521 DCHxCON<6> = CHAED = 1 522 */ 523 524 la $v0, DCH1CON 525 li $v1, 0b1100011 526 sw $v1, 0($v0) 527 528 /* 529 Initiate channel transfers when the initiating interrupt condition 530 occurs: 531 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 532 DCHxECON<4> = SIRQEN = 1 533 534 For now, however, prevent initiation by not setting SIRQEN. 535 */ 536 537 la $v0, DCH0ECON 538 li $v1, (9 << 8) 539 sw $v1, 0($v0) 540 541 /* 542 Initiate reset channel transfer when channel 0 is finished: 543 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 544 DCHxECON<4> = SIRQEN = 1 545 */ 546 547 la $v0, DCH1ECON 548 li $v1, (60 << 8) | (1 << 4) 549 sw $v1, 0($v0) 550 551 /* 552 The line channel has a cell size of the number bytes in a line: 553 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 554 */ 555 556 la $v0, DCH0CSIZ 557 li $v1, LINE_LENGTH 558 sw $v1, 0($v0) 559 560 /* 561 The reset channel has a cell size of a single zero byte: 562 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 563 */ 564 565 la $v0, DCH1CSIZ 566 li $v1, 1 567 sw $v1, 0($v0) 568 569 /* 570 The source has a size identical to the cell size: 571 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 572 */ 573 574 la $v0, DCH0SSIZ 575 li $v1, LINE_LENGTH 576 sw $v1, 0($v0) 577 578 la $v0, DCH1SSIZ 579 li $v1, 1 580 sw $v1, 0($v0) 581 582 /* 583 The source address is the physical address of the line data: 584 DCHxSSA = physical(line data address) 585 */ 586 587 la $v0, DCH0SSA 588 li $v1, SCREEN_BASE 589 sw $v1, 0($v0) 590 591 /* 592 For the reset channel, a single byte of zero is transferred: 593 DCHxSSA = physical(zero data address) 594 */ 595 596 la $v0, DCH1SSA 597 la $v1, zerodata 598 li $t8, KSEG0_BASE 599 subu $v1, $v1, $t8 600 sw $v1, 0($v0) 601 602 /* 603 The destination has a size of 1 byte: 604 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 605 */ 606 607 li $v1, 1 608 609 la $v0, DCH0DSIZ 610 sw $v1, 0($v0) 611 612 la $v0, DCH1DSIZ 613 sw $v1, 0($v0) 614 615 /* 616 The destination address is the physical address of PORTB: 617 DCHxDSA = physical(PORTB) 618 */ 619 620 li $v1, PORTB 621 li $t8, KSEG1_BASE 622 subu $v1, $v1, $t8 623 624 la $v0, DCH0DSA 625 sw $v1, 0($v0) 626 627 la $v0, DCH1DSA 628 sw $v1, 0($v0) 629 630 /* 631 Use the block transfer completion interrupt to indicate when the source 632 address can be updated. 633 */ 634 635 la $v0, DCH0INT 636 li $v1, (1 << 19) /* CHBCIE = 1 */ 637 sw $v1, 0($v0) 638 639 /* Enable interrupt for address updating. */ 640 641 la $v0, IPC10 642 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 643 sw $v1, CLR($v0) 644 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 645 sw $v1, SET($v0) 646 647 la $v0, IEC1 648 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 649 sw $v1, SET($v0) 650 651 /* Enable line channel. */ 652 653 la $v0, DCH0CON 654 li $v1, 0b10000000 655 sw $v1, SET($v0) 656 657 jr $ra 658 nop 659 660 zerodata: 661 .word 0 662 663 664 665 /* UART initialisation. */ 666 667 init_uart: 668 /* Initialise UART. */ 669 670 la $v0, U1BRG 671 li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ 672 sw $v1, 0($v0) 673 674 la $v0, U1MODE 675 li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ 676 sw $v1, CLR($v0) 677 678 /* Start UART. */ 679 680 la $v0, U1STA 681 li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ 682 sw $v1, SET($v0) 683 684 la $v0, U1MODE 685 li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ 686 sw $v1, SET($v0) 687 688 jr $ra 689 nop 690 691 692 693 /* Utilities. */ 694 695 handle_error_level: 696 mfc0 $t3, CP0_STATUS 697 li $t4, ~(STATUS_ERL | STATUS_EXL) 698 and $t3, $t3, $t4 699 mtc0 $t3, CP0_STATUS 700 jr $ra 701 nop 702 703 enable_interrupts: 704 mfc0 $t3, CP0_STATUS 705 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 706 and $t3, $t3, $t4 707 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 708 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 709 and $t3, $t3, $t4 710 ori $t3, $t3, STATUS_IE 711 mtc0 $t3, CP0_STATUS 712 jr $ra 713 nop 714 715 init_interrupts: 716 mfc0 $t3, CP0_DEBUG 717 li $t4, ~DEBUG_DM 718 and $t3, $t3, $t4 719 mtc0 $t3, CP0_DEBUG 720 721 mfc0 $t3, CP0_STATUS 722 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 723 or $t3, $t3, $t4 724 mtc0 $t3, CP0_STATUS 725 726 la $t3, exception_handler 727 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 728 729 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 730 mtc0 $t3, CP0_INTCTL 731 732 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 733 mtc0 $t3, CP0_CAUSE 734 735 jr $ra 736 nop 737 738 739 740 /* Exception servicing. */ 741 742 .section .flash, "a" 743 744 /* TLB error servicing. */ 745 746 tlb_handler: 747 j exception_handler 748 nop 749 750 751 752 /* General exception servicing. */ 753 754 .org 0x180 755 756 exception_handler: 757 j exc_handler 758 nop 759 760 761 762 /* Interrupt servicing. */ 763 764 .org 0x200 765 766 interrupt_handler: 767 768 /* 769 Save affected registers, restoring IRQ state and switching to the IRQ 770 stack. 771 */ 772 773 li $k0, IRQ_STACK_LIMIT 774 save_affected 775 load_state 776 li $sp, IRQ_STACK_TOP 777 778 /* 779 The timer interrupt will only occur outside the visible region, but the 780 interrupt condition will still occur as the timer wraps around. 781 782 Here, we deliberately ignore the timer condition during the visible/ 783 active region. 784 785 The DMA interrupt should only be active within the visible region. 786 */ 787 788 la $t8, visible_active 789 beq $s1, $t8, irq_dma 790 nop 791 792 /* Check for a timer interrupt condition. */ 793 794 la $v0, IFS0 795 lw $v1, 0($v0) 796 andi $v1, $v1, (1 << 9) /* T2IF */ 797 beqz $v1, irq_exit 798 nop 799 800 j irq_handle 801 nop 802 803 irq_dma: 804 /* Check for a DMA interrupt condition. */ 805 806 la $v0, IFS1 807 lw $v1, 0($v0) 808 li $t8, (1 << 28) /* DMA0IF */ 809 and $v1, $v1, $t8 810 beqz $v1, irq_exit 811 nop 812 813 /* Clear the DMA interrupt condition. */ 814 815 sw $v1, CLR($v0) 816 817 /* Test the block transfer completion interrupt flag. */ 818 819 la $v0, DCH0INT 820 lw $v1, 0($v0) 821 andi $v1, $v1, (1 << 3) /* CHBCIF */ 822 beqz $v1, irq_exit 823 nop 824 825 irq_handle: 826 /* Clear the interrupt condition. */ 827 828 sw $v1, CLR($v0) 829 830 /* Increment the line counter. */ 831 832 addiu $s0, $s0, 1 833 834 /* Jump to the event handler. */ 835 836 jalr $s1 837 nop 838 839 irq_exit: 840 /* 841 Save IRQ state and restore the affected registers, switching back to the 842 original stack. 843 */ 844 845 li $k0, IRQ_STACK_LIMIT 846 save_state 847 load_affected 848 849 eret 850 nop 851 852 853 854 /* Event routines. */ 855 856 /* The vertical back porch. */ 857 858 vbp_active: 859 /* Test for visible region. */ 860 861 sltiu $v0, $s0, VISIBLE_START 862 bnez $v0, _vbp_active_ret 863 nop 864 865 /* Start the visible region. */ 866 867 la $s1, visible_active 868 869 /* Reset the line address. */ 870 871 move $s2, $s3 872 873 /* Update the source address. */ 874 875 la $v0, DCH0SSA 876 sw $s2, 0($v0) 877 878 /* 879 Suspend delivery of the timer interrupt during the visible period. 880 The condition still occurs, however. 881 */ 882 883 la $v0, IPC2 884 lw $v1, 0($v0) 885 li $t8, ~0b11111 886 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 887 ori $v1, $v1, 0b00111 /* T2IP = 1; T2IS = 3 */ 888 sw $v1, 0($v0) 889 890 /* Enable the line channel for timer event transfer initiation. */ 891 892 la $v0, DCH0ECON 893 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 894 sw $v1, SET($v0) 895 896 _vbp_active_ret: 897 jr $ra 898 nop 899 900 901 902 /* The visible region. */ 903 904 visible_active: 905 /* Test for front porch. */ 906 907 sltiu $v0, $s0, VFP_START 908 bnez $v0, visible_update_address 909 nop 910 911 /* Start the front porch region. */ 912 913 la $s1, vfp_active 914 915 /* Restore delivery of the timer interrupt after the visible period. */ 916 917 la $v0, IPC2 918 lw $v1, 0($v0) 919 li $t8, ~0b11111 920 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 921 ori $v1, $v1, 0b11111 /* T2IP = 7; T2IS = 3 */ 922 sw $v1, 0($v0) 923 924 /* Disable the line channel. */ 925 926 la $v0, DCH0ECON 927 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 928 sw $v1, CLR($v0) 929 930 _visible_active_ret: 931 jr $ra 932 nop 933 934 935 936 /* DMA update routine. */ 937 938 visible_update_address: 939 940 /* 941 Update the line data address if the line counter (referring to the 942 next line) is even. 943 */ 944 945 andi $t8, $s0, 1 946 bnez $t8, _visible_update_ret 947 nop 948 949 /* Reference the next line and update the DMA source address. */ 950 951 addiu $s2, $s2, LINE_LENGTH 952 953 /* Test for wraparound. */ 954 955 li $t8, (SCREEN_BASE + SCREEN_SIZE) 956 sltu $t8, $s2, $t8 957 bnez $t8, _visible_dma_update 958 nop 959 960 /* Reset the source address. */ 961 962 li $s2, SCREEN_BASE 963 964 _visible_dma_update: 965 966 /* Update the source address. */ 967 968 la $v0, DCH0SSA 969 sw $s2, 0($v0) 970 971 _visible_update_ret: 972 jr $ra 973 nop 974 975 976 977 /* Within the vertical front porch. */ 978 979 vfp_active: 980 /* Test for vsync. */ 981 982 sltiu $v0, $s0, VSYNC_START 983 bnez $v0, _vfp_active_ret 984 nop 985 986 /* Start the vsync. */ 987 988 la $s1, vsync_active 989 990 /* Bring vsync low when the next line starts. */ 991 992 la $v0, OC2CON 993 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 994 sw $v1, 0($v0) 995 996 _vfp_active_ret: 997 jr $ra 998 nop 999 1000 1001 1002 /* The vsync period. */ 1003 1004 vsync_active: 1005 /* Test for front porch. */ 1006 1007 sltiu $v0, $s0, VSYNC_END 1008 bnez $v0, _vsync_active_ret 1009 nop 1010 1011 /* Start the back porch. */ 1012 1013 move $s0, $zero 1014 la $s1, vbp_active 1015 1016 /* Bring vsync high when the next line starts. */ 1017 1018 la $v0, OC2CON 1019 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1020 sw $v1, 0($v0) 1021 1022 _vsync_active_ret: 1023 jr $ra 1024 nop 1025 1026 1027 1028 /* Exception handler. */ 1029 1030 exc_handler: 1031 mfc0 $t7, CP0_ERROREPC 1032 nop 1033 1034 exc_write_word: 1035 li $t8, 32 1036 la $v0, U1TXREG 1037 exc_loop: 1038 addiu $t8, $t8, -4 1039 srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ 1040 andi $v1, $v1, 0xF 1041 addiu $t9, $v1, -10 /* $t9 >= 10? */ 1042 bgez $t9, exc_alpha 1043 nop 1044 exc_digit: 1045 addiu $v1, $v1, 48 /* convert to digit: '0' */ 1046 j exc_write 1047 nop 1048 exc_alpha: 1049 addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ 1050 exc_write: 1051 sw $v1, 0($v0) 1052 bnez $t8, exc_loop 1053 nop 1054 exc_loop_end: 1055 li $v1, '\n' 1056 sw $v1, 0($v0) 1057 1058 exc_handler_end: 1059 j exc_handler_end 1060 nop