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