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