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_timers 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_timers: 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 /* Initialise Timer3 for line DMA cell transfer. */ 337 338 la $v0, T3CON 339 sw $zero, 0($v0) /* T3CON = 0 */ 340 nop 341 342 la $v0, TMR3 343 sw $zero, 0($v0) /* TMR3 = 0 */ 344 345 la $v0, PR3 346 li $v1, 1 347 sw $v1, 0($v0) /* PR3 = 1 */ 348 349 /* Start timer. */ 350 351 la $v0, T3CON 352 li $v1, (1 << 15) 353 sw $v1, SET($v0) /* ON = 1 */ 354 355 jr $ra 356 nop 357 358 359 360 /* 361 Output compare initialisation. 362 363 Timer2 will be used to trigger two events using OC1: one initiating the hsync 364 pulse, and one terminating the pulse. The pulse should appear after the line 365 data has been transferred using DMA, but this is achieved by just choosing 366 suitable start and end values. 367 368 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 369 reverse the level at a later point. In this way, the vsync pulse is generated 370 and is synchronised to the display lines. 371 372 The OC1 interrupt is used to update the state machine handling the display, 373 also invoking the address update routine at the end of each visible display 374 line, changing the source address of the DMA channel. 375 */ 376 377 init_oc: 378 /* Disable OC1 interrupts. */ 379 380 li $v1, (1 << 7) 381 382 la $v0, IEC0 383 sw $v1, CLR($v0) /* IEC0<7> = OC1IE = 0 */ 384 la $v0, IFS0 385 sw $v1, CLR($v0) /* IFS0<7> = OC1IF = 0 */ 386 387 /* Initialise OC1. */ 388 389 la $v0, OC1CON 390 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 391 sw $v1, 0($v0) 392 393 /* Pulse start and end. */ 394 395 la $v0, OC1R 396 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 397 sw $v1, 0($v0) 398 399 la $v0, OC1RS 400 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 401 sw $v1, 0($v0) 402 403 /* Enable interrupt for address updating. */ 404 405 la $v0, IPC1 406 li $v1, (0b11111 << 16) 407 sw $v1, SET($v0) /* OC1IP = 7; OC1IS = 3 */ 408 409 la $v0, IEC0 410 li $v1, (1 << 7) 411 sw $v1, SET($v0) /* IEC0<7> = OC1IE = 1 */ 412 413 /* OC1 is enabled. */ 414 415 la $v0, OC1CON 416 li $v1, (1 << 15) 417 sw $v1, SET($v0) 418 419 /* Disable OC2 interrupts. */ 420 421 li $v1, (1 << 12) 422 423 la $v0, IEC0 424 sw $v1, CLR($v0) /* IEC0<12> = OC2IE = 0 */ 425 la $v0, IFS0 426 sw $v1, CLR($v0) /* IFS0<12> = OC2IF = 0 */ 427 428 /* Initialise OC2. */ 429 430 la $v0, OC2CON 431 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 432 sw $v1, 0($v0) 433 434 /* Set pulse position. */ 435 436 la $v0, OC2R 437 sw $zero, 0($v0) 438 439 /* Enable OC2 later. */ 440 441 jr $ra 442 nop 443 444 init_io_pins: 445 /* Unlock the configuration register bits. */ 446 447 la $v0, SYSKEY 448 sw $zero, 0($v0) 449 li $v1, 0xAA996655 450 sw $v1, 0($v0) 451 li $v1, 0x556699AA 452 sw $v1, 0($v0) 453 454 la $v0, CFGCON 455 lw $t8, 0($v0) 456 li $v1, (1 << 13) /* IOLOCK = 0 */ 457 sw $v1, CLR($v0) 458 459 /* Map OC1 to RPA0. */ 460 461 la $v0, RPA0R 462 li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ 463 sw $v1, 0($v0) 464 465 /* Map OC2 to RPA1. */ 466 467 la $v0, RPA1R 468 li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ 469 sw $v1, 0($v0) 470 471 /* Map U1TX to RPB15. */ 472 473 la $v0, RPB15R 474 li $v1, 0b0001 /* RPB15R<3:0> = 0001 (U1TX) */ 475 sw $v1, 0($v0) 476 477 la $v0, CFGCON 478 sw $t8, 0($v0) 479 480 /* Lock the oscillator control register again. */ 481 482 la $v0, SYSKEY 483 li $v1, 0x33333333 484 sw $v1, 0($v0) 485 486 jr $ra 487 nop 488 489 490 491 /* 492 Direct Memory Access initialisation. 493 494 Write 160 pixels to PORTB for the line data. This is initiated by a timer 495 interrupt. 496 */ 497 498 init_dma: 499 /* Disable DMA interrupts. */ 500 501 li $v1, (0b111 << 28) 502 503 la $v0, IEC1 504 sw $v1, CLR($v0) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 505 506 /* Clear DMA interrupt flags. */ 507 508 la $v0, IFS1 509 sw $v1, CLR($v0) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 510 511 /* Enable DMA. */ 512 513 la $v0, DMACON 514 li $v1, (1 << 15) 515 sw $v1, SET($v0) 516 517 /* 518 Initialise a line channel. 519 The line channel will be channel 0 (x = 0). 520 521 Specify a priority of 3: 522 DCHxCON<1:0> = CHPRI<1:0> = 3 523 524 Auto-enable the channel: 525 DCHxCON<4> = CHAEN = 1 526 */ 527 528 la $v0, DCH0CON 529 li $v1, 0b10011 530 sw $v1, 0($v0) 531 532 /* 533 Initialise a level reset channel. 534 The reset channel will be channel 1 (x = 1). 535 536 Specify a priority of 3: 537 DCHxCON<1:0> = CHPRI<1:0> = 3 538 539 Chain the channel to channel 0: 540 DCHxCON<5> = CHCHN = 1 541 */ 542 543 la $v0, DCH1CON 544 li $v1, 0b100011 545 sw $v1, 0($v0) 546 547 la $v0, DCH2CON 548 li $v1, 0b100011 549 sw $v1, 0($v0) 550 551 /* 552 Initiate channel transfers when the initiating interrupt condition 553 occurs: 554 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 555 DCHxECON<4> = SIRQEN = 1 556 */ 557 558 la $v0, DCH0ECON 559 li $v1, (9 << 8) | (1 << 4) 560 sw $v1, 0($v0) 561 562 /* 563 Initiate reset channel transfer when channel 0 is finished; 564 timer 3 causes cell transfers: 565 DCHxECON<15:8> = CHSIRQ<7:0> = timer 3 interrupt 566 DCHxECON<4> = SIRQEN = 1 567 */ 568 569 la $v0, DCH1ECON 570 li $v1, (14 << 8) | (1 << 4) 571 sw $v1, 0($v0) 572 573 la $v0, DCH2ECON 574 li $v1, (14 << 8) | (1 << 4) 575 sw $v1, 0($v0) 576 577 /* 578 The line channel has a cell size of the number bytes in a line: 579 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 580 */ 581 582 la $v0, DCH0CSIZ 583 li $v1, LINE_LENGTH 584 sw $v1, 0($v0) 585 586 /* 587 The reset channel has a cell size of a single zero byte: 588 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 589 */ 590 591 la $v0, DCH1CSIZ 592 li $v1, 1 593 sw $v1, 0($v0) 594 595 la $v0, DCH2CSIZ 596 sw $v1, 0($v0) 597 598 /* 599 The source has a size identical to the cell size: 600 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 601 */ 602 603 la $v0, DCH0SSIZ 604 li $v1, LINE_LENGTH 605 sw $v1, 0($v0) 606 607 la $v0, DCH1SSIZ 608 li $v1, 1 609 sw $v1, 0($v0) 610 611 la $v0, DCH2SSIZ 612 sw $v1, 0($v0) 613 614 /* 615 The source address is the physical address of the line data: 616 DCHxSSA = physical(line data address) 617 */ 618 619 la $v0, DCH0SSA 620 li $v1, SCREEN_BASE 621 sw $v1, 0($v0) 622 623 /* 624 For the reset channel, a single byte of zero is transferred: 625 DCHxSSA = physical(zero data address) 626 */ 627 628 la $v0, DCH1SSA 629 la $v1, fulldata 630 li $t8, KSEG0_BASE 631 subu $v1, $v1, $t8 632 sw $v1, 0($v0) 633 634 la $v0, DCH2SSA 635 la $v1, zerodata 636 li $t8, KSEG0_BASE 637 subu $v1, $v1, $t8 638 sw $v1, 0($v0) 639 640 /* 641 The destination has a size of 1 byte: 642 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 643 */ 644 645 li $v1, 1 646 647 la $v0, DCH0DSIZ 648 sw $v1, 0($v0) 649 650 la $v0, DCH1DSIZ 651 sw $v1, 0($v0) 652 653 la $v0, DCH2DSIZ 654 sw $v1, 0($v0) 655 656 /* 657 The destination address is the physical address of PORTB: 658 DCHxDSA = physical(PORTB) 659 */ 660 661 li $v1, PORTB 662 li $t8, KSEG1_BASE 663 subu $v1, $v1, $t8 664 665 la $v0, DCH0DSA 666 sw $v1, 0($v0) 667 668 la $v0, DCH1DSA 669 sw $v1, 0($v0) 670 671 la $v0, DCH2DSA 672 sw $v1, 0($v0) 673 674 /* Enable interrupt for channel chaining. */ 675 676 la $v0, DCH0INT 677 li $v1, (1 << 19) /* CHBCIE = 1 */ 678 sw $v1, 0($v0) 679 680 la $v0, IPC10 681 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 682 sw $v1, CLR($v0) 683 li $v1, 0b10011 /* DMA0IP = 4, DMA0IS = 3 */ 684 sw $v1, SET($v0) 685 686 la $v0, IEC1 687 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 688 sw $v1, SET($v0) 689 690 /* Enable line channel later. */ 691 692 jr $ra 693 nop 694 695 zerodata: 696 .word 0 697 698 fulldata: 699 .word 255 700 701 702 703 /* 704 UART initialisation. 705 706 Initialise UART transmission at 115200 baud. This is sensitive to the peripheral 707 clock frequency. 708 */ 709 710 init_uart: 711 /* Initialise UART. */ 712 713 la $v0, U1BRG 714 li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ 715 sw $v1, 0($v0) 716 717 la $v0, U1MODE 718 li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ 719 sw $v1, CLR($v0) 720 721 /* Start UART. */ 722 723 la $v0, U1STA 724 li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ 725 sw $v1, SET($v0) 726 727 la $v0, U1MODE 728 li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ 729 sw $v1, SET($v0) 730 731 jr $ra 732 nop 733 734 735 736 /* Utilities. */ 737 738 handle_error_level: 739 mfc0 $t3, CP0_STATUS 740 li $t4, ~(STATUS_ERL | STATUS_EXL) 741 and $t3, $t3, $t4 742 mtc0 $t3, CP0_STATUS 743 jr $ra 744 nop 745 746 enable_interrupts: 747 mfc0 $t3, CP0_STATUS 748 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 749 and $t3, $t3, $t4 750 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 751 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 752 and $t3, $t3, $t4 753 ori $t3, $t3, STATUS_IE 754 mtc0 $t3, CP0_STATUS 755 jr $ra 756 nop 757 758 init_interrupts: 759 mfc0 $t3, CP0_DEBUG 760 li $t4, ~DEBUG_DM 761 and $t3, $t3, $t4 762 mtc0 $t3, CP0_DEBUG 763 764 mfc0 $t3, CP0_STATUS 765 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 766 or $t3, $t3, $t4 767 mtc0 $t3, CP0_STATUS 768 769 la $t3, exception_handler 770 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 771 772 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 773 mtc0 $t3, CP0_INTCTL 774 775 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 776 mtc0 $t3, CP0_CAUSE 777 778 jr $ra 779 nop 780 781 782 783 /* Exception servicing. */ 784 785 .section .flash, "a" 786 787 /* TLB error servicing. */ 788 789 tlb_handler: 790 j exception_handler 791 nop 792 793 794 795 /* General exception servicing. */ 796 797 .org 0x180 798 799 exception_handler: 800 j exc_handler 801 nop 802 803 804 805 /* Interrupt servicing. */ 806 807 .org 0x200 808 809 interrupt_handler: 810 811 /* 812 Save affected registers, restoring IRQ state and switching to the IRQ 813 stack. 814 */ 815 816 li $k0, IRQ_STACK_LIMIT 817 save_affected 818 load_state 819 li $sp, IRQ_STACK_TOP 820 821 /* Check for the output compare interrupt condition. */ 822 823 la $v0, IFS0 824 lw $v1, 0($v0) 825 andi $v1, $v1, (1 << 7) /* OC1IF */ 826 beqz $v1, irq_dma 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_dma: 844 /* Clear the DMA channel completion condition. */ 845 846 la $v0, IFS1 847 lw $v1, 0($v0) 848 li $t8, (1 << 28) 849 and $v1, $v1, $t8 850 beqz $v1, irq_exit 851 nop 852 853 sw $v1, CLR($v0) /* IFS1<28> = DMA0IF = 0 */ 854 855 la $v0, DCH0INT 856 lw $v1, 0($v0) 857 andi $v1, $v1, (1 << 3) 858 beqz $v1, irq_exit 859 nop 860 861 sw $v1, CLR($v0) /* CHBCIF = 0 */ 862 863 irq_exit: 864 /* 865 Save IRQ state and restore the affected registers, switching back to the 866 original stack. 867 */ 868 869 li $k0, IRQ_STACK_LIMIT 870 save_state 871 load_affected 872 873 eret 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 /* Enable the line channel for timer event transfer initiation. */ 903 904 la $v0, DCH0CON 905 li $v1, (1 << 7) 906 sw $v1, SET($v0) 907 908 _vbp_active_ret: 909 jr $ra 910 nop 911 912 913 914 /* The visible region. */ 915 916 visible_active: 917 /* Test for front porch. */ 918 919 sltiu $v0, $s0, VFP_START 920 bnez $v0, visible_update_address 921 nop 922 923 /* Start the front porch region. */ 924 925 la $s1, vfp_active 926 927 /* Disable the line channel. */ 928 929 la $v0, DCH0CON 930 li $v1, (1 << 7) 931 sw $v1, CLR($v0) 932 933 _visible_active_ret: 934 jr $ra 935 nop 936 937 938 939 /* DMA update routine. */ 940 941 visible_update_address: 942 943 /* 944 Update the line data address if the line counter (referring to the 945 next line) is even. 946 */ 947 948 andi $t8, $s0, 1 949 bnez $t8, _visible_update_ret 950 nop 951 952 /* Reference the next line and update the DMA source address. */ 953 954 addiu $s2, $s2, LINE_LENGTH 955 956 /* Test for wraparound. */ 957 958 li $t8, (SCREEN_BASE + SCREEN_SIZE) 959 sltu $t8, $s2, $t8 960 bnez $t8, _visible_dma_update 961 nop 962 963 /* Reset the source address. */ 964 965 li $s2, SCREEN_BASE 966 967 _visible_dma_update: 968 969 /* Update the source address. */ 970 971 la $v0, DCH0SSA 972 sw $s2, 0($v0) 973 974 _visible_update_ret: 975 jr $ra 976 nop 977 978 979 980 /* Within the vertical front porch. */ 981 982 vfp_active: 983 /* Test for vsync. */ 984 985 sltiu $v0, $s0, VSYNC_START 986 bnez $v0, _vfp_active_ret 987 nop 988 989 /* Start the vsync. */ 990 991 la $s1, vsync_active 992 993 /* Bring vsync low when the next line starts. */ 994 995 la $v0, OC2CON 996 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 997 sw $v1, 0($v0) 998 999 _vfp_active_ret: 1000 jr $ra 1001 nop 1002 1003 1004 1005 /* The vsync period. */ 1006 1007 vsync_active: 1008 /* Test for front porch. */ 1009 1010 sltiu $v0, $s0, VSYNC_END 1011 bnez $v0, _vsync_active_ret 1012 nop 1013 1014 /* Start the back porch. */ 1015 1016 move $s0, $zero 1017 la $s1, vbp_active 1018 1019 /* Bring vsync high when the next line starts. */ 1020 1021 la $v0, OC2CON 1022 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1023 sw $v1, 0($v0) 1024 1025 _vsync_active_ret: 1026 jr $ra 1027 nop 1028 1029 1030 1031 /* Exception handler. */ 1032 1033 exc_handler: 1034 mfc0 $t7, CP0_ERROREPC 1035 nop 1036 la $ra, exc_handler_end 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, ' ' 1060 sw $v1, 0($v0) 1061 1062 exc_handler_end: 1063 jr $ra 1064 nop