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