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 fontdata 61 .extern blit_string 62 .extern message 63 64 _start: 65 /* 66 Configure RAM. 67 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 68 */ 69 70 la $v0, BMXCON 71 lw $v1, 0($v0) 72 73 /* Set zero wait states for address setup. */ 74 75 li $t8, ~(1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 76 and $v1, $v1, $t8 77 78 /* Set bus arbitration mode. */ 79 80 li $t8, ~0b111 /* BMXCON<2:0> = BMXARB<2:0> = 0 */ 81 ori $t8, $t8, 0b010 /* BMXCON<2:0> = BMXARB<2:0> = 2 */ 82 and $v1, $v1, $t8 83 sw $v1, 0($v0) 84 85 /* Enable caching. */ 86 87 mfc0 $v1, CP0_CONFIG 88 li $t8, ~CONFIG_K0 89 and $v1, $v1, $t8 90 ori $v1, $v1, CONFIG_K0_CACHABLE_NONCOHERENT 91 mtc0 $v1, CP0_CONFIG 92 nop 93 94 /* Get the RAM size. */ 95 96 la $v0, BMXDRMSZ 97 lw $t0, 0($v0) 98 99 /* Initialise the stack pointer. */ 100 101 li $v1, KSEG0_BASE 102 addu $sp, $t0, $v1 /* sp = KSEG0_BASE + RAM size */ 103 104 /* Initialise the globals pointer. */ 105 106 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 107 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 108 109 /* Set pins for output. */ 110 111 jal init_pins 112 nop 113 114 la $t0, PORTA 115 li $t1, (1 << 3) /* PORTA<3> = RA3 */ 116 sw $t1, CLR($t0) 117 118 jal init_oc_pins 119 nop 120 121 /* Initialise the status register. */ 122 123 jal init_interrupts 124 nop 125 126 /* Initialise framebuffer. */ 127 128 la $a0, screendata 129 jal init_framebuffer 130 nop 131 132 sync 133 134 /* Initialise timer. */ 135 136 jal init_timers 137 nop 138 139 /* Initialise DMA. */ 140 141 jal init_dma 142 nop 143 144 /* Initialise OC1 and OC2. */ 145 146 jal init_oc 147 nop 148 149 /* Initialise the display state. */ 150 151 li $s0, 0 /* line counter */ 152 la $s1, vbp_active /* current event */ 153 li $s2, SCREEN_BASE /* line address */ 154 li $s3, SCREEN_BASE /* screen address */ 155 156 /* Save the state for retrieval in the interrupt handler. */ 157 158 li $k0, IRQ_STACK_LIMIT 159 sw $s0, -44($k0) 160 sw $s1, -48($k0) 161 sw $s2, -52($k0) 162 sw $s3, -56($k0) 163 164 /* Enable interrupts and loop. */ 165 166 jal enable_interrupts 167 nop 168 169 jal handle_error_level 170 nop 171 172 /* Main program. */ 173 174 li $a1, (3 << 24) /* counter ~= 50000000 */ 175 li $a2, 0xffffff /* test counter at every 1/4 of range */ 176 move $t2, $zero /* picture to show */ 177 178 /* Monitoring loop. */ 179 loop: 180 addiu $a1, $a1, -1 /* counter -= 1 */ 181 and $t1, $a2, $a1 182 bnez $t1, loop 183 nop 184 185 la $t0, PORTA 186 li $t1, (1 << 3) /* PORTA<3> = RA3 */ 187 sw $t1, INV($t0) 188 189 bnez $a1, loop /* until counter == 0 */ 190 nop 191 192 bnez $t2, _picture1 193 nop 194 195 /* Show picture 0. */ 196 197 la $a0, screendata 198 jal init_framebuffer 199 nop 200 201 la $a0, message0 202 li $a1, SCREEN_BASE_KSEG0 203 jal blit_string 204 nop 205 206 li $t2, 1 207 j _next 208 nop 209 210 _picture1: 211 /* Show picture 1. */ 212 213 jal init_framebuffer_with_pattern 214 nop 215 216 la $a0, message1 217 li $a1, SCREEN_BASE_KSEG0 218 jal blit_string 219 nop 220 221 move $t2, $zero 222 223 _next: 224 li $a1, (3 << 24) /* counter ~= 50000000 */ 225 li $a2, 0xffffff /* test counter at every 1/4 of range */ 226 j loop 227 nop 228 229 230 231 init_pins: 232 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 233 234 la $v0, CFGCON 235 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 236 sw $v1, CLR($v0) 237 238 init_outputs: 239 /* Remove analogue features from pins. */ 240 241 la $v0, ANSELA 242 sw $zero, 0($v0) /* ANSELA = 0 */ 243 la $v0, ANSELB 244 sw $zero, 0($v0) /* ANSELB = 0 */ 245 246 la $v0, TRISA 247 sw $zero, 0($v0) 248 la $v0, TRISB 249 sw $zero, 0($v0) 250 251 la $v0, PORTA 252 sw $zero, 0($v0) 253 la $v0, PORTB 254 sw $zero, 0($v0) 255 256 jr $ra 257 nop 258 259 260 261 /* Initialisation routines. */ 262 263 init_timers: 264 265 /* Initialise Timer2 for sync pulses. */ 266 267 la $v0, T2CON 268 sw $zero, 0($v0) /* T2CON = 0 */ 269 nop 270 271 la $v0, TMR2 272 sw $zero, 0($v0) /* TMR2 = 0 */ 273 274 la $v0, PR2 275 li $v1, HFREQ_LIMIT 276 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 277 278 /* Initialise Timer2 interrupt. */ 279 280 la $v0, IFS0 281 li $v1, (1 << 9) 282 sw $v1, CLR($v0) /* T2IF = 0 */ 283 284 la $v0, IPC2 285 li $v1, 0b11111 286 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 287 288 la $v0, IPC2 289 li $v1, 0b11111 290 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 291 292 la $v0, IEC0 293 li $v1, (1 << 9) 294 sw $v1, SET($v0) /* T2IE = 1 */ 295 296 /* Start timer. */ 297 298 la $v0, T2CON 299 li $v1, (1 << 15) 300 sw $v1, SET($v0) /* ON = 1 */ 301 302 /* Initialise Timer3 for line DMA cell transfer. */ 303 304 la $v0, T3CON 305 sw $zero, 0($v0) /* T3CON = 0 */ 306 nop 307 308 la $v0, TMR3 309 sw $zero, 0($v0) /* TMR3 = 0 */ 310 311 la $v0, PR3 312 li $v1, 1 313 sw $v1, 0($v0) /* PR3 = 1 */ 314 315 /* Start timer. */ 316 317 la $v0, T3CON 318 li $v1, (1 << 15) 319 sw $v1, SET($v0) /* ON = 1 */ 320 321 jr $ra 322 nop 323 324 325 326 /* 327 Output compare initialisation. 328 329 Timer2 will be used to trigger two events using OC1: one initiating the hsync 330 pulse, and one terminating the pulse. The pulse should appear after the line 331 data has been transferred using DMA, but this is achieved by just choosing 332 suitable start and end values. 333 334 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 335 reverse the level at a later point. In this way, the vsync pulse is generated 336 and is synchronised to the display lines. 337 */ 338 339 init_oc: 340 /* Disable OC1 interrupts. */ 341 342 la $v0, IEC0 343 li $v1, (1 << 7) /* IEC0<7> = OC1IE = 0 */ 344 sw $v1, CLR($v0) 345 346 la $v0, IFS0 347 li $v1, (1 << 7) /* IFS0<7> = OC1IF = 0 */ 348 sw $v1, CLR($v0) 349 350 /* Initialise OC1. */ 351 352 la $v0, OC1CON 353 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 354 sw $v1, 0($v0) 355 356 /* Pulse start and end. */ 357 358 la $v0, OC1R 359 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 360 sw $v1, 0($v0) 361 362 la $v0, OC1RS 363 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 364 sw $v1, 0($v0) 365 366 /* OC1 is enabled. */ 367 368 la $v0, OC1CON 369 li $v1, (1 << 15) 370 sw $v1, SET($v0) 371 372 /* Disable OC2 interrupts. */ 373 374 la $v0, IEC0 375 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 376 sw $v1, CLR($v0) 377 378 la $v0, IFS0 379 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 380 sw $v1, CLR($v0) 381 382 /* Initialise OC2. */ 383 384 la $v0, OC2CON 385 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 386 sw $v1, 0($v0) 387 388 /* Set pulse position. */ 389 390 la $v0, OC2R 391 sw $zero, 0($v0) 392 393 /* Enable OC2 later. */ 394 395 jr $ra 396 nop 397 398 init_oc_pins: 399 /* Unlock the configuration register bits. */ 400 401 la $v0, SYSKEY 402 sw $zero, 0($v0) 403 li $v1, 0xAA996655 404 sw $v1, 0($v0) 405 li $v1, 0x556699AA 406 sw $v1, 0($v0) 407 408 la $v0, CFGCON 409 lw $t8, 0($v0) 410 li $v1, (1 << 13) /* IOLOCK = 0 */ 411 sw $v1, CLR($v0) 412 413 /* Map OC1 to RPA0. */ 414 415 la $v0, RPA0R 416 li $v1, 0b0101 /* RPA0R<3:0> = 0101 (OC1) */ 417 sw $v1, 0($v0) 418 419 /* Map OC2 to RPA1. */ 420 421 la $v0, RPA1R 422 li $v1, 0b0101 /* RPA1R<3:0> = 0101 (OC2) */ 423 sw $v1, 0($v0) 424 425 la $v0, CFGCON 426 sw $t8, 0($v0) 427 428 /* Lock the oscillator control register again. */ 429 430 la $v0, SYSKEY 431 li $v1, 0x33333333 432 sw $v1, 0($v0) 433 434 jr $ra 435 nop 436 437 438 439 /* 440 Direct Memory Access initialisation. 441 442 Write 160 pixels to PORTB for the line data. This is initiated by a timer 443 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 444 address update routine, changing the source address of the DMA channel. 445 */ 446 447 init_dma: 448 /* Disable DMA interrupts. */ 449 450 la $v0, IEC1 451 li $v1, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 452 sw $v1, CLR($v0) 453 454 /* Clear DMA interrupt flags. */ 455 456 la $v0, IFS1 457 li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 458 sw $v1, CLR($v0) 459 460 /* Enable DMA. */ 461 462 la $v0, DMACON 463 li $v1, (1 << 15) 464 sw $v1, SET($v0) 465 466 /* 467 Initialise a line channel. 468 The line channel will be channel 0 (x = 0). 469 470 Specify a priority of 3: 471 DCHxCON<1:0> = CHPRI<1:0> = 3 472 473 Auto-enable the channel: 474 DCHxCON<4> = CHAEN = 1 475 */ 476 477 la $v0, DCH0CON 478 li $v1, 0b10011 479 sw $v1, 0($v0) 480 481 /* 482 Initialise a level reset channel. 483 The reset channel will be channel 1 (x = 1). 484 485 Specify a priority of 3: 486 DCHxCON<1:0> = CHPRI<1:0> = 3 487 488 Chain the channel to channel 0: 489 DCHxCON<5> = CHCHN = 1 490 491 Allow the channel to receive events when disabled: 492 DCHxCON<6> = CHAED = 1 493 */ 494 495 la $v0, DCH1CON 496 li $v1, 0b1100011 497 sw $v1, 0($v0) 498 499 la $v0, DCH2CON 500 li $v1, 0b1100011 501 sw $v1, 0($v0) 502 503 /* 504 Initiate channel transfers when the initiating interrupt condition 505 occurs: 506 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 507 DCHxECON<4> = SIRQEN = 1 508 509 For now, however, prevent initiation by not setting SIRQEN. 510 */ 511 512 la $v0, DCH0ECON 513 li $v1, (9 << 8) 514 sw $v1, 0($v0) 515 516 /* 517 Initiate reset channel transfer when channel 0 is finished; 518 timer 3 causes cell transfers: 519 DCHxECON<15:8> = CHSIRQ<7:0> = timer 3 interrupt 520 DCHxECON<4> = SIRQEN = 1 521 */ 522 523 la $v0, DCH1ECON 524 li $v1, (14 << 8) | (1 << 4) 525 sw $v1, 0($v0) 526 527 la $v0, DCH2ECON 528 li $v1, (14 << 8) | (1 << 4) 529 sw $v1, 0($v0) 530 531 /* 532 The line channel has a cell size of the number bytes in a line: 533 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 534 */ 535 536 la $v0, DCH0CSIZ 537 li $v1, LINE_LENGTH 538 sw $v1, 0($v0) 539 540 /* 541 The reset channel has a cell size of a single zero byte: 542 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 543 */ 544 545 la $v0, DCH1CSIZ 546 li $v1, 1 547 sw $v1, 0($v0) 548 549 la $v0, DCH2CSIZ 550 sw $v1, 0($v0) 551 552 /* 553 The source has a size identical to the cell size: 554 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 555 */ 556 557 la $v0, DCH0SSIZ 558 li $v1, LINE_LENGTH 559 sw $v1, 0($v0) 560 561 la $v0, DCH1SSIZ 562 li $v1, 1 563 sw $v1, 0($v0) 564 565 la $v0, DCH2SSIZ 566 sw $v1, 0($v0) 567 568 /* 569 The source address is the physical address of the line data: 570 DCHxSSA = physical(line data address) 571 */ 572 573 la $v0, DCH0SSA 574 li $v1, SCREEN_BASE 575 sw $v1, 0($v0) 576 577 /* 578 For the reset channel, a single byte of zero is transferred: 579 DCHxSSA = physical(zero data address) 580 */ 581 582 la $v0, DCH1SSA 583 la $v1, fulldata 584 li $t8, KSEG0_BASE 585 subu $v1, $v1, $t8 586 sw $v1, 0($v0) 587 588 la $v0, DCH2SSA 589 la $v1, zerodata 590 li $t8, KSEG0_BASE 591 subu $v1, $v1, $t8 592 sw $v1, 0($v0) 593 594 /* 595 The destination has a size of 1 byte: 596 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 597 */ 598 599 la $v0, DCH0DSIZ 600 li $v1, 1 601 sw $v1, 0($v0) 602 603 la $v0, DCH1DSIZ 604 sw $v1, 0($v0) 605 606 la $v0, DCH2DSIZ 607 sw $v1, 0($v0) 608 609 /* 610 The destination address is the physical address of PORTB: 611 DCHxDSA = physical(PORTB) 612 */ 613 614 la $v0, DCH0DSA 615 li $v1, PORTB 616 li $t8, KSEG1_BASE 617 subu $v1, $v1, $t8 618 sw $v1, 0($v0) 619 620 la $v0, DCH1DSA 621 sw $v1, 0($v0) 622 623 la $v0, DCH2DSA 624 sw $v1, 0($v0) 625 626 /* 627 Use the block transfer completion interrupt to indicate when the source 628 address can be updated. 629 */ 630 631 la $v0, DCH0INT 632 li $v1, (1 << 19) /* CHBCIE = 1 */ 633 sw $v1, 0($v0) 634 635 /* Enable interrupt for address updating. */ 636 637 la $v0, IPC10 638 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 639 sw $v1, CLR($v0) 640 641 la $v0, IPC10 642 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 643 sw $v1, SET($v0) 644 645 la $v0, IEC1 646 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 647 sw $v1, SET($v0) 648 649 /* Enable line channel. */ 650 651 la $v0, DCH0CON 652 li $v1, 0b10000000 653 sw $v1, SET($v0) 654 655 jr $ra 656 nop 657 658 zerodata: 659 .word 0 660 661 fulldata: 662 .word 255 663 664 665 666 /* Utilities. */ 667 668 handle_error_level: 669 mfc0 $t3, CP0_STATUS 670 li $t4, ~(STATUS_ERL | STATUS_EXL) 671 and $t3, $t3, $t4 672 mtc0 $t3, CP0_STATUS 673 jr $ra 674 nop 675 676 enable_interrupts: 677 mfc0 $t3, CP0_STATUS 678 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 679 and $t3, $t3, $t4 680 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 681 and $t3, $t3, $t4 682 ori $t3, $t3, STATUS_IE 683 mtc0 $t3, CP0_STATUS 684 jr $ra 685 nop 686 687 init_interrupts: 688 mfc0 $t3, CP0_DEBUG 689 li $t4, ~DEBUG_DM 690 and $t3, $t3, $t4 691 mtc0 $t3, CP0_DEBUG 692 693 mfc0 $t3, CP0_STATUS 694 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 695 or $t3, $t3, $t4 696 mtc0 $t3, CP0_STATUS 697 698 la $t3, exception_handler 699 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 700 701 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 702 mtc0 $t3, CP0_INTCTL 703 704 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 705 mtc0 $t3, CP0_CAUSE 706 707 jr $ra 708 nop 709 710 711 712 /* Exception servicing. */ 713 714 .section .flash, "a" 715 716 /* TLB error servicing. */ 717 718 tlb_handler: 719 j exception_handler 720 nop 721 722 723 724 /* General exception servicing. */ 725 726 .org 0x180 727 728 exception_handler: 729 j exc_handler 730 nop 731 732 733 734 /* Interrupt servicing. */ 735 736 .org 0x200 737 738 interrupt_handler: 739 740 /* Store affected registers. */ 741 742 li $k0, IRQ_STACK_LIMIT 743 sw $v0, -4($k0) 744 sw $v1, -8($k0) 745 sw $s0, -12($k0) 746 sw $s1, -16($k0) 747 sw $s2, -20($k0) 748 sw $s3, -24($k0) 749 sw $t8, -28($k0) 750 sw $ra, -32($k0) 751 sw $sp, -36($k0) 752 753 /* Load state. */ 754 755 lw $s0, -44($k0) 756 lw $s1, -48($k0) 757 lw $s2, -52($k0) 758 lw $s3, -56($k0) 759 760 li $sp, IRQ_STACK_TOP 761 762 /* Check for a timer interrupt condition. */ 763 764 la $v0, IFS0 765 lw $v1, 0($v0) 766 andi $v1, $v1, (1 << 9) /* T2IF */ 767 beqz $v1, irq_dma 768 nop 769 770 /* Clear the timer interrupt condition. */ 771 772 sw $v1, CLR($v0) 773 774 /* 775 The timer interrupt will only occur outside the visible region, but the 776 interrupt condition will still occur as the timer wraps around. 777 Therefore, the handling of other interrupts may find the timer interrupt 778 condition set. 779 780 For the visible region, the event handler is invoked when handling the 781 DMA interrupt. Otherwise, the event handler is invoked in response to 782 the timer interrupt. 783 */ 784 785 la $t8, visible_active 786 beq $s1, $t8, irq_dma 787 nop 788 789 /* Increment the line counter (only outside the visible region). */ 790 791 addiu $s0, $s0, 1 792 793 /* Jump to the event handler (only outside the visible region). */ 794 795 jalr $s1 796 nop 797 798 irq_dma: 799 /* Check for a DMA interrupt condition. */ 800 801 la $v0, IFS1 802 lw $v1, 0($v0) 803 li $t8, (1 << 28) /* DMA0IF */ 804 and $v1, $v1, $t8 805 beqz $v1, irq_exit 806 nop 807 808 /* Clear the DMA interrupt condition. */ 809 810 sw $v1, CLR($v0) 811 812 /* Test the block transfer completion interrupt flag. */ 813 814 la $v0, DCH0INT 815 lw $v1, 0($v0) 816 andi $v1, $v1, (1 << 3) /* CHBCIF */ 817 beqz $v1, irq_dma_next 818 nop 819 820 /* Clear the block transfer completion interrupt flag. */ 821 822 sw $v1, CLR($v0) 823 824 irq_dma_next: 825 /* Test the block transfer completion interrupt flag. */ 826 827 la $v0, DCH1INT 828 lw $v1, 0($v0) 829 andi $v1, $v1, (1 << 3) /* CHBCIF */ 830 beqz $v1, irq_exit 831 nop 832 833 /* Clear the block transfer completion interrupt flag. */ 834 835 sw $v1, CLR($v0) 836 837 /* 838 The DMA interrupt should only be active within the visible region. 839 The event handler is invoked here instead of in response to a timer 840 interrupt within that region. 841 */ 842 843 /* Increment the line counter (only within the visible region). */ 844 845 addiu $s0, $s0, 1 846 847 /* Jump to the event handler (only within the visible region). */ 848 849 jalr $s1 850 nop 851 852 /* Jump to the DMA update routine. */ 853 854 j visible_update_address 855 nop 856 857 irq_exit: 858 /* Save state. */ 859 860 li $k0, IRQ_STACK_LIMIT 861 sw $s0, -44($k0) 862 sw $s1, -48($k0) 863 sw $s2, -52($k0) 864 sw $s3, -56($k0) 865 866 /* Restore affected registers. */ 867 868 lw $v0, -4($k0) 869 lw $v1, -8($k0) 870 lw $s0, -12($k0) 871 lw $s1, -16($k0) 872 lw $s2, -20($k0) 873 lw $s3, -24($k0) 874 lw $t8, -28($k0) 875 lw $ra, -32($k0) 876 lw $sp, -36($k0) 877 878 eret 879 nop 880 881 882 883 exc_handler: 884 li $t9, 0x80000000 885 mfc0 $t6, CP0_ERROREPC 886 nop 887 exc_loop: 888 and $t7, $t9, $t6 889 beqz $t7, exc_errorepc_zero 890 nop 891 exc_errorepc_one: 892 la $v0, PORTA 893 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 894 sw $v1, SET($v0) 895 j exc_loop_wait 896 nop 897 exc_errorepc_zero: 898 la $v0, PORTA 899 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 900 sw $v1, SET($v0) 901 exc_loop_wait: 902 li $t8, 5000000 903 exc_loop_delay: 904 addiu $t8, $t8, -1 905 bnez $t8, exc_loop_delay 906 nop 907 la $v0, PORTA 908 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 909 sw $v1, CLR($v0) 910 exc_loop_wait_again: 911 li $t8, 2500000 912 exc_loop_delay_again: 913 addiu $t8, $t8, -1 914 bnez $t8, exc_loop_delay_again 915 nop 916 exc_errorepc_next: 917 srl $t9, $t9, 1 918 bnez $t9, exc_loop 919 nop 920 j exc_handler 921 nop 922 923 924 925 /* Event routines. */ 926 927 /* The vertical back porch. */ 928 929 vbp_active: 930 /* Test for visible region. */ 931 932 sltiu $v0, $s0, VISIBLE_START 933 bnez $v0, _vbp_active_ret 934 nop 935 936 /* Start the visible region. */ 937 938 la $s1, visible_active 939 940 /* Reset the line address. */ 941 942 move $s2, $s3 943 944 /* Update the source address. */ 945 946 la $v0, DCH0SSA 947 sw $s2, 0($v0) 948 949 /* Enable the line channel for timer event transfer initiation. */ 950 951 la $v0, DCH0ECON 952 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 953 sw $v1, SET($v0) 954 955 /* Disable the timer interrupt during the visible period. */ 956 957 la $v0, IEC0 958 li $v1, (1 << 9) 959 sw $v1, CLR($v0) /* T2IE = 0 */ 960 961 _vbp_active_ret: 962 jr $ra 963 nop 964 965 966 967 /* The visible region. */ 968 969 visible_active: 970 /* Test for front porch. */ 971 972 sltiu $v0, $s0, VFP_START 973 bnez $v0, _visible_active_ret 974 nop 975 976 /* Start the front porch region. */ 977 978 la $s1, vfp_active 979 980 /* Re-enable the timer interrupt after the visible period. */ 981 982 la $v0, IEC0 983 li $v1, (1 << 9) 984 sw $v1, SET($v0) /* T2IE = 1 */ 985 986 _visible_active_ret: 987 jr $ra 988 nop 989 990 991 992 /* DMA update routine. */ 993 994 visible_update_address: 995 996 /* Test for the last visible line. */ 997 998 la $v0, vfp_active 999 bne $s1, $v0, _visible_update_address 1000 nop 1001 1002 /* Disable the line channel. */ 1003 1004 la $v0, DCH0ECON 1005 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 1006 sw $v1, CLR($v0) 1007 1008 j _visible_update_ret 1009 nop 1010 1011 _visible_update_address: 1012 1013 /* 1014 Update the line data address if the line counter (referring to the 1015 next line) is even. 1016 */ 1017 1018 andi $t8, $s0, 1 1019 bnez $t8, _visible_update_ret 1020 nop 1021 1022 /* Reference the next line and update the DMA source address. */ 1023 1024 addiu $s2, $s2, LINE_LENGTH 1025 1026 /* Test for wraparound. */ 1027 1028 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1029 sltu $t8, $s2, $t8 1030 bnez $t8, _visible_dma_update 1031 nop 1032 1033 /* Reset the source address. */ 1034 1035 li $s2, SCREEN_BASE 1036 1037 _visible_dma_update: 1038 1039 /* Update the source address. */ 1040 1041 la $v0, DCH0SSA 1042 sw $s2, 0($v0) 1043 1044 _visible_update_ret: 1045 j irq_exit 1046 nop 1047 1048 1049 1050 /* Within the vertical front porch. */ 1051 1052 vfp_active: 1053 /* Test for vsync. */ 1054 1055 sltiu $v0, $s0, VSYNC_START 1056 bnez $v0, _vfp_active_ret 1057 nop 1058 1059 /* Start the vsync. */ 1060 1061 la $s1, vsync_active 1062 1063 /* Bring vsync low when the next line starts. */ 1064 1065 la $v0, OC2CON 1066 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1067 sw $v1, 0($v0) 1068 1069 _vfp_active_ret: 1070 jr $ra 1071 nop 1072 1073 1074 1075 /* The vsync period. */ 1076 1077 vsync_active: 1078 /* Test for front porch. */ 1079 1080 sltiu $v0, $s0, VSYNC_END 1081 bnez $v0, _vsync_active_ret 1082 nop 1083 1084 /* Start the back porch. */ 1085 1086 move $s0, $zero 1087 la $s1, vbp_active 1088 1089 /* Bring vsync high when the next line starts. */ 1090 1091 la $v0, OC2CON 1092 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1093 sw $v1, 0($v0) 1094 1095 _vsync_active_ret: 1096 jr $ra 1097 nop