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 li $v1, (3 << 28) 483 484 la $v0, IEC1 485 sw $v1, CLR($v0) /* IEC1<29:28> = DMA1IE, DMA0IE = 0 */ 486 487 /* Clear DMA interrupt flags. */ 488 489 la $v0, IFS1 490 sw $v1, CLR($v0) /* IFS1<29:28> = DMA1IF, DMA0IF = 0 */ 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 524 la $v0, DCH1CON 525 li $v1, 0b100011 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 535 la $v0, DCH0ECON 536 li $v1, (9 << 8) | (1 << 4) 537 sw $v1, 0($v0) 538 539 /* 540 Initiate reset channel transfer when channel 0 is finished: 541 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 542 DCHxECON<4> = SIRQEN = 1 543 */ 544 545 la $v0, DCH1ECON 546 li $v1, (60 << 8) | (1 << 4) 547 sw $v1, 0($v0) 548 549 /* 550 The line channel has a cell size of the number bytes in a line: 551 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 552 */ 553 554 la $v0, DCH0CSIZ 555 li $v1, LINE_LENGTH 556 sw $v1, 0($v0) 557 558 /* 559 The reset channel has a cell size of a single zero byte: 560 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 561 */ 562 563 la $v0, DCH1CSIZ 564 li $v1, 1 565 sw $v1, 0($v0) 566 567 /* 568 The source has a size identical to the cell size: 569 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 570 */ 571 572 la $v0, DCH0SSIZ 573 li $v1, LINE_LENGTH 574 sw $v1, 0($v0) 575 576 la $v0, DCH1SSIZ 577 li $v1, 1 578 sw $v1, 0($v0) 579 580 /* 581 The source address is the physical address of the line data: 582 DCHxSSA = physical(line data address) 583 */ 584 585 la $v0, DCH0SSA 586 li $v1, SCREEN_BASE 587 sw $v1, 0($v0) 588 589 /* 590 For the reset channel, a single byte of zero is transferred: 591 DCHxSSA = physical(zero data address) 592 */ 593 594 la $v0, DCH1SSA 595 la $v1, zerodata 596 li $t8, KSEG0_BASE 597 subu $v1, $v1, $t8 598 sw $v1, 0($v0) 599 600 /* 601 The destination has a size of 1 byte: 602 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 603 */ 604 605 li $v1, 1 606 607 la $v0, DCH0DSIZ 608 sw $v1, 0($v0) 609 610 la $v0, DCH1DSIZ 611 sw $v1, 0($v0) 612 613 /* 614 The destination address is the physical address of PORTB: 615 DCHxDSA = physical(PORTB) 616 */ 617 618 li $v1, PORTB 619 li $t8, KSEG1_BASE 620 subu $v1, $v1, $t8 621 622 la $v0, DCH0DSA 623 sw $v1, 0($v0) 624 625 la $v0, DCH1DSA 626 sw $v1, 0($v0) 627 628 /* Enable interrupt for channel chaining. */ 629 630 la $v0, DCH0INT 631 li $v1, (1 << 19) /* CHBCIE = 1 */ 632 sw $v1, 0($v0) 633 634 la $v0, IPC10 635 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 636 sw $v1, CLR($v0) 637 li $v1, 0b10011 /* DMA0IP = 4, DMA0IS = 3 */ 638 sw $v1, SET($v0) 639 640 la $v0, IEC1 641 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 642 sw $v1, SET($v0) 643 644 /* Enable line channel later. */ 645 646 jr $ra 647 nop 648 649 zerodata: 650 .word 0 651 652 653 654 /* 655 UART initialisation. 656 657 Initialise UART transmission at 115200 baud. This is sensitive to the peripheral 658 clock frequency. 659 */ 660 661 init_uart: 662 /* Initialise UART. */ 663 664 la $v0, U1BRG 665 li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ 666 sw $v1, 0($v0) 667 668 la $v0, U1MODE 669 li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ 670 sw $v1, CLR($v0) 671 672 /* Start UART. */ 673 674 la $v0, U1STA 675 li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ 676 sw $v1, SET($v0) 677 678 la $v0, U1MODE 679 li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ 680 sw $v1, SET($v0) 681 682 jr $ra 683 nop 684 685 686 687 /* Utilities. */ 688 689 handle_error_level: 690 mfc0 $t3, CP0_STATUS 691 li $t4, ~(STATUS_ERL | STATUS_EXL) 692 and $t3, $t3, $t4 693 mtc0 $t3, CP0_STATUS 694 jr $ra 695 nop 696 697 enable_interrupts: 698 mfc0 $t3, CP0_STATUS 699 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 700 and $t3, $t3, $t4 701 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 702 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 703 and $t3, $t3, $t4 704 ori $t3, $t3, STATUS_IE 705 mtc0 $t3, CP0_STATUS 706 jr $ra 707 nop 708 709 init_interrupts: 710 mfc0 $t3, CP0_DEBUG 711 li $t4, ~DEBUG_DM 712 and $t3, $t3, $t4 713 mtc0 $t3, CP0_DEBUG 714 715 mfc0 $t3, CP0_STATUS 716 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 717 or $t3, $t3, $t4 718 mtc0 $t3, CP0_STATUS 719 720 la $t3, exception_handler 721 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 722 723 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 724 mtc0 $t3, CP0_INTCTL 725 726 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 727 mtc0 $t3, CP0_CAUSE 728 729 jr $ra 730 nop 731 732 733 734 /* Exception servicing. */ 735 736 .section .flash, "a" 737 738 /* TLB error servicing. */ 739 740 tlb_handler: 741 j exception_handler 742 nop 743 744 745 746 /* General exception servicing. */ 747 748 .org 0x180 749 750 exception_handler: 751 j exc_handler 752 nop 753 754 755 756 /* Interrupt servicing. */ 757 758 .org 0x200 759 760 interrupt_handler: 761 762 /* 763 Save affected registers, restoring IRQ state and switching to the IRQ 764 stack. 765 */ 766 767 li $k0, IRQ_STACK_LIMIT 768 save_affected 769 load_state 770 li $sp, IRQ_STACK_TOP 771 772 /* Check for the output compare interrupt condition. */ 773 774 la $v0, IFS0 775 lw $v1, 0($v0) 776 andi $v1, $v1, (1 << 7) /* OC1IF */ 777 beqz $v1, irq_dma 778 nop 779 780 irq_handle: 781 /* Clear the interrupt condition. */ 782 783 sw $v1, CLR($v0) 784 785 /* Increment the line counter. */ 786 787 addiu $s0, $s0, 1 788 789 /* Jump to the event handler. */ 790 791 jalr $s1 792 nop 793 794 irq_dma: 795 /* Clear the DMA channel completion condition. */ 796 797 la $v0, IFS1 798 lw $v1, 0($v0) 799 li $t8, (1 << 28) 800 and $v1, $v1, $t8 801 beqz $v1, irq_exit 802 nop 803 804 sw $v1, CLR($v0) /* IFS1<28> = DMA0IF = 0 */ 805 806 la $v0, DCH0INT 807 lw $v1, 0($v0) 808 andi $v1, $v1, (1 << 3) 809 beqz $v1, irq_exit 810 nop 811 812 sw $v1, CLR($v0) /* CHBCIF = 0 */ 813 814 irq_exit: 815 /* 816 Save IRQ state and restore the affected registers, switching back to the 817 original stack. 818 */ 819 820 li $k0, IRQ_STACK_LIMIT 821 save_state 822 load_affected 823 824 eret 825 nop 826 827 828 829 /* Event routines. */ 830 831 /* The vertical back porch. */ 832 833 vbp_active: 834 /* Test for visible region. */ 835 836 sltiu $v0, $s0, VISIBLE_START 837 bnez $v0, _vbp_active_ret 838 nop 839 840 /* Start the visible region. */ 841 842 la $s1, visible_active 843 844 /* Reset the line address. */ 845 846 move $s2, $s3 847 848 /* Update the source address. */ 849 850 la $v0, DCH0SSA 851 sw $s2, 0($v0) 852 853 /* Enable the line channel for timer event transfer initiation. */ 854 855 la $v0, DCH0CON 856 li $v1, (1 << 7) 857 sw $v1, SET($v0) 858 859 _vbp_active_ret: 860 jr $ra 861 nop 862 863 864 865 /* The visible region. */ 866 867 visible_active: 868 /* Test for front porch. */ 869 870 sltiu $v0, $s0, VFP_START 871 bnez $v0, visible_update_address 872 nop 873 874 /* Start the front porch region. */ 875 876 la $s1, vfp_active 877 878 /* Disable the line channel. */ 879 880 la $v0, DCH0CON 881 li $v1, (1 << 7) 882 sw $v1, CLR($v0) 883 884 _visible_active_ret: 885 jr $ra 886 nop 887 888 889 890 /* DMA update routine. */ 891 892 visible_update_address: 893 894 /* 895 Update the line data address if the line counter (referring to the 896 next line) is even. 897 */ 898 899 andi $t8, $s0, 1 900 bnez $t8, _visible_update_ret 901 nop 902 903 /* Reference the next line and update the DMA source address. */ 904 905 addiu $s2, $s2, LINE_LENGTH 906 907 /* Test for wraparound. */ 908 909 li $t8, (SCREEN_BASE + SCREEN_SIZE) 910 sltu $t8, $s2, $t8 911 bnez $t8, _visible_dma_update 912 nop 913 914 /* Reset the source address. */ 915 916 li $s2, SCREEN_BASE 917 918 _visible_dma_update: 919 920 /* Update the source address. */ 921 922 la $v0, DCH0SSA 923 sw $s2, 0($v0) 924 925 _visible_update_ret: 926 jr $ra 927 nop 928 929 930 931 /* Within the vertical front porch. */ 932 933 vfp_active: 934 /* Test for vsync. */ 935 936 sltiu $v0, $s0, VSYNC_START 937 bnez $v0, _vfp_active_ret 938 nop 939 940 /* Start the vsync. */ 941 942 la $s1, vsync_active 943 944 /* Bring vsync low when the next line starts. */ 945 946 la $v0, OC2CON 947 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 948 sw $v1, 0($v0) 949 950 _vfp_active_ret: 951 jr $ra 952 nop 953 954 955 956 /* The vsync period. */ 957 958 vsync_active: 959 /* Test for front porch. */ 960 961 sltiu $v0, $s0, VSYNC_END 962 bnez $v0, _vsync_active_ret 963 nop 964 965 /* Start the back porch. */ 966 967 move $s0, $zero 968 la $s1, vbp_active 969 970 /* Bring vsync high when the next line starts. */ 971 972 la $v0, OC2CON 973 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 974 sw $v1, 0($v0) 975 976 _vsync_active_ret: 977 jr $ra 978 nop 979 980 981 982 /* Exception handler. */ 983 984 exc_handler: 985 mfc0 $t7, CP0_ERROREPC 986 nop 987 la $ra, exc_handler_end 988 989 exc_write_word: 990 li $t8, 32 991 la $v0, U1TXREG 992 exc_loop: 993 addiu $t8, $t8, -4 994 srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ 995 andi $v1, $v1, 0xF 996 addiu $t9, $v1, -10 /* $t9 >= 10? */ 997 bgez $t9, exc_alpha 998 nop 999 exc_digit: 1000 addiu $v1, $v1, 48 /* convert to digit: '0' */ 1001 j exc_write 1002 nop 1003 exc_alpha: 1004 addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ 1005 exc_write: 1006 sw $v1, 0($v0) 1007 bnez $t8, exc_loop 1008 nop 1009 exc_loop_end: 1010 li $v1, ' ' 1011 sw $v1, 0($v0) 1012 1013 exc_handler_end: 1014 jr $ra 1015 nop