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