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 /* Initialisation routines. */ 310 311 init_timers: 312 313 /* Initialise Timer2 for sync pulses. */ 314 315 la $v0, T2CON 316 sw $zero, 0($v0) /* T2CON = 0 */ 317 nop 318 319 la $v0, TMR2 320 sw $zero, 0($v0) /* TMR2 = 0 */ 321 322 la $v0, PR2 323 li $v1, HFREQ_LIMIT 324 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 325 326 /* Initialise Timer2 interrupt. */ 327 328 la $v0, IFS0 329 li $v1, (1 << 9) 330 sw $v1, CLR($v0) /* T2IF = 0 */ 331 332 la $v0, IPC2 333 li $v1, 0b11111 334 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 335 li $v1, 0b11111 336 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 337 338 la $v0, IEC0 339 li $v1, (1 << 9) 340 sw $v1, SET($v0) /* T2IE = 1 */ 341 342 /* Start timer. */ 343 344 la $v0, T2CON 345 li $v1, (1 << 15) 346 sw $v1, SET($v0) /* ON = 1 */ 347 348 /* Initialise Timer3 for line DMA cell transfer. */ 349 350 la $v0, T3CON 351 sw $zero, 0($v0) /* T3CON = 0 */ 352 nop 353 354 la $v0, TMR3 355 sw $zero, 0($v0) /* TMR3 = 0 */ 356 357 la $v0, PR3 358 li $v1, 1 359 sw $v1, 0($v0) /* PR3 = 1 */ 360 361 /* Initialise Timer3 interrupt. */ 362 363 la $v0, IFS0 364 li $v1, (1 << 14) 365 sw $v1, CLR($v0) /* T3IF = 0 */ 366 367 la $v0, IPC3 368 li $v1, 0b11111 369 sw $v1, CLR($v0) /* T3IP, T3IS = 0 */ 370 li $v1, 0b00111 371 sw $v1, SET($v0) /* T3IP = 1; T3IS = 3 */ 372 373 la $v0, IEC0 374 li $v1, (1 << 14) 375 sw $v1, SET($v0) /* T3IE = 1 */ 376 377 /* Start timer. */ 378 379 la $v0, T3CON 380 li $v1, (1 << 15) 381 sw $v1, SET($v0) /* ON = 1 */ 382 383 jr $ra 384 nop 385 386 387 388 /* 389 Output compare initialisation. 390 391 Timer2 will be used to trigger two events using OC1: one initiating the hsync 392 pulse, and one terminating the pulse. The pulse should appear after the line 393 data has been transferred using DMA, but this is achieved by just choosing 394 suitable start and end values. 395 396 Using OC2, Timer2 triggers a level shifting event and OC2 is reconfigured to 397 reverse the level at a later point. In this way, the vsync pulse is generated 398 and is synchronised to the display lines. 399 */ 400 401 init_oc: 402 /* Disable OC1 interrupts. */ 403 404 la $v0, IEC0 405 li $v1, (1 << 7) /* IEC0<7> = OC1IE = 0 */ 406 sw $v1, CLR($v0) 407 408 la $v0, IFS0 409 li $v1, (1 << 7) /* IFS0<7> = OC1IF = 0 */ 410 sw $v1, CLR($v0) 411 412 /* Initialise OC1. */ 413 414 la $v0, OC1CON 415 li $v1, 0b101 /* OC1CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 416 sw $v1, 0($v0) 417 418 /* Pulse start and end. */ 419 420 la $v0, OC1R 421 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 422 sw $v1, 0($v0) 423 424 la $v0, OC1RS 425 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 426 sw $v1, 0($v0) 427 428 /* OC1 is enabled. */ 429 430 la $v0, OC1CON 431 li $v1, (1 << 15) 432 sw $v1, SET($v0) 433 434 /* Disable OC2 interrupts. */ 435 436 la $v0, IEC0 437 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 438 sw $v1, CLR($v0) 439 440 la $v0, IFS0 441 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 442 sw $v1, CLR($v0) 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. Upon completion of the transfer, a DMA interrupt initiates the 512 address update routine, changing the source address of the DMA channel. 513 */ 514 515 init_dma: 516 /* Disable DMA interrupts. */ 517 518 la $v0, IEC1 519 li $v1, (0b111 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 520 sw $v1, CLR($v0) 521 522 /* Clear DMA interrupt flags. */ 523 524 la $v0, IFS1 525 li $v1, (0b111 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 526 sw $v1, CLR($v0) 527 528 /* Enable DMA. */ 529 530 la $v0, DMACON 531 li $v1, (1 << 15) 532 sw $v1, SET($v0) 533 534 /* 535 Initialise a line channel. 536 The line channel will be channel 0 (x = 0). 537 538 Specify a priority of 3: 539 DCHxCON<1:0> = CHPRI<1:0> = 3 540 541 Auto-enable the channel: 542 DCHxCON<4> = CHAEN = 1 543 */ 544 545 la $v0, DCH0CON 546 li $v1, 0b10011 547 sw $v1, 0($v0) 548 549 /* 550 Initialise a level reset channel. 551 The reset channel will be channel 1 (x = 1). 552 553 Specify a priority of 3: 554 DCHxCON<1:0> = CHPRI<1:0> = 3 555 556 Chain the channel to channel 0: 557 DCHxCON<5> = CHCHN = 1 558 559 Allow the channel to receive events when disabled: 560 DCHxCON<6> = CHAED = 1 561 */ 562 563 la $v0, DCH1CON 564 li $v1, 0b1100011 565 sw $v1, 0($v0) 566 567 la $v0, DCH2CON 568 li $v1, 0b1100011 569 sw $v1, 0($v0) 570 571 /* 572 Initiate channel transfers when the initiating interrupt condition 573 occurs: 574 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 575 DCHxECON<4> = SIRQEN = 1 576 577 For now, however, prevent initiation by not setting SIRQEN. 578 */ 579 580 la $v0, DCH0ECON 581 li $v1, (9 << 8) 582 sw $v1, 0($v0) 583 584 /* 585 Initiate reset channel transfer when channel 0 is finished; 586 timer 3 causes cell transfers: 587 DCHxECON<15:8> = CHSIRQ<7:0> = timer 3 interrupt 588 DCHxECON<4> = SIRQEN = 1 589 */ 590 591 la $v0, DCH1ECON 592 li $v1, (14 << 8) | (1 << 4) 593 sw $v1, 0($v0) 594 595 la $v0, DCH2ECON 596 li $v1, (14 << 8) | (1 << 4) 597 sw $v1, 0($v0) 598 599 /* 600 The line channel has a cell size of the number bytes in a line: 601 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 602 */ 603 604 la $v0, DCH0CSIZ 605 li $v1, LINE_LENGTH 606 sw $v1, 0($v0) 607 608 /* 609 The reset channel has a cell size of a single zero byte: 610 DCHxCSIZ<15:0> = CHCSIZ<15:0> = 1 611 */ 612 613 la $v0, DCH1CSIZ 614 li $v1, 1 615 sw $v1, 0($v0) 616 617 la $v0, DCH2CSIZ 618 sw $v1, 0($v0) 619 620 /* 621 The source has a size identical to the cell size: 622 DCHxSSIZ<15:0> = CHSSIZ<15:0> = LINE_LENGTH or 1 623 */ 624 625 la $v0, DCH0SSIZ 626 li $v1, LINE_LENGTH 627 sw $v1, 0($v0) 628 629 la $v0, DCH1SSIZ 630 li $v1, 1 631 sw $v1, 0($v0) 632 633 la $v0, DCH2SSIZ 634 sw $v1, 0($v0) 635 636 /* 637 The source address is the physical address of the line data: 638 DCHxSSA = physical(line data address) 639 */ 640 641 la $v0, DCH0SSA 642 li $v1, SCREEN_BASE 643 sw $v1, 0($v0) 644 645 /* 646 For the reset channel, a single byte of zero is transferred: 647 DCHxSSA = physical(zero data address) 648 */ 649 650 la $v0, DCH1SSA 651 la $v1, fulldata 652 li $t8, KSEG0_BASE 653 subu $v1, $v1, $t8 654 sw $v1, 0($v0) 655 656 la $v0, DCH2SSA 657 la $v1, zerodata 658 li $t8, KSEG0_BASE 659 subu $v1, $v1, $t8 660 sw $v1, 0($v0) 661 662 /* 663 The destination has a size of 1 byte: 664 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 665 */ 666 667 li $v1, 1 668 669 la $v0, DCH0DSIZ 670 sw $v1, 0($v0) 671 672 la $v0, DCH1DSIZ 673 sw $v1, 0($v0) 674 675 la $v0, DCH2DSIZ 676 sw $v1, 0($v0) 677 678 /* 679 The destination address is the physical address of PORTB: 680 DCHxDSA = physical(PORTB) 681 */ 682 683 li $v1, PORTB 684 li $t8, KSEG1_BASE 685 subu $v1, $v1, $t8 686 687 la $v0, DCH0DSA 688 sw $v1, 0($v0) 689 690 la $v0, DCH1DSA 691 sw $v1, 0($v0) 692 693 la $v0, DCH2DSA 694 sw $v1, 0($v0) 695 696 /* 697 Use the block transfer completion interrupt to indicate when the source 698 address can be updated. 699 */ 700 701 la $v0, DCH0INT 702 li $v1, (1 << 19) /* CHBCIE = 1 */ 703 sw $v1, 0($v0) 704 705 /* Enable interrupt for address updating. */ 706 707 la $v0, IPC10 708 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 709 sw $v1, CLR($v0) 710 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 711 sw $v1, SET($v0) 712 713 la $v0, IEC1 714 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 715 sw $v1, SET($v0) 716 717 /* Enable line channel. */ 718 719 la $v0, DCH0CON 720 li $v1, 0b10000000 721 sw $v1, SET($v0) 722 723 jr $ra 724 nop 725 726 zerodata: 727 .word 0 728 729 fulldata: 730 .word 255 731 732 733 734 /* UART initialisation. */ 735 736 init_uart: 737 /* Initialise UART. */ 738 739 la $v0, U1BRG 740 li $v1, 12 /* U1BRG<15:0> = BRG = (FPB / (16 * baudrate)) - 1 = (24000000 / (16 * 115200)) - 1 = 12 */ 741 sw $v1, 0($v0) 742 743 la $v0, U1MODE 744 li $v1, (1 << 15) /* U1MODE<15> = ON = 0 */ 745 sw $v1, CLR($v0) 746 747 /* Start UART. */ 748 749 la $v0, U1STA 750 li $v1, (1 << 10) /* U1STA<10> = UTXEN = 1 */ 751 sw $v1, SET($v0) 752 753 la $v0, U1MODE 754 li $v1, (1 << 15) /* U1MODE<15> = ON = 1 */ 755 sw $v1, SET($v0) 756 757 jr $ra 758 nop 759 760 761 762 /* Utilities. */ 763 764 handle_error_level: 765 mfc0 $t3, CP0_STATUS 766 li $t4, ~(STATUS_ERL | STATUS_EXL) 767 and $t3, $t3, $t4 768 mtc0 $t3, CP0_STATUS 769 jr $ra 770 nop 771 772 enable_interrupts: 773 mfc0 $t3, CP0_STATUS 774 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 775 and $t3, $t3, $t4 776 ori $t3, $t3, (3 << STATUS_IRQ_SHIFT) 777 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 778 and $t3, $t3, $t4 779 ori $t3, $t3, STATUS_IE 780 mtc0 $t3, CP0_STATUS 781 jr $ra 782 nop 783 784 init_interrupts: 785 mfc0 $t3, CP0_DEBUG 786 li $t4, ~DEBUG_DM 787 and $t3, $t3, $t4 788 mtc0 $t3, CP0_DEBUG 789 790 mfc0 $t3, CP0_STATUS 791 li $t4, STATUS_BEV /* BEV = 1 or EBASE cannot be set */ 792 or $t3, $t3, $t4 793 mtc0 $t3, CP0_STATUS 794 795 la $t3, exception_handler 796 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 797 798 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 799 mtc0 $t3, CP0_INTCTL 800 801 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 802 mtc0 $t3, CP0_CAUSE 803 804 jr $ra 805 nop 806 807 808 809 /* Exception servicing. */ 810 811 .section .flash, "a" 812 813 /* TLB error servicing. */ 814 815 tlb_handler: 816 j exception_handler 817 nop 818 819 820 821 /* General exception servicing. */ 822 823 .org 0x180 824 825 exception_handler: 826 j exc_handler 827 nop 828 829 830 831 /* Interrupt servicing. */ 832 833 .org 0x200 834 835 interrupt_handler: 836 837 /* 838 Save affected registers, restoring IRQ state and switching to the IRQ 839 stack. 840 */ 841 842 li $k0, IRQ_STACK_LIMIT 843 save_affected 844 load_state 845 li $sp, IRQ_STACK_TOP 846 847 /* 848 The timer interrupt will only occur outside the visible region, but the 849 interrupt condition will still occur as the timer wraps around. 850 851 Here, we deliberately ignore the timer condition during the visible/ 852 active region. 853 854 The DMA interrupt should only be active within the visible region. 855 */ 856 857 la $t8, visible_active 858 beq $s1, $t8, irq_dma 859 nop 860 861 /* Check for a timer interrupt condition. */ 862 863 la $v0, IFS0 864 lw $v1, 0($v0) 865 andi $v1, $v1, (1 << 9) /* T2IF */ 866 beqz $v1, irq_exit 867 nop 868 869 j irq_handle 870 nop 871 872 irq_dma: 873 /* Check for a DMA interrupt condition. */ 874 875 la $v0, IFS1 876 lw $v1, 0($v0) 877 li $t8, (1 << 28) /* DMA0IF */ 878 and $v1, $v1, $t8 879 beqz $v1, irq_exit 880 nop 881 882 /* Clear the DMA interrupt condition. */ 883 884 sw $v1, CLR($v0) 885 886 /* Test the block transfer completion interrupt flag. */ 887 888 la $v0, DCH0INT 889 lw $v1, 0($v0) 890 andi $v1, $v1, (1 << 3) /* CHBCIF */ 891 beqz $v1, irq_exit 892 nop 893 894 irq_handle: 895 /* Clear the interrupt condition. */ 896 897 sw $v1, CLR($v0) 898 899 /* Increment the line counter. */ 900 901 addiu $s0, $s0, 1 902 903 /* Jump to the event handler. */ 904 905 jalr $s1 906 nop 907 908 irq_exit: 909 /* 910 Save IRQ state and restore the affected registers, switching back to the 911 original stack. 912 */ 913 914 li $k0, IRQ_STACK_LIMIT 915 save_state 916 load_affected 917 918 eret 919 nop 920 921 922 923 /* Event routines. */ 924 925 /* The vertical back porch. */ 926 927 vbp_active: 928 /* Test for visible region. */ 929 930 sltiu $v0, $s0, VISIBLE_START 931 bnez $v0, _vbp_active_ret 932 nop 933 934 /* Start the visible region. */ 935 936 la $s1, visible_active 937 938 /* Reset the line address. */ 939 940 move $s2, $s3 941 942 /* Update the source address. */ 943 944 la $v0, DCH0SSA 945 sw $s2, 0($v0) 946 947 /* 948 Suspend delivery of the timer interrupt during the visible period. 949 The condition still occurs, however. 950 */ 951 952 la $v0, IPC2 953 lw $v1, 0($v0) 954 li $t8, ~0b11111 955 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 956 ori $v1, $v1, 0b00111 /* T2IP = 1; T2IS = 3 */ 957 sw $v1, 0($v0) 958 959 /* Enable the line channel for timer event transfer initiation. */ 960 961 la $v0, DCH0ECON 962 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 1 */ 963 sw $v1, SET($v0) 964 965 _vbp_active_ret: 966 jr $ra 967 nop 968 969 970 971 /* The visible region. */ 972 973 visible_active: 974 /* Test for front porch. */ 975 976 sltiu $v0, $s0, VFP_START 977 bnez $v0, visible_update_address 978 nop 979 980 /* Start the front porch region. */ 981 982 la $s1, vfp_active 983 984 /* Restore delivery of the timer interrupt after the visible period. */ 985 986 la $v0, IPC2 987 lw $v1, 0($v0) 988 li $t8, ~0b11111 989 and $v1, $v1, $t8 /* T2IP = 0; T2IS = 0 */ 990 ori $v1, $v1, 0b11111 /* T2IP = 7; T2IS = 3 */ 991 sw $v1, 0($v0) 992 993 /* Disable the line channel. */ 994 995 la $v0, DCH0ECON 996 li $v1, (1 << 4) /* DCH0ECON<4> = SIRQEN = 0 */ 997 sw $v1, CLR($v0) 998 999 _visible_active_ret: 1000 jr $ra 1001 nop 1002 1003 1004 1005 /* DMA update routine. */ 1006 1007 visible_update_address: 1008 1009 /* 1010 Update the line data address if the line counter (referring to the 1011 next line) is even. 1012 */ 1013 1014 andi $t8, $s0, 1 1015 bnez $t8, _visible_update_ret 1016 nop 1017 1018 /* Reference the next line and update the DMA source address. */ 1019 1020 addiu $s2, $s2, LINE_LENGTH 1021 1022 /* Test for wraparound. */ 1023 1024 li $t8, (SCREEN_BASE + SCREEN_SIZE) 1025 sltu $t8, $s2, $t8 1026 bnez $t8, _visible_dma_update 1027 nop 1028 1029 /* Reset the source address. */ 1030 1031 li $s2, SCREEN_BASE 1032 1033 _visible_dma_update: 1034 1035 /* Update the source address. */ 1036 1037 la $v0, DCH0SSA 1038 sw $s2, 0($v0) 1039 1040 _visible_update_ret: 1041 jr $ra 1042 nop 1043 1044 1045 1046 /* Within the vertical front porch. */ 1047 1048 vfp_active: 1049 /* Test for vsync. */ 1050 1051 sltiu $v0, $s0, VSYNC_START 1052 bnez $v0, _vfp_active_ret 1053 nop 1054 1055 /* Start the vsync. */ 1056 1057 la $s1, vsync_active 1058 1059 /* Bring vsync low when the next line starts. */ 1060 1061 la $v0, OC2CON 1062 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 1063 sw $v1, 0($v0) 1064 1065 _vfp_active_ret: 1066 jr $ra 1067 nop 1068 1069 1070 1071 /* The vsync period. */ 1072 1073 vsync_active: 1074 /* Test for front porch. */ 1075 1076 sltiu $v0, $s0, VSYNC_END 1077 bnez $v0, _vsync_active_ret 1078 nop 1079 1080 /* Start the back porch. */ 1081 1082 move $s0, $zero 1083 la $s1, vbp_active 1084 1085 /* Bring vsync high when the next line starts. */ 1086 1087 la $v0, OC2CON 1088 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 1089 sw $v1, 0($v0) 1090 1091 _vsync_active_ret: 1092 jr $ra 1093 nop 1094 1095 1096 1097 /* Exception handler. */ 1098 1099 exc_handler: 1100 mfc0 $t7, CP0_ERROREPC 1101 nop 1102 1103 exc_write_word: 1104 li $t8, 32 1105 la $v0, U1TXREG 1106 exc_loop: 1107 addiu $t8, $t8, -4 1108 srlv $v1, $t7, $t8 /* $v1 = $t7 >> $t8 */ 1109 andi $v1, $v1, 0xF 1110 addiu $t9, $v1, -10 /* $t9 >= 10? */ 1111 bgez $t9, exc_alpha 1112 nop 1113 exc_digit: 1114 addiu $v1, $v1, 48 /* convert to digit: '0' */ 1115 j exc_write 1116 nop 1117 exc_alpha: 1118 addiu $v1, $v1, 55 /* convert to alpha: 'A' - 10 */ 1119 exc_write: 1120 sw $v1, 0($v0) 1121 bnez $t8, exc_loop 1122 nop 1123 exc_loop_end: 1124 li $v1, '\n' 1125 sw $v1, 0($v0) 1126 1127 exc_handler_end: 1128 j exc_handler_end 1129 nop