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 la $v0, DCH1INT 636 li $v1, (1 << 19) /* CHBCIE = 1 */ 637 sw $v1, 0($v0) 638 639 /* Enable interrupt for address updating. */ 640 641 la $v0, IPC10 642 li $v1, 0b1111100011111 /* DMA1IP, DMA1IS, DMA0IP, DMA0IS = 0 */ 643 sw $v1, CLR($v0) 644 645 la $v0, IPC10 646 li $v1, 0b1111100011111 /* DMA1IP, DMA0IP = 7, DMA1IS, DMA0IS = 3 */ 647 sw $v1, SET($v0) 648 649 la $v0, IEC1 650 li $v1, (0b11 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 1 */ 651 sw $v1, SET($v0) 652 653 /* Enable line channel. */ 654 655 la $v0, DCH0CON 656 li $v1, 0b10000000 657 sw $v1, SET($v0) 658 659 jr $ra 660 nop 661 662 zerodata: 663 .word 0 664 665 fulldata: 666 .word 255 667 668 669 670 /* Utilities. */ 671 672 handle_error_level: 673 mfc0 $t3, CP0_STATUS 674 li $t4, ~(STATUS_ERL | STATUS_EXL) 675 and $t3, $t3, $t4 676 mtc0 $t3, CP0_STATUS 677 jr $ra 678 nop 679 680 enable_interrupts: 681 mfc0 $t3, CP0_STATUS 682 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 683 and $t3, $t3, $t4 684 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 685 and $t3, $t3, $t4 686 ori $t3, $t3, STATUS_IE 687 mtc0 $t3, CP0_STATUS 688 jr $ra 689 nop 690 691 init_interrupts: 692 mfc0 $t3, CP0_DEBUG 693 li $t4, ~DEBUG_DM 694 and $t3, $t3, $t4 695 mtc0 $t3, CP0_DEBUG 696 697 mfc0 $t3, CP0_STATUS 698 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 699 or $t3, $t3, $t4 700 mtc0 $t3, CP0_STATUS 701 702 la $t3, exception_handler 703 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 704 705 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 706 mtc0 $t3, CP0_INTCTL 707 708 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 709 mtc0 $t3, CP0_CAUSE 710 711 jr $ra 712 nop 713 714 715 716 /* Exception servicing. */ 717 718 .section .flash, "a" 719 720 /* TLB error servicing. */ 721 722 tlb_handler: 723 j exception_handler 724 nop 725 726 727 728 /* General exception servicing. */ 729 730 .org 0x180 731 732 exception_handler: 733 j exc_handler 734 nop 735 736 737 738 /* Interrupt servicing. */ 739 740 .org 0x200 741 742 interrupt_handler: 743 744 /* Store affected registers. */ 745 746 li $k0, IRQ_STACK_LIMIT 747 sw $v0, -4($k0) 748 sw $v1, -8($k0) 749 sw $s0, -12($k0) 750 sw $s1, -16($k0) 751 sw $s2, -20($k0) 752 sw $s3, -24($k0) 753 sw $t8, -28($k0) 754 sw $ra, -32($k0) 755 sw $sp, -36($k0) 756 757 /* Load state. */ 758 759 lw $s0, -44($k0) 760 lw $s1, -48($k0) 761 lw $s2, -52($k0) 762 lw $s3, -56($k0) 763 764 li $sp, IRQ_STACK_TOP 765 766 /* Check for a timer interrupt condition. */ 767 768 la $v0, IFS0 769 lw $v1, 0($v0) 770 andi $v1, $v1, (1 << 9) /* T2IF */ 771 beqz $v1, irq_dma 772 nop 773 774 /* Clear the timer interrupt condition. */ 775 776 sw $v1, CLR($v0) 777 778 /* 779 The timer interrupt will only occur outside the visible region, but the 780 interrupt condition will still occur as the timer wraps around. 781 Therefore, the handling of other interrupts may find the timer interrupt 782 condition set. 783 784 For the visible region, the event handler is invoked when handling the 785 DMA interrupt. Otherwise, the event handler is invoked in response to 786 the timer interrupt. 787 */ 788 789 la $t8, visible_active 790 beq $s1, $t8, irq_dma 791 nop 792 793 /* Increment the line counter (only outside the visible region). */ 794 795 addiu $s0, $s0, 1 796 797 /* Jump to the event handler (only outside the visible region). */ 798 799 jalr $s1 800 nop 801 802 irq_dma: 803 /* Check for a DMA interrupt condition. */ 804 805 la $v0, IFS1 806 lw $v1, 0($v0) 807 li $t8, (0b11 << 28) /* DMA1IF, DMA0IF */ 808 and $v1, $v1, $t8 809 beqz $v1, irq_exit 810 nop 811 812 /* Clear the DMA interrupt condition. */ 813 814 sw $v1, CLR($v0) 815 816 /* Test the block transfer completion interrupt flag. */ 817 818 la $v0, DCH0INT 819 lw $v1, 0($v0) 820 andi $v1, $v1, (1 << 3) /* CHBCIF */ 821 beqz $v1, irq_dma_next 822 nop 823 824 /* Clear the block transfer completion interrupt flag. */ 825 826 sw $v1, CLR($v0) 827 828 irq_dma_next: 829 /* Test the block transfer completion interrupt flag. */ 830 831 la $v0, DCH1INT 832 lw $v1, 0($v0) 833 andi $v1, $v1, (1 << 3) /* CHBCIF */ 834 beqz $v1, irq_exit 835 nop 836 837 /* Clear the block transfer completion interrupt flag. */ 838 839 sw $v1, CLR($v0) 840 841 /* 842 The DMA interrupt should only be active within the visible region. 843 The event handler is invoked here instead of in response to a timer 844 interrupt within that region. 845 */ 846 847 /* Increment the line counter (only within the visible region). */ 848 849 addiu $s0, $s0, 1 850 851 /* Jump to the event handler (only within the visible region). */ 852 853 jalr $s1 854 nop 855 856 /* Jump to the DMA update routine. */ 857 858 j visible_update_address 859 nop 860 861 irq_exit: 862 /* Save state. */ 863 864 li $k0, IRQ_STACK_LIMIT 865 sw $s0, -44($k0) 866 sw $s1, -48($k0) 867 sw $s2, -52($k0) 868 sw $s3, -56($k0) 869 870 /* Restore affected registers. */ 871 872 lw $v0, -4($k0) 873 lw $v1, -8($k0) 874 lw $s0, -12($k0) 875 lw $s1, -16($k0) 876 lw $s2, -20($k0) 877 lw $s3, -24($k0) 878 lw $t8, -28($k0) 879 lw $ra, -32($k0) 880 lw $sp, -36($k0) 881 882 eret 883 nop 884 885 886 887 exc_handler: 888 li $t9, 0x80000000 889 mfc0 $t6, CP0_ERROREPC 890 nop 891 exc_loop: 892 and $t7, $t9, $t6 893 beqz $t7, exc_errorepc_zero 894 nop 895 exc_errorepc_one: 896 la $v0, PORTA 897 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 898 sw $v1, SET($v0) 899 j exc_loop_wait 900 nop 901 exc_errorepc_zero: 902 la $v0, PORTA 903 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 904 sw $v1, SET($v0) 905 exc_loop_wait: 906 li $t8, 5000000 907 exc_loop_delay: 908 addiu $t8, $t8, -1 909 bnez $t8, exc_loop_delay 910 nop 911 la $v0, PORTA 912 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 913 sw $v1, CLR($v0) 914 exc_loop_wait_again: 915 li $t8, 2500000 916 exc_loop_delay_again: 917 addiu $t8, $t8, -1 918 bnez $t8, exc_loop_delay_again 919 nop 920 exc_errorepc_next: 921 srl $t9, $t9, 1 922 bnez $t9, exc_loop 923 nop 924 j exc_handler 925 nop 926 927 928 929 /* Event routines. */ 930 931 /* The vertical back porch. */ 932 933 vbp_active: 934 /* Test for visible region. */ 935 936 sltiu $v0, $s0, VISIBLE_START 937 bnez $v0, _vbp_active_ret 938 nop 939 940 /* Start the visible region. */ 941 942 la $s1, visible_active 943 944 /* Reset the line address. */ 945 946 move $s2, $s3 947 948 /* Update the source address. */ 949 950 la $v0, DCH0SSA 951 sw $s2, 0($v0) 952 953 /* Enable the line channel for timer event transfer initiation. */ 954 955 la $v0, DCH0ECON 956 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 957 sw $v1, SET($v0) 958 959 /* Disable the timer interrupt during the visible period. */ 960 961 la $v0, IEC0 962 li $v1, (1 << 9) 963 sw $v1, CLR($v0) /* T2IE = 0 */ 964 965 _vbp_active_ret: 966 jr $ra 967 nop 968 969 970 971 /* The visible region. */ 972 973 visible_active: 974 /* Test for front porch. */ 975 976 sltiu $v0, $s0, VFP_START 977 bnez $v0, _visible_active_ret 978 nop 979 980 /* Start the front porch region. */ 981 982 la $s1, vfp_active 983 984 /* Re-enable the timer interrupt after the visible period. */ 985 986 la $v0, IEC0 987 li $v1, (1 << 9) 988 sw $v1, SET($v0) /* T2IE = 1 */ 989 990 _visible_active_ret: 991 jr $ra 992 nop 993 994 995 996 /* DMA update routine. */ 997 998 visible_update_address: 999 1000 /* Test for the last visible line. */ 1001 1002 la $v0, vfp_active 1003 bne $s1, $v0, _visible_update_address 1004 nop 1005 1006 /* Disable the line channel. */ 1007 1008 la $v0, DCH0ECON 1009 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 1010 sw $v1, CLR($v0) 1011 1012 j _visible_update_ret 1013 nop 1014 1015 _visible_update_address: 1016 1017 /* 1018 Update the line data address if the line counter (referring to the 1019 next line) is even. 1020 */ 1021 1022 andi $t8, $s0, 1 1023 bnez $t8, _visible_update_ret 1024 nop 1025 1026 /* Reference the next line and update the DMA source address. */ 1027 1028 addiu $s2, $s2, LINE_LENGTH 1029 1030 /* Test for wraparound. */ 1031 1032 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1033 sltu $t8, $s2, $t8 1034 bnez $t8, _visible_dma_update 1035 nop 1036 1037 /* Reset the source address. */ 1038 1039 li $s2, SCREEN_BASE 1040 1041 _visible_dma_update: 1042 1043 /* Update the source address. */ 1044 1045 la $v0, DCH0SSA 1046 sw $s2, 0($v0) 1047 1048 _visible_update_ret: 1049 j irq_exit 1050 nop 1051 1052 1053 1054 /* Within the vertical front porch. */ 1055 1056 vfp_active: 1057 /* Test for vsync. */ 1058 1059 sltiu $v0, $s0, VSYNC_START 1060 bnez $v0, _vfp_active_ret 1061 nop 1062 1063 /* Start the vsync. */ 1064 1065 la $s1, vsync_active 1066 1067 /* Bring vsync low when the next line starts. */ 1068 1069 la $v0, OC2CON 1070 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1071 sw $v1, 0($v0) 1072 1073 _vfp_active_ret: 1074 jr $ra 1075 nop 1076 1077 1078 1079 /* The vsync period. */ 1080 1081 vsync_active: 1082 /* Test for front porch. */ 1083 1084 sltiu $v0, $s0, VSYNC_END 1085 bnez $v0, _vsync_active_ret 1086 nop 1087 1088 /* Start the back porch. */ 1089 1090 move $s0, $zero 1091 la $s1, vbp_active 1092 1093 /* Bring vsync high when the next line starts. */ 1094 1095 la $v0, OC2CON 1096 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1097 sw $v1, 0($v0) 1098 1099 _vsync_active_ret: 1100 jr $ra 1101 nop