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 /* 310 Timer initialisation. 311 312 Timer2 is used to drive the output compare and DMA peripherals. 313 */ 314 315 init_timer2: 316 317 /* Initialise Timer2 for sync pulses. */ 318 319 la $v0, T2CON 320 sw $zero, 0($v0) /* T2CON = 0 */ 321 nop 322 323 la $v0, TMR2 324 sw $zero, 0($v0) /* TMR2 = 0 */ 325 326 la $v0, PR2 327 li $v1, HFREQ_LIMIT 328 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 329 330 /* Start timer. */ 331 332 la $v0, T2CON 333 li $v1, (1 << 15) 334 sw $v1, SET($v0) /* ON = 1 */ 335 336 jr $ra 337 nop 338 339 340 341 /* 342 Output compare initialisation. 343 344 Timer2 will be used to trigger two events using OC1: one initiating the hsync 345 pulse, and one terminating the pulse. The pulse should appear after the line 346 data has been transferred using DMA, but this is achieved by just choosing 347 suitable start and end values. 348 349 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 350 reverse the level at a later point. In this way, the vsync pulse is generated 351 and is synchronised to the display lines. 352 353 The OC1 interrupt is used to update the state machine handling the display, 354 also invoking the address update routine at the end of each visible display 355 line, changing the source address of the DMA channel. 356 */ 357 358 init_oc: 359 /* Disable OC1 interrupts. */ 360 361 li $v1, (1 << 7) 362 363 la $v0, IEC0 364 sw $v1, CLR($v0) /* IEC0<7> = OC1IE = 0 */ 365 la $v0, IFS0 366 sw $v1, CLR($v0) /* IFS0<7> = OC1IF = 0 */ 367 368 /* Initialise OC1. */ 369 370 la $v0, OC1CON 371 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 372 sw $v1, 0($v0) 373 374 /* Pulse start and end. */ 375 376 la $v0, OC1R 377 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 378 sw $v1, 0($v0) 379 380 la $v0, OC1RS 381 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 382 sw $v1, 0($v0) 383 384 /* Enable interrupt for address updating. */ 385 386 la $v0, IPC1 387 li $v1, (0b11111 << 16) 388 sw $v1, SET($v0) /* OC1IP = 7; OC1IS = 3 */ 389 390 la $v0, IEC0 391 li $v1, (1 << 7) 392 sw $v1, SET($v0) /* IEC0<7> = OC1IE = 1 */ 393 394 /* OC1 is enabled. */ 395 396 la $v0, OC1CON 397 li $v1, (1 << 15) 398 sw $v1, SET($v0) 399 400 /* Disable OC2 interrupts. */ 401 402 li $v1, (1 << 12) 403 404 la $v0, IEC0 405 sw $v1, CLR($v0) /* IEC0<12> = OC2IE = 0 */ 406 la $v0, IFS0 407 sw $v1, CLR($v0) /* IFS0<12> = OC2IF = 0 */ 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. 477 */ 478 479 init_dma: 480 /* Disable DMA interrupts. */ 481 482 la $v0, IEC1 483 li $v1, (3 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 0 */ 484 sw $v1, CLR($v0) 485 486 /* Clear DMA interrupt flags. */ 487 488 la $v0, IFS1 489 li $v1, (3 << 28) /* IFS1<29:28> = DMA1IF, DMA0IF = 0 */ 490 sw $v1, CLR($v0) 491 492 /* Enable DMA. */ 493 494 la $v0, DMACON 495 li $v1, (1 << 15) 496 sw $v1, SET($v0) 497 498 /* 499 Initialise a line channel. 500 The line channel will be channel 0 (x = 0). 501 502 Specify a priority of 3: 503 DCHxCON<1:0> = CHPRI<1:0> = 3 504 505 Auto-enable the channel: 506 DCHxCON<4> = CHAEN = 1 507 */ 508 509 la $v0, DCH0CON 510 li $v1, 0b10011 511 sw $v1, 0($v0) 512 513 /* 514 Initialise a level reset channel. 515 The reset channel will be channel 1 (x = 1). 516 517 Specify a priority of 3: 518 DCHxCON<1:0> = CHPRI<1:0> = 3 519 520 Chain the channel to channel 0: 521 DCHxCON<5> = CHCHN = 1 522 523 Allow the channel to receive events when disabled: 524 DCHxCON<6> = CHAED = 1 525 */ 526 527 la $v0, DCH1CON 528 li $v1, 0b1100011 529 sw $v1, 0($v0) 530 531 /* 532 Initiate channel transfers when the initiating interrupt condition 533 occurs: 534 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 535 DCHxECON<4> = SIRQEN = 1 536 537 For now, however, prevent initiation by not setting SIRQEN. 538 */ 539 540 la $v0, DCH0ECON 541 li $v1, (9 << 8) 542 sw $v1, 0($v0) 543 544 /* 545 Initiate reset channel transfer when channel 0 is finished: 546 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 547 DCHxECON<4> = SIRQEN = 1 548 */ 549 550 la $v0, DCH1ECON 551 li $v1, (60 << 8) | (1 << 4) 552 sw $v1, 0($v0) 553 554 /* 555 The line channel has a cell size of the number bytes in a line: 556 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 557 */ 558 559 la $v0, DCH0CSIZ 560 li $v1, LINE_LENGTH 561 sw $v1, 0($v0) 562 563 /* 564 The reset channel has a cell size of a single zero byte: 565 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 566 */ 567 568 la $v0, DCH1CSIZ 569 li $v1, 1 570 sw $v1, 0($v0) 571 572 /* 573 The source has a size identical to the cell size: 574 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 575 */ 576 577 la $v0, DCH0SSIZ 578 li $v1, LINE_LENGTH 579 sw $v1, 0($v0) 580 581 la $v0, DCH1SSIZ 582 li $v1, 1 583 sw $v1, 0($v0) 584 585 /* 586 The source address is the physical address of the line data: 587 DCHxSSA = physical(line data address) 588 */ 589 590 la $v0, DCH0SSA 591 li $v1, SCREEN_BASE 592 sw $v1, 0($v0) 593 594 /* 595 For the reset channel, a single byte of zero is transferred: 596 DCHxSSA = physical(zero data address) 597 */ 598 599 la $v0, DCH1SSA 600 la $v1, zerodata 601 li $t8, KSEG0_BASE 602 subu $v1, $v1, $t8 603 sw $v1, 0($v0) 604 605 /* 606 The destination has a size of 1 byte: 607 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 608 */ 609 610 li $v1, 1 611 612 la $v0, DCH0DSIZ 613 sw $v1, 0($v0) 614 615 la $v0, DCH1DSIZ 616 sw $v1, 0($v0) 617 618 /* 619 The destination address is the physical address of PORTB: 620 DCHxDSA = physical(PORTB) 621 */ 622 623 li $v1, PORTB 624 li $t8, KSEG1_BASE 625 subu $v1, $v1, $t8 626 627 la $v0, DCH0DSA 628 sw $v1, 0($v0) 629 630 la $v0, DCH1DSA 631 sw $v1, 0($v0) 632 633 /* Enable interrupt for channel chaining. */ 634 635 la $v0, DCH0INT 636 li $v1, (1 << 19) /* CHBCIE = 1 */ 637 sw $v1, 0($v0) 638 639 la $v0, IPC10 640 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 641 sw $v1, CLR($v0) 642 li $v1, 0b10011 /* DMA0IP = 4, DMA0IS = 3 */ 643 sw $v1, SET($v0) 644 645 la $v0, IEC1 646 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 647 sw $v1, SET($v0) 648 649 /* Enable line channel. */ 650 651 la $v0, DCH0CON 652 li $v1, 0b10000000 653 sw $v1, SET($v0) 654 655 jr $ra 656 nop 657 658 zerodata: 659 .word 0 660 661 662 663 /* 664 UART initialisation. 665 666 Initialise UART transmission at 115200 baud. This is sensitive to the peripheral 667 clock frequency. 668 */ 669 670 init_uart: 671 /* Initialise UART. */ 672 673 la $v0, U1BRG 674 li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ 675 sw $v1, 0($v0) 676 677 la $v0, U1MODE 678 li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ 679 sw $v1, CLR($v0) 680 681 /* Start UART. */ 682 683 la $v0, U1STA 684 li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ 685 sw $v1, SET($v0) 686 687 la $v0, U1MODE 688 li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ 689 sw $v1, SET($v0) 690 691 jr $ra 692 nop 693 694 695 696 /* Utilities. */ 697 698 handle_error_level: 699 mfc0 $t3, CP0_STATUS 700 li $t4, ~(STATUS_ERL | STATUS_EXL) 701 and $t3, $t3, $t4 702 mtc0 $t3, CP0_STATUS 703 jr $ra 704 nop 705 706 enable_interrupts: 707 mfc0 $t3, CP0_STATUS 708 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 709 and $t3, $t3, $t4 710 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 711 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 712 and $t3, $t3, $t4 713 ori $t3, $t3, STATUS_IE 714 mtc0 $t3, CP0_STATUS 715 jr $ra 716 nop 717 718 init_interrupts: 719 mfc0 $t3, CP0_DEBUG 720 li $t4, ~DEBUG_DM 721 and $t3, $t3, $t4 722 mtc0 $t3, CP0_DEBUG 723 724 mfc0 $t3, CP0_STATUS 725 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 726 or $t3, $t3, $t4 727 mtc0 $t3, CP0_STATUS 728 729 la $t3, exception_handler 730 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 731 732 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 733 mtc0 $t3, CP0_INTCTL 734 735 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 736 mtc0 $t3, CP0_CAUSE 737 738 jr $ra 739 nop 740 741 742 743 /* Exception servicing. */ 744 745 .section .flash, "a" 746 747 /* TLB error servicing. */ 748 749 tlb_handler: 750 j exception_handler 751 nop 752 753 754 755 /* General exception servicing. */ 756 757 .org 0x180 758 759 exception_handler: 760 j exc_handler 761 nop 762 763 764 765 /* Interrupt servicing. */ 766 767 .org 0x200 768 769 interrupt_handler: 770 771 /* 772 Save affected registers, restoring IRQ state and switching to the IRQ 773 stack. 774 */ 775 776 li $k0, IRQ_STACK_LIMIT 777 save_affected 778 load_state 779 li $sp, IRQ_STACK_TOP 780 781 /* Check for the output compare interrupt condition. */ 782 783 la $v0, IFS0 784 lw $v1, 0($v0) 785 andi $v1, $v1, (1 << 7) /* OC1IF */ 786 beqz $v1, irq_dma 787 nop 788 789 irq_handle: 790 /* Clear the interrupt condition. */ 791 792 sw $v1, CLR($v0) 793 794 /* Increment the line counter. */ 795 796 addiu $s0, $s0, 1 797 798 /* Jump to the event handler. */ 799 800 jalr $s1 801 nop 802 803 irq_dma: 804 /* Clear the DMA channel completion condition. */ 805 806 la $v0, IFS1 807 lw $v1, 0($v0) 808 li $t8, (1 << 28) 809 and $v1, $v1, $t8 810 beqz $v1, irq_exit 811 nop 812 813 sw $v1, CLR($v0) /* IFS1<28> = DMA0IF = 0 */ 814 815 la $v0, DCH0INT 816 lw $v1, 0($v0) 817 andi $v1, $v1, (1 << 3) 818 beqz $v1, irq_exit 819 nop 820 821 sw $v1, CLR($v0) /* CHBCIF = 0 */ 822 823 irq_exit: 824 /* 825 Save IRQ state and restore the affected registers, switching back to the 826 original stack. 827 */ 828 829 li $k0, IRQ_STACK_LIMIT 830 save_state 831 load_affected 832 833 eret 834 nop 835 836 837 838 /* Event routines. */ 839 840 /* The vertical back porch. */ 841 842 vbp_active: 843 /* Test for visible region. */ 844 845 sltiu $v0, $s0, VISIBLE_START 846 bnez $v0, _vbp_active_ret 847 nop 848 849 /* Start the visible region. */ 850 851 la $s1, visible_active 852 853 /* Reset the line address. */ 854 855 move $s2, $s3 856 857 /* Update the source address. */ 858 859 la $v0, DCH0SSA 860 sw $s2, 0($v0) 861 862 /* Enable the line channel for timer event transfer initiation. */ 863 864 la $v0, DCH0ECON 865 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 866 sw $v1, SET($v0) 867 868 _vbp_active_ret: 869 jr $ra 870 nop 871 872 873 874 /* The visible region. */ 875 876 visible_active: 877 /* Test for front porch. */ 878 879 sltiu $v0, $s0, VFP_START 880 bnez $v0, visible_update_address 881 nop 882 883 /* Start the front porch region. */ 884 885 la $s1, vfp_active 886 887 /* Disable the line channel. */ 888 889 la $v0, DCH0ECON 890 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 891 sw $v1, CLR($v0) 892 893 _visible_active_ret: 894 jr $ra 895 nop 896 897 898 899 /* DMA update routine. */ 900 901 visible_update_address: 902 903 /* 904 Update the line data address if the line counter (referring to the 905 next line) is even. 906 */ 907 908 andi $t8, $s0, 1 909 bnez $t8, _visible_update_ret 910 nop 911 912 /* Reference the next line and update the DMA source address. */ 913 914 addiu $s2, $s2, LINE_LENGTH 915 916 /* Test for wraparound. */ 917 918 li $t8, (SCREEN_BASE + SCREEN_SIZE) 919 sltu $t8, $s2, $t8 920 bnez $t8, _visible_dma_update 921 nop 922 923 /* Reset the source address. */ 924 925 li $s2, SCREEN_BASE 926 927 _visible_dma_update: 928 929 /* Update the source address. */ 930 931 la $v0, DCH0SSA 932 sw $s2, 0($v0) 933 934 _visible_update_ret: 935 jr $ra 936 nop 937 938 939 940 /* Within the vertical front porch. */ 941 942 vfp_active: 943 /* Test for vsync. */ 944 945 sltiu $v0, $s0, VSYNC_START 946 bnez $v0, _vfp_active_ret 947 nop 948 949 /* Start the vsync. */ 950 951 la $s1, vsync_active 952 953 /* Bring vsync low when the next line starts. */ 954 955 la $v0, OC2CON 956 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 957 sw $v1, 0($v0) 958 959 _vfp_active_ret: 960 jr $ra 961 nop 962 963 964 965 /* The vsync period. */ 966 967 vsync_active: 968 /* Test for front porch. */ 969 970 sltiu $v0, $s0, VSYNC_END 971 bnez $v0, _vsync_active_ret 972 nop 973 974 /* Start the back porch. */ 975 976 move $s0, $zero 977 la $s1, vbp_active 978 979 /* Bring vsync high when the next line starts. */ 980 981 la $v0, OC2CON 982 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 983 sw $v1, 0($v0) 984 985 _vsync_active_ret: 986 jr $ra 987 nop 988 989 990 991 /* Exception handler. */ 992 993 exc_handler: 994 mfc0 $t7, CP0_ERROREPC 995 nop 996 la $ra, exc_handler_end 997 998 exc_write_word: 999 li $t8, 32 1000 la $v0, U1TXREG 1001 exc_loop: 1002 addiu $t8, $t8, -4 1003 srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ 1004 andi $v1, $v1, 0xF 1005 addiu $t9, $v1, -10 /* $t9 >= 10? */ 1006 bgez $t9, exc_alpha 1007 nop 1008 exc_digit: 1009 addiu $v1, $v1, 48 /* convert to digit: '0' */ 1010 j exc_write 1011 nop 1012 exc_alpha: 1013 addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ 1014 exc_write: 1015 sw $v1, 0($v0) 1016 bnez $t8, exc_loop 1017 nop 1018 exc_loop_end: 1019 li $v1, ' ' 1020 sw $v1, 0($v0) 1021 1022 exc_handler_end: 1023 jr $ra 1024 nop