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_exit 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_exit: 241 eret 242 nop 243 244 245 246 /* Utilities. */ 247 248 handle_error_level: 249 mfc0 $t3, CP0_STATUS 250 li $t4, ~(STATUS_ERL | STATUS_EXL) 251 and $t3, $t3, $t4 252 mtc0 $t3, CP0_STATUS 253 jr $ra 254 nop 255 256 enable_interrupts: 257 mfc0 $t3, CP0_STATUS 258 nop 259 ori $t3, $t3, STATUS_IRQ | STATUS_IE 260 mtc0 $t3, CP0_STATUS 261 jr $ra 262 nop 263 264 init_interrupts: 265 mfc0 $t3, CP0_STATUS 266 li $t4, ~STATUS_BEV 267 and $t3, $t3, $t4 268 mtc0 $t3, CP0_STATUS /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 269 270 la $t3, _start 271 mtc0 $t3, CP0_EBASE /* EBASE = _start */ 272 273 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 274 mtc0 $t3, CP0_CAUSE 275 276 jr $ra 277 nop 278 279 280 281 /* Event routines. */ 282 283 /* Start of, and within, the vertical front porch. */ 284 285 vfp_start: 286 /* Clear vsync. */ 287 288 la $v0, PORTB 289 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 290 sw $v1, CLR($v0) 291 292 /* Enter the active region. */ 293 294 la $s1, vfp_active 295 296 vfp_active: 297 /* Test for vsync. */ 298 299 subu $v0, $s0, VSYNC_START 300 bnez $v0, _vfp_active_ret 301 nop 302 303 /* Start the vsync region. */ 304 305 la $s1, vsync_start 306 307 _vfp_active_ret: 308 jr $ra 309 nop 310 311 312 313 /* Start of, and within, vsync. */ 314 315 vsync_start: 316 /* Set vsync. */ 317 318 la $v0, PORTB 319 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 320 sw $v1, SET($v0) 321 322 /* Enter the active region. */ 323 324 la $s1, vsync_active 325 326 vsync_active: 327 /* Test for visible region. */ 328 329 subu $v0, $s0, VISIBLE_START 330 bnez $v0, _vsync_active_ret 331 nop 332 333 /* Start the visible region. */ 334 335 la $s1, visible_start 336 337 /* Enable OC2. */ 338 339 la $v0, OC2CON 340 li $v1, (1 << 15) 341 sw $v1, SET($v0) 342 343 _vsync_active_ret: 344 jr $ra 345 nop 346 347 348 349 /* Start of, and within, the visible period. */ 350 351 visible_start: 352 /* Clear vsync. */ 353 354 la $v0, PORTB 355 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 356 sw $v1, CLR($v0) 357 358 /* Enter the active region. */ 359 360 la $s1, visible_active 361 362 visible_active: 363 /* Test for back porch. */ 364 365 subu $v0, $s0, VBP_START 366 bnez $v0, _visible_active_ret 367 nop 368 369 /* Start the back porch. */ 370 371 la $s1, vbp_active 372 373 /* Disable OC2. */ 374 375 la $v0, OC2CON 376 li $v1, (1 << 15) 377 sw $v1, CLR($v0) 378 379 _visible_active_ret: 380 jr $ra 381 nop 382 383 384 385 /* Within the vertical back porch. */ 386 387 vbp_active: 388 /* Test for front porch. */ 389 390 subu $v0, $s0, VBP_END 391 bnez $v0, _vbp_active_ret 392 nop 393 394 /* Start the front porch. */ 395 396 li $s0, 0 397 la $s1, vfp_start 398 399 _vbp_active_ret: 400 jr $ra 401 nop 402 403 404 405 /* Initialisation routines. */ 406 407 init_timer2: 408 409 /* Initialise Timer2 interrupt. */ 410 411 la $v0, T2CON 412 sw $zero, 0($v0) /* T2CON = 0 */ 413 nop 414 415 la $v0, TMR2 416 sw $zero, 0($v0) /* TMR2 = 0 */ 417 418 la $v0, PR2 419 li $v1, HFREQ_LIMIT 420 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 421 422 /* Initialise Timer2 interrupt. */ 423 424 la $v0, IFS0 425 li $v1, (1 << 9) 426 sw $v1, CLR($v0) /* T2IF = 0 */ 427 428 la $v0, IPC2 429 li $v1, 0b11111 430 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 431 432 la $v0, IPC2 433 li $v1, 0b11111 434 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 435 436 la $v0, IEC0 437 li $v1, (1 << 9) 438 sw $v1, SET($v0) /* T2IE = 1 */ 439 440 /* Start timer. */ 441 442 la $v0, T2CON 443 li $v1, (1 << 15) 444 sw $v1, SET($v0) /* ON = 1 */ 445 446 jr $ra 447 nop 448 449 450 451 /* 452 Output compare initialisation. 453 454 Timer2 will be used to trigger two events: one initiating the hsync pulse, and 455 one terminating the pulse. Upon the termination of the pulse, an interrupt 456 condition will cause the line data to be transferred using DMA. 457 458 */ 459 460 init_oc2: 461 /* Disable OC interrupts. */ 462 463 la $v0, IEC0 464 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 465 sw $v1, CLR($v0) 466 467 la $v0, IFS0 468 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 469 sw $v1, CLR($v0) 470 471 /* Initialise OC2. */ 472 473 la $v0, OC2CON 474 li $v1, 0b101 /* OC2CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 475 sw $v1, 0($v0) 476 477 /* Set hsync start and end. */ 478 479 la $v0, OC2R 480 li $v1, 16 481 sw $v1, 0($v0) 482 483 la $v0, OC2RS 484 li $v1, 32 485 sw $v1, 0($v0) 486 487 /* OC2 is enabled elsewhere when it needs to operate. */ 488 489 jr $ra 490 nop 491 492 493 494 init_oc_pins: 495 /* Unlock the configuration register bits. */ 496 497 la $v0, SYSKEY 498 sw $zero, 0($v0) 499 li $v1, 0xAA996655 500 sw $v1, 0($v0) 501 li $v1, 0x556699AA 502 sw $v1, 0($v0) 503 504 la $v0, CFGCON 505 lw $t8, 0($v0) 506 li $v1, (1 << 13) /* IOLOCK = 0 */ 507 sw $v1, CLR($v0) 508 509 /* Map OC2 to RPB5. */ 510 511 la $v0, RPB5R 512 li $v1, 0b0101 /* RPB5R<3:0> = 0101 (OC2) */ 513 sw $v1, 0($v0) 514 515 la $v0, CFGCON 516 sw $t8, 0($v0) 517 518 /* Lock the oscillator control register again. */ 519 520 la $v0, SYSKEY 521 li $v1, 0x33333333 522 sw $v1, 0($v0) 523 524 jr $ra 525 nop 526 527 528 529 /* Parallel Master Port initialisation. */ 530 531 init_pmp: 532 /* Disable PMP interrupts. */ 533 534 la $v0, IEC1 535 li $v1, (1 << 16) /* IEC1<16> = PMPIE = 0 */ 536 sw $v1, CLR($v0) 537 538 /* Initialise PMP. 539 540 PMCON<12:11> = ADDRMUX<1:0> = 0; demultiplexed address and data 541 PMCON<9> = PTWREN<0> = 0; no write pin 542 PMCON<8> = PTRDEN<0> = 0; no read pin 543 PMCON<7:6> = CSF<1:0> = 0; no chip select pins 544 */ 545 546 la $v0, PMCON 547 sw $zero, 0($v0) 548 549 /* 550 PMMODE<14:13> = IRQM<1:0> = 1; interrupt after every read/write 551 PMMODE<12:11> = INCM<1:0> = 0; no increment on every read/write 552 PMMODE<10> = MODE16<0> = 0; 8-bit transfers 553 PMMODE<9:8> = MODE<1:0> = 10; master mode 2 554 PMMODE<5:2> = WAITM<3:0> = 00; single cycle read/write, no chip select wait cycles 555 */ 556 557 la $v0, PMMODE 558 li $v1, 0x2200 559 sw $v1, 0($v0) 560 561 /* Free non-essential pins for general I/O. */ 562 563 la $v0, PMAEN 564 sw $zero, 0($v0) 565 566 la $v0, PMADDR 567 sw $zero, 0($v0) 568 569 la $v0, IFS1 570 li $v1, (3 << 16) /* IFS1<17:16> = PMPEIF, PMPIF = 0 */ 571 sw $v1, CLR($v0) 572 573 /* Start PMP mode. */ 574 575 la $v0, PMCON 576 li $v1, (1 << 15) /* PMCON<15> = ON = 1 */ 577 sw $v1, SET($v0) 578 579 jr $ra 580 nop 581 582 583 584 /* 585 Direct Memory Access initialisation. 586 587 Write 160 pixels to the PMP for the line data. This is initiated by an output 588 compare interrupt. 589 */ 590 591 init_dma: 592 /* Disable DMA interrupts. */ 593 594 la $v0, IEC1 595 li $v1, (7 << 28) /* IEC1<30:28> = DMA2IE, DMA1IE, DMA0IE = 0 */ 596 sw $v1, CLR($v0) 597 598 /* Clear DMA interrupt flags. */ 599 600 la $v0, IFS1 601 li $v1, (7 << 28) /* IFS1<30:28> = DMA2IF, DMA1IF, DMA0IF = 0 */ 602 sw $v1, CLR($v0) 603 604 /* Enable DMA. */ 605 606 la $v0, DMACON 607 li $v1, (1 << 15) 608 sw $v1, SET($v0) 609 610 /* 611 Initialise a line channel. 612 The line channel will be channel 0 (x = 0). 613 614 Once the hsync channel has completed a transfer, the line channel 615 transfer is initiated. 616 617 Specify a priority of 3: 618 DCHxCON<1:0> = CHPRI<1:0> = 3 619 620 Auto-enable the channels: 621 DCHxCON<4> = CHAEN = 1 622 */ 623 624 la $v0, DCH0CON 625 li $v1, 0b10011 626 sw $v1, 0($v0) 627 628 /* 629 Initiate channel transfers when the initiating interrupt condition 630 occurs: 631 DCHxECON<15:8> = CHSIRQ<7:0> = output compare 2 interrupt 632 */ 633 634 la $v0, DCH0ECON 635 li $v1, (12 << 8) | (1 << 4) 636 sw $v1, 0($v0) 637 638 /* 639 The line channel has a cell size of 160 bytes: 640 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LIMIT 641 */ 642 643 la $v0, DCH0CSIZ 644 li $v1, LINE_LIMIT 645 sw $v1, 0($v0) 646 647 /* 648 Each source has a size identical to the cell size: 649 DCHxSSIZ<15:0> = CHSSIZ<15:0> = n 650 */ 651 652 la $v0, DCH0SSIZ 653 li $v1, LINE_LIMIT 654 sw $v1, 0($v0) 655 656 /* 657 The source address is the physical address of the line data: 658 DCHxSSA = line data physical address 659 */ 660 661 la $v0, DCH0SSA 662 sw $zero, 0($v0) 663 664 /* 665 Each destination has a size of 1 byte: 666 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 667 */ 668 669 la $v0, DCH0DSIZ 670 li $v1, 1 671 sw $v1, 0($v0) 672 673 /* 674 For the line channel, the destination address is the physical address of 675 PMDIN: DCHxDSA = physical(PMDIN) 676 */ 677 678 la $v0, DCH0DSA 679 li $v1, PMDIN 680 li $t8, KSEG1_BASE 681 subu $v1, $v1, $t8 682 sw $v1, 0($v0) 683 684 la $v0, DCH0INT 685 sw $zero, 0($v0) 686 687 /* Enable channels. */ 688 689 la $v0, DCH0CON 690 li $v1, 0b10000000 691 sw $v1, SET($v0) 692 693 jr $ra 694 nop 695 696 697 698 /* Framebuffer initialisation. */ 699 700 init_framebuffer: 701 li $v0, KSEG0_BASE 702 li $t8, 40 * 1024 703 li $v1, 0xff 704 705 _init_fb_loop: 706 sw $v1, 0($v0) 707 addiu $v0, $v0, 4 708 addiu $t8, $t8, -4 709 bnez $t8, _init_fb_loop 710 nop 711 712 jr $ra 713 nop