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 line channel. 501 The line 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 a level reset channel. 516 The reset channel will be channel 1 (x = 1). 517 518 Specify a priority of 3: 519 DCHxCON<1:0> = CHPRI<1:0> = 3 520 521 Chain the channel to channel 0: 522 DCHxCON<5> = CHCHN = 1 523 524 Allow the channel to receive events when disabled: 525 DCHxCON<6> = CHAED = 1 526 */ 527 528 la $v0, DCH1CON 529 li $v1, 0b1100011 530 sw $v1, 0($v0) 531 532 la $v0, DCH2CON 533 li $v1, 0b1100011 534 sw $v1, 0($v0) 535 536 /* 537 Initiate channel transfers when the initiating interrupt condition 538 occurs: 539 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 540 DCHxECON<4> = SIRQEN = 1 541 542 For now, however, prevent initiation by not setting SIRQEN. 543 */ 544 545 la $v0, DCH0ECON 546 li $v1, (9 << 8) 547 sw $v1, 0($v0) 548 549 /* 550 Initiate reset channel transfer when channel 0 is finished: 551 DCHxECON<15:8> = CHSIRQ<7:0> = channel 0 interrupt 552 DCHxECON<4> = SIRQEN = 1 553 */ 554 555 la $v0, DCH1ECON 556 li $v1, (60 << 8) | (1 << 4) 557 sw $v1, 0($v0) 558 559 la $v0, DCH2ECON 560 li $v1, (61 << 8) | (1 << 4) 561 sw $v1, 0($v0) 562 563 /* 564 The line channel has a cell size of the number bytes in a line: 565 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 566 */ 567 568 la $v0, DCH0CSIZ 569 li $v1, LINE_LENGTH 570 sw $v1, 0($v0) 571 572 /* 573 The reset channel has a cell size of a single zero byte: 574 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 575 */ 576 577 la $v0, DCH1CSIZ 578 li $v1, 1 579 sw $v1, 0($v0) 580 581 la $v0, DCH2CSIZ 582 sw $v1, 0($v0) 583 584 /* 585 The source has a size identical to the cell size: 586 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 587 */ 588 589 la $v0, DCH0SSIZ 590 li $v1, LINE_LENGTH 591 sw $v1, 0($v0) 592 593 la $v0, DCH1SSIZ 594 li $v1, 1 595 sw $v1, 0($v0) 596 597 la $v0, DCH2SSIZ 598 sw $v1, 0($v0) 599 600 /* 601 The source address is the physical address of the line data: 602 DCHxSSA = physical(line data address) 603 */ 604 605 la $v0, DCH0SSA 606 li $v1, SCREEN_BASE 607 sw $v1, 0($v0) 608 609 /* 610 For the reset channel, a single byte of zero is transferred: 611 DCHxSSA = physical(zero data address) 612 */ 613 614 la $v0, DCH1SSA 615 la $v1, fulldata 616 li $t8, KSEG0_BASE 617 subu $v1, $v1, $t8 618 sw $v1, 0($v0) 619 620 la $v0, DCH2SSA 621 la $v1, zerodata 622 li $t8, KSEG0_BASE 623 subu $v1, $v1, $t8 624 sw $v1, 0($v0) 625 626 /* 627 The destination has a size of 1 byte: 628 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 629 */ 630 631 la $v0, DCH0DSIZ 632 li $v1, 1 633 sw $v1, 0($v0) 634 635 la $v0, DCH1DSIZ 636 sw $v1, 0($v0) 637 638 la $v0, DCH2DSIZ 639 sw $v1, 0($v0) 640 641 /* 642 The destination address is the physical address of PORTB: 643 DCHxDSA = physical(PORTB) 644 */ 645 646 la $v0, DCH0DSA 647 li $v1, PORTB 648 li $t8, KSEG1_BASE 649 subu $v1, $v1, $t8 650 sw $v1, 0($v0) 651 652 la $v0, DCH1DSA 653 sw $v1, 0($v0) 654 655 la $v0, DCH2DSA 656 sw $v1, 0($v0) 657 658 /* 659 Use the block transfer completion interrupt to indicate when the source 660 address can be updated. 661 */ 662 663 la $v0, DCH0INT 664 li $v1, (1 << 19) /* CHBCIE = 1 */ 665 sw $v1, 0($v0) 666 667 la $v0, DCH1INT 668 li $v1, (1 << 19) /* CHBCIE = 1 */ 669 sw $v1, 0($v0) 670 671 /* Enable interrupt for address updating. */ 672 673 la $v0, IPC10 674 li $v1, 0b1111100011111 /* DMA1IP, DMA1IS, DMA0IP, DMA0IS = 0 */ 675 sw $v1, CLR($v0) 676 677 la $v0, IPC10 678 li $v1, 0b1111100011111 /* DMA1IP, DMA0IP = 7, DMA1IS, DMA0IS = 3 */ 679 sw $v1, SET($v0) 680 681 la $v0, IEC1 682 li $v1, (0b11 << 28) /* IEC1<29:28> = DMA1IE, DMA0IE = 1 */ 683 sw $v1, SET($v0) 684 685 /* Enable line channel. */ 686 687 la $v0, DCH0CON 688 li $v1, 0b10000000 689 sw $v1, SET($v0) 690 691 jr $ra 692 nop 693 694 zerodata: 695 .word 0 696 697 fulldata: 698 .word 255 699 700 701 702 /* Utilities. */ 703 704 handle_error_level: 705 mfc0 $t3, CP0_STATUS 706 li $t4, ~(STATUS_ERL | STATUS_EXL) 707 and $t3, $t3, $t4 708 mtc0 $t3, CP0_STATUS 709 jr $ra 710 nop 711 712 enable_interrupts: 713 mfc0 $t3, CP0_STATUS 714 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 715 and $t3, $t3, $t4 716 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 717 and $t3, $t3, $t4 718 ori $t3, $t3, STATUS_IE 719 mtc0 $t3, CP0_STATUS 720 jr $ra 721 nop 722 723 init_interrupts: 724 mfc0 $t3, CP0_DEBUG 725 li $t4, ~DEBUG_DM 726 and $t3, $t3, $t4 727 mtc0 $t3, CP0_DEBUG 728 729 mfc0 $t3, CP0_STATUS 730 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 731 or $t3, $t3, $t4 732 mtc0 $t3, CP0_STATUS 733 734 la $t3, exception_handler 735 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 736 737 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 738 mtc0 $t3, CP0_INTCTL 739 740 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 741 mtc0 $t3, CP0_CAUSE 742 743 jr $ra 744 nop 745 746 747 748 /* Exception servicing. */ 749 750 .section .flash, "a" 751 752 /* TLB error servicing. */ 753 754 tlb_handler: 755 j exception_handler 756 nop 757 758 759 760 /* General exception servicing. */ 761 762 .org 0x180 763 764 exception_handler: 765 j exc_handler 766 nop 767 768 769 770 /* Interrupt servicing. */ 771 772 .org 0x200 773 774 interrupt_handler: 775 776 /* Store affected registers. */ 777 778 li $k0, IRQ_STACK_LIMIT 779 sw $v0, -4($k0) 780 sw $v1, -8($k0) 781 sw $s0, -12($k0) 782 sw $s1, -16($k0) 783 sw $s2, -20($k0) 784 sw $s3, -24($k0) 785 sw $t8, -28($k0) 786 sw $ra, -32($k0) 787 sw $sp, -36($k0) 788 789 /* Load state. */ 790 791 lw $s0, -44($k0) 792 lw $s1, -48($k0) 793 lw $s2, -52($k0) 794 lw $s3, -56($k0) 795 796 li $sp, IRQ_STACK_TOP 797 798 /* Check for a timer interrupt condition. */ 799 800 la $v0, IFS0 801 lw $v1, 0($v0) 802 andi $v1, $v1, (1 << 9) /* T2IF */ 803 beqz $v1, irq_dma 804 nop 805 806 /* Clear the timer interrupt condition. */ 807 808 sw $v1, CLR($v0) 809 810 /* 811 The timer interrupt will only occur outside the visible region, but the 812 interrupt condition will still occur as the timer wraps around. 813 Therefore, the handling of other interrupts may find the timer interrupt 814 condition set. 815 816 For the visible region, the event handler is invoked when handling the 817 DMA interrupt. Otherwise, the event handler is invoked in response to 818 the timer interrupt. 819 */ 820 821 la $t8, visible_active 822 beq $s1, $t8, irq_dma 823 nop 824 825 /* Increment the line counter (only outside the visible region). */ 826 827 addiu $s0, $s0, 1 828 829 /* Jump to the event handler (only outside the visible region). */ 830 831 jalr $s1 832 nop 833 834 irq_dma: 835 /* Check for a DMA interrupt condition. */ 836 837 la $v0, IFS1 838 lw $v1, 0($v0) 839 li $t8, (0b11 << 28) /* DMA1IF, DMA0IF */ 840 and $v1, $v1, $t8 841 beqz $v1, irq_exit 842 nop 843 844 /* Clear the DMA interrupt condition. */ 845 846 sw $v1, CLR($v0) 847 848 /* Test the block transfer completion interrupt flag. */ 849 850 la $v0, DCH0INT 851 lw $v1, 0($v0) 852 andi $v1, $v1, (1 << 3) /* CHBCIF */ 853 beqz $v1, irq_dma_next 854 nop 855 856 /* Clear the block transfer completion interrupt flag. */ 857 858 sw $v1, CLR($v0) 859 860 irq_dma_next: 861 /* Test the block transfer completion interrupt flag. */ 862 863 la $v0, DCH1INT 864 lw $v1, 0($v0) 865 andi $v1, $v1, (1 << 3) /* CHBCIF */ 866 beqz $v1, irq_exit 867 nop 868 869 /* Clear the block transfer completion interrupt flag. */ 870 871 sw $v1, CLR($v0) 872 873 /* 874 The DMA interrupt should only be active within the visible region. 875 The event handler is invoked here instead of in response to a timer 876 interrupt within that region. 877 */ 878 879 /* Increment the line counter (only within the visible region). */ 880 881 addiu $s0, $s0, 1 882 883 /* Jump to the event handler (only within the visible region). */ 884 885 jalr $s1 886 nop 887 888 /* Jump to the DMA update routine. */ 889 890 j visible_update_address 891 nop 892 893 irq_exit: 894 /* Save state. */ 895 896 li $k0, IRQ_STACK_LIMIT 897 sw $s0, -44($k0) 898 sw $s1, -48($k0) 899 sw $s2, -52($k0) 900 sw $s3, -56($k0) 901 902 /* Restore affected registers. */ 903 904 lw $v0, -4($k0) 905 lw $v1, -8($k0) 906 lw $s0, -12($k0) 907 lw $s1, -16($k0) 908 lw $s2, -20($k0) 909 lw $s3, -24($k0) 910 lw $t8, -28($k0) 911 lw $ra, -32($k0) 912 lw $sp, -36($k0) 913 914 eret 915 nop 916 917 918 919 exc_handler: 920 li $t9, 0x80000000 921 mfc0 $t6, CP0_ERROREPC 922 nop 923 exc_loop: 924 and $t7, $t9, $t6 925 beqz $t7, exc_errorepc_zero 926 nop 927 exc_errorepc_one: 928 la $v0, PORTA 929 li $v1, (1 << 2) /* PORTA<2> = RA2 */ 930 sw $v1, SET($v0) 931 j exc_loop_wait 932 nop 933 exc_errorepc_zero: 934 la $v0, PORTA 935 li $v1, (1 << 3) /* PORTA<3> = RA3 */ 936 sw $v1, SET($v0) 937 exc_loop_wait: 938 li $t8, 5000000 939 exc_loop_delay: 940 addiu $t8, $t8, -1 941 bnez $t8, exc_loop_delay 942 nop 943 la $v0, PORTA 944 li $v1, (3 << 2) /* PORTA<3:2> = RA3, RA2 */ 945 sw $v1, CLR($v0) 946 exc_loop_wait_again: 947 li $t8, 2500000 948 exc_loop_delay_again: 949 addiu $t8, $t8, -1 950 bnez $t8, exc_loop_delay_again 951 nop 952 exc_errorepc_next: 953 srl $t9, $t9, 1 954 bnez $t9, exc_loop 955 nop 956 j exc_handler 957 nop 958 959 960 961 /* Event routines. */ 962 963 /* The vertical back porch. */ 964 965 vbp_active: 966 /* Test for visible region. */ 967 968 sltiu $v0, $s0, VISIBLE_START 969 bnez $v0, _vbp_active_ret 970 nop 971 972 /* Start the visible region. */ 973 974 la $s1, visible_active 975 976 /* Reset the line address. */ 977 978 move $s2, $s3 979 980 /* Update the source address. */ 981 982 la $v0, DCH0SSA 983 sw $s2, 0($v0) 984 985 /* Enable the line channel for timer event transfer initiation. */ 986 987 la $v0, DCH0ECON 988 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 989 sw $v1, SET($v0) 990 991 /* Disable the timer interrupt during the visible period. */ 992 993 la $v0, IEC0 994 li $v1, (1 << 9) 995 sw $v1, CLR($v0) /* T2IE = 0 */ 996 997 _vbp_active_ret: 998 jr $ra 999 nop 1000 1001 1002 1003 /* The visible region. */ 1004 1005 visible_active: 1006 /* Test for front porch. */ 1007 1008 sltiu $v0, $s0, VFP_START 1009 bnez $v0, _visible_active_ret 1010 nop 1011 1012 /* Start the front porch region. */ 1013 1014 la $s1, vfp_active 1015 1016 /* Re-enable the timer interrupt after the visible period. */ 1017 1018 la $v0, IEC0 1019 li $v1, (1 << 9) 1020 sw $v1, SET($v0) /* T2IE = 1 */ 1021 1022 _visible_active_ret: 1023 jr $ra 1024 nop 1025 1026 1027 1028 /* DMA update routine. */ 1029 1030 visible_update_address: 1031 1032 /* Test for the last visible line. */ 1033 1034 la $v0, vfp_active 1035 bne $s1, $v0, _visible_update_address 1036 nop 1037 1038 /* Disable the line channel. */ 1039 1040 la $v0, DCH0ECON 1041 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 1042 sw $v1, CLR($v0) 1043 1044 j _visible_update_ret 1045 nop 1046 1047 _visible_update_address: 1048 1049 /* 1050 Update the line data address if the line counter (referring to the 1051 next line) is even. 1052 */ 1053 1054 andi $t8, $s0, 1 1055 bnez $t8, _visible_update_ret 1056 nop 1057 1058 /* Reference the next line and update the DMA source address. */ 1059 1060 addiu $s2, $s2, LINE_LENGTH 1061 1062 /* Test for wraparound. */ 1063 1064 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1065 sltu $t8, $s2, $t8 1066 bnez $t8, _visible_dma_update 1067 nop 1068 1069 /* Reset the source address. */ 1070 1071 li $s2, SCREEN_BASE 1072 1073 _visible_dma_update: 1074 1075 /* Update the source address. */ 1076 1077 la $v0, DCH0SSA 1078 sw $s2, 0($v0) 1079 1080 _visible_update_ret: 1081 j irq_exit 1082 nop 1083 1084 1085 1086 /* Within the vertical front porch. */ 1087 1088 vfp_active: 1089 /* Test for vsync. */ 1090 1091 sltiu $v0, $s0, VSYNC_START 1092 bnez $v0, _vfp_active_ret 1093 nop 1094 1095 /* Start the vsync. */ 1096 1097 la $s1, vsync_active 1098 1099 /* Bring vsync low when the next line starts. */ 1100 1101 la $v0, OC2CON 1102 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1103 sw $v1, 0($v0) 1104 1105 _vfp_active_ret: 1106 jr $ra 1107 nop 1108 1109 1110 1111 /* The vsync period. */ 1112 1113 vsync_active: 1114 /* Test for front porch. */ 1115 1116 sltiu $v0, $s0, VSYNC_END 1117 bnez $v0, _vsync_active_ret 1118 nop 1119 1120 /* Start the back porch. */ 1121 1122 move $s0, $zero 1123 la $s1, vbp_active 1124 1125 /* Bring vsync high when the next line starts. */ 1126 1127 la $v0, OC2CON 1128 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1129 sw $v1, 0($v0) 1130 1131 _vsync_active_ret: 1132 jr $ra 1133 nop