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 23 #define HFREQ_LIMIT 627 /* 20MHz cycles */ 24 #define HSYNC_LIMIT 16 /* bytes or pixels */ 25 #define LINE_LIMIT 160 /* bytes or pixels */ 26 27 #define VSYNC_START 1 /* horizontal lines, front porch end */ 28 #define VISIBLE_START 4 /* horizontal lines */ 29 #define VBP_START 516 /* horizontal lines, back porch start */ 30 #define VBP_END 531 /* horizontal lines, back porch end */ 31 32 /* Disable JTAG functionality on pins. */ 33 34 .section .devcfg0, "a" 35 .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ 36 37 /* 38 Set the oscillator to be the FRC oscillator with PLL, with peripheral clock 39 divided by 1, HS oscillator mode selected (for PLL), and FRCDIV+PLL selected. 40 With a system clock of 20MHz, the peripheral clock is therefore 20MHz. 41 42 The watchdog timer (FWDTEN) is also disabled. 43 */ 44 45 .section .devcfg1, "a" 46 .word 0xff7fcff9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<2:0> = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ 47 48 /* 49 Set the FRC oscillator PLL function with an input division of 4, an output 50 division of 2, a multiplication of 20, yielding a multiplication of 2.5. 51 This should take the FRC at 8MHz and produce a system clock of 20MHz. 52 */ 53 54 .section .devcfg2, "a" 55 .word 0xfff9ffdb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 1; DEVCFG2<6:4> = FPLLMUL<2:0> = 101; DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ 56 57 .text 58 .globl _start 59 60 _start: 61 /* 62 Configure RAM. 63 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 64 */ 65 66 la $v0, BMXCON 67 li $v1, (1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 68 sw $v1, CLR($v0) 69 70 /* Enable caching. */ 71 72 li $v0, CONFIG_CM_CACHABLE_NONCOHERENT 73 mtc0 $v0, CP0_CONFIG 74 nop 75 76 /* Get the RAM size. */ 77 78 la $v0, BMXDRMSZ 79 lw $v0, 0($v0) 80 81 /* Initialise the stack pointer. */ 82 83 li $v1, KSEG0_BASE 84 addu $sp, $v0, $v1 /* sp = KSEG0_BASE + RAM size */ 85 86 /* Initialise the globals pointer. */ 87 88 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 89 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 90 91 /* Set pins for output. */ 92 93 jal init_pins 94 nop 95 96 jal init_oc_pins 97 nop 98 99 /* Initialise the status register. */ 100 101 jal init_interrupts 102 nop 103 104 /* Initialise timer. */ 105 106 jal init_timer2 107 nop 108 109 /* Initialise PMP. */ 110 111 jal init_pmp 112 nop 113 114 /* Initialise DMA. */ 115 116 jal init_dma 117 nop 118 119 /* Initialise OC2. */ 120 121 jal init_oc2 122 nop 123 124 /* Initialise framebuffer. */ 125 126 jal init_framebuffer 127 nop 128 129 /* Enable interrupts and loop. */ 130 131 jal enable_interrupts 132 nop 133 134 jal handle_error_level 135 nop 136 137 /* Main program. */ 138 139 li $a1, 20000000 /* counter = 20000000 */ 140 141 /* Initialise the display state. */ 142 143 li $s0, 0 /* line counter */ 144 la $s1, vfp_active /* current event */ 145 move $s2, $zero /* line address */ 146 147 /* Monitoring loop. */ 148 loop: 149 addiu $a1, $a1, -1 /* counter -= 1 */ 150 bnez $a1, loop /* until counter == 0 */ 151 nop 152 153 li $a1, 20000000 /* counter = 20000000 */ 154 155 la $t0, PORTB 156 li $t1, (1 << 10) /* PORTB<10> = RB10 */ 157 sw $t1, INV($t0) 158 159 _next: 160 j loop 161 nop 162 163 164 165 init_pins: 166 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 167 168 la $v0, CFGCON 169 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 170 sw $v1, CLR($v0) 171 172 init_outputs: 173 /* Remove analogue features from pins. */ 174 175 la $v0, ANSELA 176 sw $zero, 0($v0) /* ANSELA = 0 */ 177 la $v0, ANSELB 178 sw $zero, 0($v0) /* ANSELB = 0 */ 179 180 la $v0, TRISA 181 sw $zero, 0($v0) 182 la $v0, TRISB 183 sw $zero, 0($v0) 184 185 jr $ra 186 nop 187 188 189 190 /* Interrupt servicing. */ 191 192 .org 0x200 193 194 interrupt_handler: 195 196 /* Check for a timer interrupt condition. */ 197 198 la $v0, IFS0 199 lw $v1, 0($v0) 200 andi $v1, $v1, (1 << 9) /* T2IF */ 201 beqz $v1, irq_pmp 202 nop 203 204 /* Increment the line counter. */ 205 206 addiu $s0, $s0, 1 207 208 /* Jump to the event handler. */ 209 210 jalr $s1 211 nop 212 213 irq_clear_timer: 214 215 /* Clear the timer interrupt condition. */ 216 217 la $v0, IFS0 218 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 219 sw $v1, CLR($v0) 220 221 irq_pmp: 222 223 /* Check for a PMP interrupt condition. */ 224 225 la $v0, IFS1 226 lw $v1, 0($v0) 227 li $t8, (1 << 16) /* PMPIF */ 228 and $v1, $v1, $t8 229 beqz $v1, irq_dma 230 nop 231 232 irq_clear_pmp: 233 234 /* Clear the PMP interrupt condition. */ 235 236 la $v0, IFS1 237 li $v1, (1 << 16) /* IFS1<16> = PMPIF = 0 */ 238 sw $v1, CLR($v0) 239 240 irq_dma: 241 242 /* Check for a DMA interrupt condition. */ 243 244 la $v0, IFS1 245 lw $v1, 0($v0) 246 li $t8, (1 << 28) /* DMA0IF */ 247 and $v1, $v1, $t8 248 beqz $v1, irq_exit 249 nop 250 251 /* Test the block transfer completion interrupt flag. */ 252 253 la $v0, DCH0INT 254 lw $v1, 0($v0) 255 andi $v1, $v1, (1 << 3) /* CHBCIF */ 256 beqz $v1, irq_clear_dma 257 nop 258 259 /* Clear the block transfer completion interrupt flag. */ 260 261 li $v1, (1 << 3) /* CHBCIF = 0 */ 262 sw $v1, CLR($v0) 263 264 /* 265 Update the line data address if the line counter (referring to the 266 next line) is even. 267 */ 268 269 andi $t8, $s0, 1 270 bnez $t8, irq_clear_dma 271 nop 272 273 /* Reference the next line and update the DMA source address. */ 274 275 addiu $s2, $s2, LINE_LIMIT 276 la $v0, DCH0SSA 277 sw $s2, 0($v0) 278 279 irq_clear_dma: 280 281 /* Clear the DMA interrupt condition. */ 282 283 la $v0, IFS1 284 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 285 sw $v1, CLR($v0) 286 287 irq_exit: 288 eret 289 nop 290 291 292 293 /* Utilities. */ 294 295 handle_error_level: 296 mfc0 $t3, CP0_STATUS 297 li $t4, ~(STATUS_ERL | STATUS_EXL) 298 and $t3, $t3, $t4 299 mtc0 $t3, CP0_STATUS 300 jr $ra 301 nop 302 303 enable_interrupts: 304 mfc0 $t3, CP0_STATUS 305 nop 306 ori $t3, $t3, STATUS_IRQ | STATUS_IE 307 mtc0 $t3, CP0_STATUS 308 jr $ra 309 nop 310 311 init_interrupts: 312 mfc0 $t3, CP0_STATUS 313 li $t4, ~STATUS_BEV 314 and $t3, $t3, $t4 315 mtc0 $t3, CP0_STATUS /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 316 317 la $t3, _start 318 mtc0 $t3, CP0_EBASE /* EBASE = _start */ 319 320 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 321 mtc0 $t3, CP0_CAUSE 322 323 jr $ra 324 nop 325 326 327 328 /* Event routines. */ 329 330 /* Start of, and within, the vertical front porch. */ 331 332 vfp_start: 333 /* Clear vsync. */ 334 335 la $v0, PORTB 336 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 337 sw $v1, CLR($v0) 338 339 /* Enter the active region. */ 340 341 la $s1, vfp_active 342 343 vfp_active: 344 /* Test for vsync. */ 345 346 subu $v0, $s0, VSYNC_START 347 bnez $v0, _vfp_active_ret 348 nop 349 350 /* Start the vsync region. */ 351 352 la $s1, vsync_start 353 354 _vfp_active_ret: 355 jr $ra 356 nop 357 358 359 360 /* Start of, and within, vsync. */ 361 362 vsync_start: 363 /* Set vsync. */ 364 365 la $v0, PORTB 366 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 367 sw $v1, SET($v0) 368 369 /* Enter the active region. */ 370 371 la $s1, vsync_active 372 373 vsync_active: 374 /* Test for visible region. */ 375 376 subu $v0, $s0, VISIBLE_START 377 bnez $v0, _vsync_active_ret 378 nop 379 380 /* Start the visible region. */ 381 382 la $s1, visible_start 383 384 /* Enable OC2 for the next line. */ 385 386 la $v0, OC2CON 387 li $v1, (1 << 15) 388 sw $v1, SET($v0) 389 390 /* Set the start address for line data. */ 391 392 move $s2, $zero 393 la $v0, DCH0SSA 394 sw $s2, 0($v0) 395 396 _vsync_active_ret: 397 jr $ra 398 nop 399 400 401 402 /* Start of, and within, the visible period. */ 403 404 visible_start: 405 /* Clear vsync. */ 406 407 la $v0, PORTB 408 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 409 sw $v1, CLR($v0) 410 411 /* Enter the active region. */ 412 413 la $s1, visible_active 414 415 visible_active: 416 /* Test for back porch. */ 417 418 subu $v0, $s0, VBP_START 419 bnez $v0, _visible_active_ret 420 nop 421 422 /* Start the back porch. */ 423 424 la $s1, vbp_active 425 426 /* Disable OC2 for the next line. */ 427 428 la $v0, OC2CON 429 li $v1, (1 << 15) 430 sw $v1, CLR($v0) 431 432 _visible_active_ret: 433 jr $ra 434 nop 435 436 437 438 /* Within the vertical back porch. */ 439 440 vbp_active: 441 /* Test for front porch. */ 442 443 subu $v0, $s0, VBP_END 444 bnez $v0, _vbp_active_ret 445 nop 446 447 /* Start the front porch. */ 448 449 li $s0, 0 450 la $s1, vfp_start 451 452 _vbp_active_ret: 453 jr $ra 454 nop 455 456 457 458 /* Initialisation routines. */ 459 460 init_timer2: 461 462 /* Initialise Timer2 interrupt. */ 463 464 la $v0, T2CON 465 sw $zero, 0($v0) /* T2CON = 0 */ 466 nop 467 468 la $v0, TMR2 469 sw $zero, 0($v0) /* TMR2 = 0 */ 470 471 la $v0, PR2 472 li $v1, HFREQ_LIMIT 473 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 474 475 /* Initialise Timer2 interrupt. */ 476 477 la $v0, IFS0 478 li $v1, (1 << 9) 479 sw $v1, CLR($v0) /* T2IF = 0 */ 480 481 la $v0, IPC2 482 li $v1, 0b11111 483 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 484 485 la $v0, IPC2 486 li $v1, 0b11111 487 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 488 489 la $v0, IEC0 490 li $v1, (1 << 9) 491 sw $v1, SET($v0) /* T2IE = 1 */ 492 493 /* Start timer. */ 494 495 la $v0, T2CON 496 li $v1, (1 << 15) 497 sw $v1, SET($v0) /* ON = 1 */ 498 499 jr $ra 500 nop 501 502 503 504 /* 505 Output compare initialisation. 506 507 Timer2 will be used to trigger two events: one initiating the hsync pulse, and 508 one terminating the pulse. Upon the termination of the pulse, an interrupt 509 condition will cause the line data to be transferred using DMA. 510 511 */ 512 513 init_oc2: 514 /* Disable OC interrupts. */ 515 516 la $v0, IEC0 517 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 518 sw $v1, CLR($v0) 519 520 la $v0, IFS0 521 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 522 sw $v1, CLR($v0) 523 524 /* Initialise OC2. */ 525 526 la $v0, OC2CON 527 li $v1, 0b101 /* OC2CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 528 sw $v1, 0($v0) 529 530 /* Set hsync start and end. */ 531 532 la $v0, OC2R 533 li $v1, 16 534 sw $v1, 0($v0) 535 536 la $v0, OC2RS 537 li $v1, 32 538 sw $v1, 0($v0) 539 540 /* OC2 is enabled elsewhere when it needs to operate. */ 541 542 jr $ra 543 nop 544 545 546 547 init_oc_pins: 548 /* Unlock the configuration register bits. */ 549 550 la $v0, SYSKEY 551 sw $zero, 0($v0) 552 li $v1, 0xAA996655 553 sw $v1, 0($v0) 554 li $v1, 0x556699AA 555 sw $v1, 0($v0) 556 557 la $v0, CFGCON 558 lw $t8, 0($v0) 559 li $v1, (1 << 13) /* IOLOCK = 0 */ 560 sw $v1, CLR($v0) 561 562 /* Map OC2 to RPB5. */ 563 564 la $v0, RPB5R 565 li $v1, 0b0101 /* RPB5R<3:0> = 0101 (OC2) */ 566 sw $v1, 0($v0) 567 568 la $v0, CFGCON 569 sw $t8, 0($v0) 570 571 /* Lock the oscillator control register again. */ 572 573 la $v0, SYSKEY 574 li $v1, 0x33333333 575 sw $v1, 0($v0) 576 577 jr $ra 578 nop 579 580 581 582 /* Parallel Master Port initialisation. */ 583 584 init_pmp: 585 /* Disable PMP interrupts. */ 586 587 la $v0, IEC1 588 li $v1, (1 << 16) /* IEC1<16> = PMPIE = 0 */ 589 sw $v1, CLR($v0) 590 591 /* Initialise PMP. 592 593 PMCON<12:11> = ADDRMUX<1:0> = 0; demultiplexed address and data 594 PMCON<9> = PTWREN<0> = 0; no write pin 595 PMCON<8> = PTRDEN<0> = 0; no read pin 596 PMCON<7:6> = CSF<1:0> = 0; no chip select pins 597 */ 598 599 la $v0, PMCON 600 sw $zero, 0($v0) 601 602 /* 603 PMMODE<14:13> = IRQM<1:0> = 1; interrupt after every read/write 604 PMMODE<12:11> = INCM<1:0> = 0; no increment on every read/write 605 PMMODE<10> = MODE16<0> = 0; 8-bit transfers 606 PMMODE<9:8> = MODE<1:0> = 10; master mode 2 607 PMMODE<5:2> = WAITM<3:0> = 00; single cycle read/write, no chip select wait cycles 608 */ 609 610 la $v0, PMMODE 611 li $v1, 0x2200 612 sw $v1, 0($v0) 613 614 /* Free non-essential pins for general I/O. */ 615 616 la $v0, PMAEN 617 sw $zero, 0($v0) 618 619 la $v0, PMADDR 620 sw $zero, 0($v0) 621 622 la $v0, IFS1 623 li $v1, (3 << 16) /* IFS1<17:16> = PMPEIF, PMPIF = 0 */ 624 sw $v1, CLR($v0) 625 626 /* Start PMP mode. */ 627 628 la $v0, PMCON 629 li $v1, (1 << 15) /* PMCON<15> = ON = 1 */ 630 sw $v1, SET($v0) 631 632 jr $ra 633 nop 634 635 636 637 /* 638 Direct Memory Access initialisation. 639 640 Write 160 pixels to the PMP for the line data. This is initiated by an output 641 compare interrupt. Upon completion of the transfer, a DMA interrupt initiates 642 the address update routine, changing the source address of the DMA channel. 643 */ 644 645 init_dma: 646 /* Disable DMA interrupts. */ 647 648 la $v0, IEC1 649 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 0 */ 650 sw $v1, CLR($v0) 651 652 /* Clear DMA interrupt flags. */ 653 654 la $v0, IFS1 655 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 656 sw $v1, CLR($v0) 657 658 /* Enable DMA. */ 659 660 la $v0, DMACON 661 li $v1, (1 << 15) 662 sw $v1, SET($v0) 663 664 /* 665 Initialise a line channel. 666 The line channel will be channel 0 (x = 0). 667 668 Once the hsync channel has completed a transfer, the line channel 669 transfer is initiated. 670 671 Specify a priority of 3: 672 DCHxCON<1:0> = CHPRI<1:0> = 3 673 674 Auto-enable the channels: 675 DCHxCON<4> = CHAEN = 1 676 */ 677 678 la $v0, DCH0CON 679 li $v1, 0b10011 680 sw $v1, 0($v0) 681 682 /* 683 Initiate channel transfers when the initiating interrupt condition 684 occurs: 685 DCHxECON<15:8> = CHSIRQ<7:0> = output compare 2 interrupt 686 */ 687 688 la $v0, DCH0ECON 689 li $v1, (12 << 8) | (1 << 4) 690 sw $v1, 0($v0) 691 692 /* 693 The line channel has a cell size of 160 bytes: 694 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LIMIT 695 */ 696 697 la $v0, DCH0CSIZ 698 li $v1, LINE_LIMIT 699 sw $v1, 0($v0) 700 701 /* 702 Each source has a size identical to the cell size: 703 DCHxSSIZ<15:0> = CHSSIZ<15:0> = n 704 */ 705 706 la $v0, DCH0SSIZ 707 li $v1, LINE_LIMIT 708 sw $v1, 0($v0) 709 710 /* 711 The source address is the physical address of the line data: 712 DCHxSSA = line data physical address 713 */ 714 715 la $v0, DCH0SSA 716 sw $zero, 0($v0) 717 718 /* 719 Each destination has a size of 1 byte: 720 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 721 */ 722 723 la $v0, DCH0DSIZ 724 li $v1, 1 725 sw $v1, 0($v0) 726 727 /* 728 For the line channel, the destination address is the physical address of 729 PMDIN: DCHxDSA = physical(PMDIN) 730 */ 731 732 la $v0, DCH0DSA 733 li $v1, PMDIN 734 li $t8, KSEG1_BASE 735 subu $v1, $v1, $t8 736 sw $v1, 0($v0) 737 738 /* 739 Use the block transfer completion interrupt to indicate when the source 740 address can be updated. 741 */ 742 743 la $v0, DCH0INT 744 li $v1, (1 << 19) /* CHBCIE = 1 */ 745 sw $v1, 0($v0) 746 747 /* Enable interrupt for address updating. */ 748 749 la $v0, IPC10 750 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 751 sw $v1, CLR($v0) 752 753 la $v0, IPC10 754 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 755 sw $v1, SET($v0) 756 757 la $v0, IEC1 758 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 759 /* sw $v1, SET($v0) */ 760 761 /* Enable channels. */ 762 763 la $v0, DCH0CON 764 li $v1, 0b10000000 765 sw $v1, SET($v0) 766 767 jr $ra 768 nop 769 770 771 772 /* Framebuffer initialisation. */ 773 774 init_framebuffer: 775 li $v0, KSEG0_BASE 776 li $t8, 40 * 1024 777 li $v1, 0xff 778 779 _init_fb_loop: 780 sw $v1, 0($v0) 781 addiu $v0, $v0, 4 782 addiu $t8, $t8, -4 783 bnez $t8, _init_fb_loop 784 nop 785 786 jr $ra 787 nop