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