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