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