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