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