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 and set outputs low. */ 92 93 jal init_pins 94 nop 95 96 /* Initialise the status register. */ 97 98 jal init_interrupts 99 nop 100 101 /* Initialise timer. */ 102 103 jal init_timer2 104 nop 105 106 /* Initialise PMP. */ 107 108 jal init_pmp 109 nop 110 111 /* Initialise DMA. */ 112 113 jal init_dma 114 nop 115 116 /* Initialise framebuffer. */ 117 118 jal init_framebuffer 119 nop 120 121 /* Enable interrupts and loop. */ 122 123 jal enable_interrupts 124 nop 125 126 jal handle_error_level 127 nop 128 129 /* Main program. */ 130 131 li $a1, 20000000 /* counter = 20000000 */ 132 li $a2, 3187 /* test timer scaling */ 133 134 /* Initialise the display state. */ 135 136 li $s0, 0 /* line counter */ 137 la $s1, vfp_active /* current event */ 138 139 /* Monitoring loop. */ 140 loop: 141 addiu $a1, $a1, -1 /* counter -= 1 */ 142 bnez $a1, loop /* until counter == 0 */ 143 nop 144 145 li $a1, 20000000 /* counter = 20000000 */ 146 147 la $t0, PORTB 148 la $t1, (1 << 10) /* PORTB<10> = RB10 */ 149 sw $t1, INV($t0) 150 151 _next: 152 j loop 153 nop 154 155 156 157 init_pins: 158 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 159 160 la $v0, CFGCON 161 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 162 sw $v1, CLR($v0) 163 164 init_outputs: 165 /* Remove analogue features from pins. */ 166 167 la $v0, ANSELA 168 sw $zero, 0($v0) /* ANSELA = 0 */ 169 la $v0, ANSELB 170 sw $zero, 0($v0) /* ANSELB = 0 */ 171 172 la $v0, TRISA 173 sw $zero, 0($v0) 174 la $v0, TRISB 175 sw $zero, 0($v0) 176 177 jr $ra 178 nop 179 180 181 182 /* Interrupt servicing. */ 183 184 .org 0x200 185 186 interrupt_handler: 187 188 /* Check for a timer interrupt condition. */ 189 190 la $v0, IFS0 191 lw $v1, 0($v0) 192 andi $v1, $v1, (1 << 9) /* T2IF */ 193 beqz $v1, irq_next 194 nop 195 196 /* Timer scaling for testing purposes. */ 197 198 addiu $a2, $a2, -1 199 bnez $a2, irq_clear_timer 200 nop 201 li $a2, 3187 202 203 /* Increment the line counter. */ 204 205 addiu $s0, $s0, 1 206 207 /* Jump to the event handler. */ 208 209 jalr $s1 210 nop 211 212 irq_clear_timer: 213 214 /* Clear the timer interrupt condition. */ 215 216 la $v0, IFS0 217 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 218 sw $v1, CLR($v0) 219 220 irq_next: 221 222 /* Check for a PMP interrupt condition. */ 223 224 la $v0, IFS1 225 lw $v1, 0($v0) 226 li $t8, (1 << 16) /* PMPIF */ 227 and $v1, $v1, $t8 228 beqz $v1, irq_exit 229 nop 230 231 irq_clear_pmp: 232 233 /* Clear the PMP interrupt condition. */ 234 235 la $v0, IFS1 236 li $v1, (1 << 16) /* IFS1<16> = PMPIF = 0 */ 237 sw $v1, CLR($v0) 238 239 irq_exit: 240 eret 241 nop 242 243 244 245 /* Utilities. */ 246 247 handle_error_level: 248 mfc0 $t3, CP0_STATUS 249 li $t4, ~(STATUS_ERL | STATUS_EXL) 250 and $t3, $t3, $t4 251 mtc0 $t3, CP0_STATUS 252 jr $ra 253 nop 254 255 enable_interrupts: 256 mfc0 $t3, CP0_STATUS 257 nop 258 ori $t3, $t3, STATUS_IRQ | STATUS_IE 259 mtc0 $t3, CP0_STATUS 260 jr $ra 261 nop 262 263 init_interrupts: 264 mfc0 $t3, CP0_STATUS 265 li $t4, ~STATUS_BEV 266 and $t3, $t3, $t4 267 mtc0 $t3, CP0_STATUS /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 268 269 la $t3, _start 270 mtc0 $t3, CP0_EBASE /* EBASE = _start */ 271 272 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 273 mtc0 $t3, CP0_CAUSE 274 275 jr $ra 276 nop 277 278 279 280 /* Event routines. */ 281 282 /* Start of, and within, the vertical front porch. */ 283 284 vfp_start: 285 /* Clear vsync. */ 286 287 la $v0, PORTB 288 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 289 sw $v1, CLR($v0) 290 291 /* Enter the active region. */ 292 293 la $s1, vfp_active 294 295 vfp_active: 296 /* Test for vsync. */ 297 298 subu $v0, $s0, VSYNC_START 299 bnez $v0, _vfp_active_ret 300 nop 301 302 /* Start the vsync region. */ 303 304 la $s1, vsync_start 305 306 _vfp_active_ret: 307 jr $ra 308 nop 309 310 311 312 /* Start of, and within, vsync. */ 313 314 vsync_start: 315 /* Set vsync. */ 316 317 la $v0, PORTB 318 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 319 sw $v1, SET($v0) 320 321 /* Enter the active region. */ 322 323 la $s1, vsync_active 324 325 vsync_active: 326 /* Test for visible region. */ 327 328 subu $v0, $s0, VISIBLE_START 329 bnez $v0, _vsync_active_ret 330 nop 331 332 /* Start the visible region. */ 333 334 la $s1, visible_start 335 336 _vsync_active_ret: 337 jr $ra 338 nop 339 340 341 342 /* Start of, and within, the visible period. */ 343 344 visible_start: 345 /* Clear vsync. */ 346 347 la $v0, PORTB 348 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 349 sw $v1, CLR($v0) 350 351 /* Enter the active region. */ 352 353 la $s1, visible_active 354 355 visible_active: 356 /* Initiate hsync transfer and subsequent line transfer. */ 357 358 la $v0, DCH0ECON 359 li $v1, (1 << 7) 360 sw $v1, SET($v0) 361 362 /* Test for back porch. */ 363 364 subu $v0, $s0, VBP_START 365 bnez $v0, _visible_active_ret 366 nop 367 368 /* Start the back porch. */ 369 370 la $s1, vbp_active 371 372 _visible_active_ret: 373 jr $ra 374 nop 375 376 377 378 /* Within the vertical back porch. */ 379 380 vbp_active: 381 /* Test for front porch. */ 382 383 subu $v0, $s0, VBP_END 384 bnez $v0, _vbp_active_ret 385 nop 386 387 /* Start the front porch. */ 388 389 li $s0, 0 390 la $s1, vfp_start 391 392 _vbp_active_ret: 393 jr $ra 394 nop 395 396 397 398 /* Initialisation routines. */ 399 400 init_timer2: 401 402 /* Initialise Timer2 interrupt. */ 403 404 la $v0, T2CON 405 sw $zero, 0($v0) /* T2CON = 0 */ 406 nop 407 408 la $v0, TMR2 409 sw $zero, 0($v0) /* TMR2 = 0 */ 410 la $v0, PR2 411 li $v1, HFREQ_LIMIT 412 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 413 414 /* Initialise Timer2 interrupt. */ 415 416 la $v0, IFS0 417 li $v1, (1 << 9) 418 sw $v1, CLR($v0) /* IFS0CLR: T2IF = 0 */ 419 la $v0, IPC2 420 li $v1, (7 << 2) 421 sw $v1, SET($v0) /* IPC1SET: T2IP = 7 */ 422 la $v0, IPC2 423 li $v1, 3 424 sw $v1, SET($v0) /* IPC1SET: T2IS = 3 */ 425 la $v0, IEC0 426 li $v1, (1 << 9) 427 sw $v1, SET($v0) /* IEC0SET: T2IE = 1 */ 428 429 /* Start timer. */ 430 431 la $v0, T2CON 432 li $v1, (1 << 15) /* ON = 1 */ 433 sw $v1, SET($v0) /* T1CONSET: ON = 1 */ 434 435 jr $ra 436 nop 437 438 439 440 /* Parallel Master Port initialisation. */ 441 442 init_pmp: 443 /* Disable PMP interrupts. */ 444 445 la $v0, IEC1 446 li $v1, (1 << 16) /* IEC1<16> = PMPIE = 0 */ 447 sw $v1, CLR($v0) 448 449 /* Initialise PMP. 450 451 PMCON<12:11> = ADDRMUX<1:0> = 0; demultiplexed address and data 452 PMCON<9> = PTWREN<0> = 0; no write pin 453 PMCON<8> = PTRDEN<0> = 0; no read pin 454 PMCON<7:6> = CSF<1:0> = 0; no chip select pins 455 */ 456 457 la $v0, PMCON 458 sw $zero, 0($v0) 459 460 /* 461 PMMODE<14:13> = IRQM<1:0> = 1; interrupt after every read/write 462 PMMODE<12:11> = INCM<1:0> = 0; no increment on every read/write 463 PMMODE<10> = MODE16<0> = 0; 8-bit transfers 464 PMMODE<9:8> = MODE<1:0> = 10; master mode 2 465 PMMODE<5:2> = WAITM<3:0> = 00; single cycle read/write, no chip select wait cycles 466 */ 467 468 la $v0, PMMODE 469 li $v1, 0x2200 470 sw $v1, 0($v0) 471 472 /* Free non-essential pins for general I/O. */ 473 474 la $v0, PMAEN 475 sw $zero, 0($v0) 476 477 la $v0, PMADDR 478 sw $zero, 0($v0) 479 480 la $v0, IFS1 481 li $v1, (3 << 16) /* IFS1<17:16> = PMPEIF, PMPIF = 0 */ 482 sw $v1, CLR($v0) 483 484 /* Start PMP mode. */ 485 486 la $v0, PMCON 487 li $v1, (1 << 15) /* PMCON<15> = ON = 1 */ 488 sw $v1, SET($v0) 489 490 jr $ra 491 nop 492 493 494 495 /* Direct Memory Access initialisation. */ 496 497 /* 498 Write 16 pixels to the PMP for a hsync pulse. This channel is invoked 499 explicitly in the interrupt handler for Timer1 since it will not happen on every 500 occurrence of the interrupt, but only on those occurrences within the active 501 region of the display output. 502 503 Write 160 pixels to the PMP for the line data. This is initiated by the 504 completion of the hsync transfer. 505 */ 506 507 init_dma: 508 /* Disable DMA interrupts. */ 509 510 la $v0, IEC1 511 li $v1, (7 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 512 sw $v1, CLR($v0) 513 514 /* Clear DMA interrupt flags. */ 515 516 la $v0, IFS1 517 li $v1, (7 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 518 sw $v1, CLR($v0) 519 520 /* Enable DMA. */ 521 522 la $v0, DMACON 523 li $v1, (1 << 15) 524 sw $v1, SET($v0) 525 526 /* 527 Initialise a hsync channel and a line channel. 528 The hsync channel will be channel 0 (x = 0). 529 The line channel will be channel 1 (x = 1). 530 531 Once the hsync channel has completed a transfer, the line channel 532 transfer is initiated. 533 534 Specify a priority of 3: 535 DCHxCON<1:0> = CHPRI<1:0> = 3 536 537 Auto-enable the channels: 538 DCHxCON<4> = CHAEN = 1 539 */ 540 541 la $v0, DCH0CON 542 li $v1, 0b10011 543 sw $v1, 0($v0) 544 545 la $v0, DCH1CON 546 li $v1, 0b10011 547 sw $v1, 0($v0) 548 549 la $v0, DCH2CON 550 li $v1, 0b10011 551 sw $v1, 0($v0) 552 553 /* Initiate channel transfers when the preceding channel interrupt occurs. */ 554 555 la $v0, DCH0ECON 556 sw $zero, 0($v0) 557 558 la $v0, DCH1ECON 559 li $v1, (60 << 8) | (1 << 4) 560 sw $v1, 0($v0) 561 562 la $v0, DCH2ECON 563 li $v1, (61 << 8) | (1 << 4) 564 sw $v1, 0($v0) 565 566 /* 567 The hsync channels have a cell size of 16 bytes: 568 DCHxCSIZ<15:0> = CHCSIZ<15:0> = HSYNC_LIMIT 569 570 The line channel has a cell size of 160 bytes: 571 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LIMIT 572 */ 573 574 la $v0, DCH0CSIZ 575 li $v1, HSYNC_LIMIT 576 sw $v1, 0($v0) 577 578 la $v0, DCH1CSIZ 579 li $v1, HSYNC_LIMIT 580 sw $v1, 0($v0) 581 582 la $v0, DCH2CSIZ 583 li $v1, LINE_LIMIT 584 sw $v1, 0($v0) 585 586 /* 587 Each source has a size identical to the cell size: 588 DCHxSSIZ<15:0> = CHSSIZ<15:0> = n 589 */ 590 591 la $v0, DCH0SSIZ 592 li $v1, HSYNC_LIMIT 593 sw $v1, 0($v0) 594 595 la $v0, DCH1SSIZ 596 li $v1, HSYNC_LIMIT 597 sw $v1, 0($v0) 598 599 la $v0, DCH2SSIZ 600 li $v1, LINE_LIMIT 601 sw $v1, 0($v0) 602 603 /* 604 The source address is the physical address of either the hsync pulse 605 data or the line data: 606 DCHxSSA = pulse data physical address 607 DCHxSSA = line data physical address 608 */ 609 610 la $v0, DCH0SSA 611 la $v1, set_ra2 612 li $t8, KSEG0_BASE 613 subu $v1, $v1, $t8 614 sw $v1, 0($v0) 615 616 la $v0, DCH1SSA 617 sw $v1, 0($v0) 618 619 la $v0, DCH2SSA 620 sw $zero, 0($v0) 621 622 /* 623 Each destination has a size of 1 byte: 624 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 625 */ 626 627 la $v0, DCH0DSIZ 628 li $v1, 1 629 sw $v1, 0($v0) 630 631 la $v0, DCH1DSIZ 632 sw $v1, 0($v0) 633 634 la $v0, DCH2DSIZ 635 sw $v1, 0($v0) 636 637 /* 638 For the hsync channels, the destination address is the physical address 639 for an I/O register that sets the output signal for hsync: 640 DCHxDSA = physical(PORTA) 641 */ 642 643 la $v0, DCH0DSA 644 li $v1, PORTA + INV 645 li $t8, KSEG1_BASE 646 subu $v1, $v1, $t8 647 sw $v1, 0($v0) 648 649 la $v0, DCH1DSA 650 li $v1, PORTA + CLR 651 li $t8, KSEG1_BASE 652 subu $v1, $v1, $t8 653 sw $v1, 0($v0) 654 655 /* 656 For the line channel, the destination address is the physical address of 657 PMDIN: DCHxDSA = physical(PMDIN) 658 */ 659 660 la $v0, DCH2DSA 661 li $v1, PMDIN 662 li $t8, KSEG1_BASE 663 subu $v1, $v1, $t8 664 sw $v1, 0($v0) 665 666 /* 667 The block transfer complete interrupt needs to be enabled, so that upon 668 completion of the transfer, the next transfer can be initiated: 669 DCHxINT<19> = CHBCIE = 1 670 */ 671 672 la $v0, DCH0INT 673 li $v1, (1 << 19) 674 sw $v1, 0($v0) 675 676 la $v0, DCH1INT 677 li $v1, (1 << 19) 678 sw $v1, 0($v0) 679 680 la $v0, DCH2INT 681 sw $zero, 0($v0) 682 683 /* Enable channels. */ 684 685 la $v0, DCH0CON 686 li $v1, 0b10000000 687 sw $v1, SET($v0) 688 689 la $v0, DCH1CON 690 sw $v1, SET($v0) 691 692 la $v0, DCH2CON 693 sw $v1, SET($v0) 694 695 jr $ra 696 nop 697 698 699 700 /* Framebuffer initialisation. */ 701 702 init_framebuffer: 703 li $v0, KSEG0_BASE 704 li $t8, 40 * 1024 705 li $v1, 0xff 706 707 _init_fb_loop: 708 sw $v1, 0($v0) 709 addiu $v0, $v0, 4 710 addiu $t8, $t8, -4 711 bnez $t8, _init_fb_loop 712 nop 713 714 jr $ra 715 nop 716 717 718 719 /* 16 bytes of pulse data used to set PORTA<2> = RA2. */ 720 721 set_ra2: 722 .byte (1 << 2) 723 .byte (1 << 2) 724 .byte (1 << 2) 725 .byte (1 << 2) 726 .byte (1 << 2) 727 .byte (1 << 2) 728 .byte (1 << 2) 729 .byte (1 << 2) 730 .byte (1 << 2) 731 .byte (1 << 2) 732 .byte (1 << 2) 733 .byte (1 << 2) 734 .byte (1 << 2) 735 .byte (1 << 2) 736 .byte (1 << 2) 737 .byte (1 << 2)