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