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 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 /* Enable interrupt for channel chaining. */ 631 632 la $v0, DCH0INT 633 li $v1, (1 << 19) /* CHBCIE = 1 */ 634 sw $v1, 0($v0) 635 636 la $v0, IPC10 637 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 638 sw $v1, CLR($v0) 639 li $v1, 0b10011 /* DMA0IP = 4, DMA0IS = 3 */ 640 sw $v1, SET($v0) 641 642 la $v0, IEC1 643 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 644 sw $v1, SET($v0) 645 646 /* Enable line channel. */ 647 648 la $v0, DCH0CON 649 li $v1, 0b10000000 650 sw $v1, SET($v0) 651 652 jr $ra 653 nop 654 655 zerodata: 656 .word 0 657 658 659 660 /* 661 UART initialisation. 662 663 Initialise UART transmission at 115200 baud. This is sensitive to the peripheral 664 clock frequency. 665 */ 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 /* Check for the output compare interrupt condition. */ 779 780 la $v0, IFS0 781 lw $v1, 0($v0) 782 andi $v1, $v1, (1 << 7) /* OC1IF */ 783 beqz $v1, irq_dma 784 nop 785 786 irq_handle: 787 /* Clear the interrupt condition. */ 788 789 sw $v1, CLR($v0) 790 791 /* Increment the line counter. */ 792 793 addiu $s0, $s0, 1 794 795 /* Jump to the event handler. */ 796 797 jalr $s1 798 nop 799 800 irq_dma: 801 /* Clear the DMA channel completion condition. */ 802 803 la $v0, IFS1 804 lw $v1, 0($v0) 805 li $t8, (1 << 28) 806 and $v1, $v1, $t8 807 beqz $v1, irq_exit 808 nop 809 810 sw $v1, CLR($v0) /* IFS1<28> = DMA0IF = 0 */ 811 812 la $v0, DCH0INT 813 lw $v1, 0($v0) 814 andi $v1, $v1, (1 << 3) 815 beqz $v1, irq_exit 816 nop 817 818 sw $v1, CLR($v0) /* CHBCIF = 0 */ 819 820 irq_exit: 821 /* 822 Save IRQ state and restore the affected registers, switching back to the 823 original stack. 824 */ 825 826 li $k0, IRQ_STACK_LIMIT 827 save_state 828 load_affected 829 830 eret 831 nop 832 833 834 835 /* Event routines. */ 836 837 /* The vertical back porch. */ 838 839 vbp_active: 840 /* Test for visible region. */ 841 842 sltiu $v0, $s0, VISIBLE_START 843 bnez $v0, _vbp_active_ret 844 nop 845 846 /* Start the visible region. */ 847 848 la $s1, visible_active 849 850 /* Reset the line address. */ 851 852 move $s2, $s3 853 854 /* Update the source address. */ 855 856 la $v0, DCH0SSA 857 sw $s2, 0($v0) 858 859 /* Enable the line channel for timer event transfer initiation. */ 860 861 la $v0, DCH0ECON 862 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 863 sw $v1, SET($v0) 864 865 _vbp_active_ret: 866 jr $ra 867 nop 868 869 870 871 /* The visible region. */ 872 873 visible_active: 874 /* Test for front porch. */ 875 876 sltiu $v0, $s0, VFP_START 877 bnez $v0, visible_update_address 878 nop 879 880 /* Start the front porch region. */ 881 882 la $s1, vfp_active 883 884 /* Disable the line channel. */ 885 886 la $v0, DCH0ECON 887 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 888 sw $v1, CLR($v0) 889 890 _visible_active_ret: 891 jr $ra 892 nop 893 894 895 896 /* DMA update routine. */ 897 898 visible_update_address: 899 900 /* 901 Update the line data address if the line counter (referring to the 902 next line) is even. 903 */ 904 905 andi $t8, $s0, 1 906 bnez $t8, _visible_update_ret 907 nop 908 909 /* Reference the next line and update the DMA source address. */ 910 911 addiu $s2, $s2, LINE_LENGTH 912 913 /* Test for wraparound. */ 914 915 li $t8, (SCREEN_BASE + SCREEN_SIZE) 916 sltu $t8, $s2, $t8 917 bnez $t8, _visible_dma_update 918 nop 919 920 /* Reset the source address. */ 921 922 li $s2, SCREEN_BASE 923 924 _visible_dma_update: 925 926 /* Update the source address. */ 927 928 la $v0, DCH0SSA 929 sw $s2, 0($v0) 930 931 _visible_update_ret: 932 jr $ra 933 nop 934 935 936 937 /* Within the vertical front porch. */ 938 939 vfp_active: 940 /* Test for vsync. */ 941 942 sltiu $v0, $s0, VSYNC_START 943 bnez $v0, _vfp_active_ret 944 nop 945 946 /* Start the vsync. */ 947 948 la $s1, vsync_active 949 950 /* Bring vsync low when the next line starts. */ 951 952 la $v0, OC2CON 953 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 954 sw $v1, 0($v0) 955 956 _vfp_active_ret: 957 jr $ra 958 nop 959 960 961 962 /* The vsync period. */ 963 964 vsync_active: 965 /* Test for front porch. */ 966 967 sltiu $v0, $s0, VSYNC_END 968 bnez $v0, _vsync_active_ret 969 nop 970 971 /* Start the back porch. */ 972 973 move $s0, $zero 974 la $s1, vbp_active 975 976 /* Bring vsync high when the next line starts. */ 977 978 la $v0, OC2CON 979 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 980 sw $v1, 0($v0) 981 982 _vsync_active_ret: 983 jr $ra 984 nop 985 986 987 988 /* Exception handler. */ 989 990 exc_handler: 991 mfc0 $t7, CP0_ERROREPC 992 nop 993 la $ra, exc_handler_end 994 995 exc_write_word: 996 li $t8, 32 997 la $v0, U1TXREG 998 exc_loop: 999 addiu $t8, $t8, -4 1000 srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ 1001 andi $v1, $v1, 0xF 1002 addiu $t9, $v1, -10 /* $t9 >= 10? */ 1003 bgez $t9, exc_alpha 1004 nop 1005 exc_digit: 1006 addiu $v1, $v1, 48 /* convert to digit: '0' */ 1007 j exc_write 1008 nop 1009 exc_alpha: 1010 addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ 1011 exc_write: 1012 sw $v1, 0($v0) 1013 bnez $t8, exc_loop 1014 nop 1015 exc_loop_end: 1016 li $v1, ' ' 1017 sw $v1, 0($v0) 1018 1019 exc_handler_end: 1020 jr $ra 1021 nop