paul@0 | 1 | /* |
paul@0 | 2 | * Generate a VGA signal using a PIC32 microcontroller. |
paul@0 | 3 | * |
paul@0 | 4 | * Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk> |
paul@0 | 5 | * |
paul@0 | 6 | * This program is free software: you can redistribute it and/or modify |
paul@0 | 7 | * it under the terms of the GNU General Public License as published by |
paul@0 | 8 | * the Free Software Foundation, either version 3 of the License, or |
paul@0 | 9 | * (at your option) any later version. |
paul@0 | 10 | * |
paul@0 | 11 | * This program is distributed in the hope that it will be useful, |
paul@0 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@0 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@0 | 14 | * GNU General Public License for more details. |
paul@0 | 15 | * |
paul@0 | 16 | * You should have received a copy of the GNU General Public License |
paul@0 | 17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
paul@0 | 18 | */ |
paul@0 | 19 | |
paul@0 | 20 | #include "mips.h" |
paul@0 | 21 | #include "pic32.h" |
paul@0 | 22 | |
paul@0 | 23 | #define HFREQ_LIMIT 627 /* 20MHz cycles */ |
paul@1 | 24 | #define VSYNC_START 1 /* horizontal lines, front porch end */ |
paul@1 | 25 | #define VISIBLE_START 4 /* horizontal lines */ |
paul@1 | 26 | #define VBP_START 516 /* horizontal lines, back porch start */ |
paul@1 | 27 | #define VBP_END 531 /* horizontal lines, back porch end */ |
paul@0 | 28 | |
paul@0 | 29 | /* Disable JTAG functionality on pins. */ |
paul@0 | 30 | |
paul@0 | 31 | .section .devcfg0, "a" |
paul@0 | 32 | .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ |
paul@0 | 33 | |
paul@0 | 34 | /* |
paul@0 | 35 | Set the oscillator to be the FRC oscillator with PLL, with peripheral clock |
paul@0 | 36 | divided by 1, HS oscillator mode selected (for PLL), and FRCDIV+PLL selected. |
paul@0 | 37 | With a system clock of 20MHz, the peripheral clock is therefore 20MHz. |
paul@0 | 38 | |
paul@0 | 39 | The watchdog timer (FWDTEN) is also disabled. |
paul@0 | 40 | */ |
paul@0 | 41 | |
paul@0 | 42 | .section .devcfg1, "a" |
paul@0 | 43 | .word 0xff7fcff9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<2:0> = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ |
paul@0 | 44 | |
paul@0 | 45 | /* |
paul@0 | 46 | Set the FRC oscillator PLL function with an input division of 4, an output |
paul@0 | 47 | division of 2, a multiplication of 20, yielding a multiplication of 2.5. |
paul@0 | 48 | This should take the FRC at 8MHz and produce a system clock of 20MHz. |
paul@0 | 49 | */ |
paul@0 | 50 | |
paul@0 | 51 | .section .devcfg2, "a" |
paul@0 | 52 | .word 0xfff9ffdb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 1; DEVCFG2<6:4> = FPLLMUL<2:0> = 101; DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ |
paul@0 | 53 | |
paul@0 | 54 | .text |
paul@0 | 55 | .globl _start |
paul@0 | 56 | |
paul@0 | 57 | _start: |
paul@0 | 58 | /* |
paul@0 | 59 | Configure RAM. |
paul@0 | 60 | See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization |
paul@0 | 61 | */ |
paul@0 | 62 | |
paul@0 | 63 | la $v0, BMXCON |
paul@0 | 64 | li $v1, (1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ |
paul@0 | 65 | sw $v1, CLR($v0) |
paul@0 | 66 | |
paul@0 | 67 | /* Enable caching. */ |
paul@0 | 68 | |
paul@0 | 69 | li $v0, CONFIG_CM_CACHABLE_NONCOHERENT |
paul@0 | 70 | mtc0 $v0, CP0_CONFIG |
paul@0 | 71 | nop |
paul@0 | 72 | |
paul@0 | 73 | /* Get the RAM size. */ |
paul@0 | 74 | |
paul@0 | 75 | la $v0, BMXDRMSZ |
paul@0 | 76 | lw $v0, 0($v0) |
paul@0 | 77 | |
paul@0 | 78 | /* Initialise the stack pointer. */ |
paul@0 | 79 | |
paul@0 | 80 | lui $v1, 0x8000 /* 0x80000000 */ |
paul@0 | 81 | addu $sp, $v0, $v1 /* sp = 0x80000000 + RAM size */ |
paul@0 | 82 | |
paul@0 | 83 | /* Initialise the globals pointer. */ |
paul@0 | 84 | |
paul@0 | 85 | lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) |
paul@0 | 86 | ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) |
paul@0 | 87 | |
paul@0 | 88 | /* Set pins for output and set outputs low. */ |
paul@0 | 89 | |
paul@0 | 90 | jal init_pins |
paul@0 | 91 | nop |
paul@0 | 92 | |
paul@0 | 93 | /* Initialise the status register. */ |
paul@0 | 94 | |
paul@0 | 95 | jal init_interrupts |
paul@0 | 96 | nop |
paul@0 | 97 | |
paul@0 | 98 | /* Initialise timer. */ |
paul@0 | 99 | |
paul@0 | 100 | jal init_timer1 |
paul@0 | 101 | nop |
paul@0 | 102 | |
paul@0 | 103 | /* Enable interrupts and loop. */ |
paul@0 | 104 | |
paul@0 | 105 | jal enable_interrupts |
paul@0 | 106 | nop |
paul@0 | 107 | |
paul@0 | 108 | jal handle_error_level |
paul@0 | 109 | nop |
paul@0 | 110 | |
paul@0 | 111 | li $a1, 20000000 /* counter = 20000000 */ |
paul@1 | 112 | li $a2, 6374 /* test timer scaling */ |
paul@1 | 113 | li $s0, 0 /* line counter */ |
paul@1 | 114 | la $s1, vfp_active /* current event */ |
paul@0 | 115 | loop: |
paul@0 | 116 | addiu $a1, $a1, -1 /* counter -= 1 */ |
paul@0 | 117 | bnez $a1, loop /* until counter == 0 */ |
paul@0 | 118 | nop |
paul@0 | 119 | |
paul@0 | 120 | li $a1, 20000000 /* counter = 20000000 */ |
paul@0 | 121 | |
paul@1 | 122 | la $v0, PORTA |
paul@1 | 123 | la $v1, (1 << 2) /* PORTA<2> = RA2 */ |
paul@0 | 124 | sw $v1, INV($v0) |
paul@0 | 125 | |
paul@0 | 126 | _next: |
paul@0 | 127 | j loop |
paul@0 | 128 | nop |
paul@0 | 129 | |
paul@0 | 130 | |
paul@0 | 131 | |
paul@0 | 132 | init_pins: |
paul@1 | 133 | /* DEVCFG0<2> needs setting to 0 before the program is run. */ |
paul@0 | 134 | |
paul@0 | 135 | la $v0, CFGCON |
paul@0 | 136 | li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ |
paul@0 | 137 | sw $v1, CLR($v0) |
paul@0 | 138 | |
paul@0 | 139 | la $v0, TRISB |
paul@0 | 140 | li $v1, (7 << 7) | 15 /* TRISB<9:7> = RB9, RB8, RB7 = 0 */ |
paul@0 | 141 | /* sw $v1, CLR($v0) */ |
paul@0 | 142 | |
paul@0 | 143 | la $v0, PORTB |
paul@0 | 144 | li $v1, (7 << 7) | 15 /* PORTB<9:7> = RB9, RB8, RB7 = 0 */ |
paul@0 | 145 | /* sw $v1, CLR($v0) */ |
paul@0 | 146 | |
paul@0 | 147 | init_leds: |
paul@0 | 148 | la $v0, TRISA |
paul@0 | 149 | li $v1, (1 << 2) /* TRISA<2> = RA2 = 0 */ |
paul@0 | 150 | sw $v1, CLR($v0) |
paul@0 | 151 | |
paul@0 | 152 | la $v0, TRISB |
paul@0 | 153 | li $v1, (3 << 10) /* TRISB<11:10> = RB11, RB10 = 0 */ |
paul@0 | 154 | sw $v1, CLR($v0) |
paul@0 | 155 | |
paul@0 | 156 | clear_leds: |
paul@0 | 157 | la $v0, PORTA |
paul@0 | 158 | li $v1, (1 << 2) /* PORTA<2> = RA2 = 0 */ |
paul@0 | 159 | sw $v1, CLR($v0) |
paul@0 | 160 | |
paul@0 | 161 | la $v0, PORTB |
paul@0 | 162 | li $v1, (3 << 10) /* PORTB<11:10> = RB11, RB10 = 0 */ |
paul@0 | 163 | sw $v1, CLR($v0) |
paul@0 | 164 | |
paul@0 | 165 | jr $ra |
paul@0 | 166 | nop |
paul@0 | 167 | |
paul@0 | 168 | |
paul@0 | 169 | |
paul@0 | 170 | /* Utilities. */ |
paul@0 | 171 | |
paul@0 | 172 | handle_error_level: |
paul@0 | 173 | mfc0 $t3, CP0_STATUS |
paul@0 | 174 | li $t4, ~(STATUS_ERL | STATUS_EXL) |
paul@0 | 175 | and $t3, $t3, $t4 |
paul@0 | 176 | mtc0 $t3, CP0_STATUS |
paul@0 | 177 | jr $ra |
paul@0 | 178 | nop |
paul@0 | 179 | |
paul@0 | 180 | enable_interrupts: |
paul@0 | 181 | mfc0 $t3, CP0_STATUS |
paul@0 | 182 | nop |
paul@0 | 183 | ori $t3, $t3, STATUS_IRQ | STATUS_IE |
paul@0 | 184 | mtc0 $t3, CP0_STATUS |
paul@0 | 185 | jr $ra |
paul@0 | 186 | nop |
paul@0 | 187 | |
paul@0 | 188 | init_interrupts: |
paul@0 | 189 | mfc0 $t3, CP0_STATUS |
paul@0 | 190 | li $t4, ~STATUS_BEV |
paul@0 | 191 | and $t3, $t3, $t4 |
paul@0 | 192 | mtc0 $t3, CP0_STATUS /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ |
paul@0 | 193 | |
paul@0 | 194 | la $t3, _start |
paul@0 | 195 | mtc0 $t3, CP0_EBASE /* EBASE = _start */ |
paul@0 | 196 | |
paul@0 | 197 | li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ |
paul@0 | 198 | mtc0 $t3, CP0_CAUSE |
paul@0 | 199 | |
paul@0 | 200 | jr $ra |
paul@0 | 201 | nop |
paul@0 | 202 | |
paul@0 | 203 | |
paul@0 | 204 | |
paul@0 | 205 | /* Interrupt servicing. */ |
paul@0 | 206 | |
paul@0 | 207 | .org 0x200 |
paul@0 | 208 | |
paul@0 | 209 | interrupt_handler: |
paul@0 | 210 | |
paul@0 | 211 | /* Check for a timer interrupt condition. */ |
paul@0 | 212 | |
paul@0 | 213 | la $v0, IFS0 |
paul@0 | 214 | lw $v1, 0($v0) |
paul@0 | 215 | and $v1, $v1, (1 << 4) /* T1IF */ |
paul@0 | 216 | beqz $v1, irq_exit |
paul@0 | 217 | nop |
paul@0 | 218 | |
paul@1 | 219 | /* Test timer scaling. */ |
paul@1 | 220 | |
paul@0 | 221 | addiu $a2, $a2, -1 |
paul@0 | 222 | bnez $a2, irq_clear |
paul@0 | 223 | nop |
paul@1 | 224 | li $a2, 6374 |
paul@0 | 225 | |
paul@1 | 226 | /* Increment the line counter. */ |
paul@0 | 227 | |
paul@1 | 228 | addiu $s0, $s0, 1 |
paul@0 | 229 | |
paul@1 | 230 | /* Jump to the event handler. */ |
paul@1 | 231 | |
paul@1 | 232 | jalr $s1 |
paul@1 | 233 | nop |
paul@0 | 234 | |
paul@0 | 235 | irq_clear: |
paul@0 | 236 | |
paul@0 | 237 | /* Clear the timer interrupt condition. */ |
paul@0 | 238 | |
paul@0 | 239 | la $v0, IFS0 |
paul@0 | 240 | li $v1, (1 << 4) /* IFS0<4> = T1IF = 0 */ |
paul@0 | 241 | sw $v1, CLR($v0) |
paul@0 | 242 | |
paul@0 | 243 | irq_exit: |
paul@0 | 244 | eret |
paul@0 | 245 | nop |
paul@0 | 246 | |
paul@0 | 247 | |
paul@0 | 248 | |
paul@0 | 249 | /* Initialisation routines. */ |
paul@0 | 250 | |
paul@0 | 251 | init_timer1: |
paul@0 | 252 | |
paul@0 | 253 | /* Initialise Timer1 interrupt. */ |
paul@0 | 254 | |
paul@0 | 255 | la $v0, T1CON |
paul@0 | 256 | sw $zero, 0($v0) /* T1CON = 0 */ |
paul@0 | 257 | nop |
paul@0 | 258 | |
paul@0 | 259 | la $v0, TMR1 |
paul@0 | 260 | sw $zero, 0($v0) /* TMR1 = 0 */ |
paul@0 | 261 | la $v0, PR1 |
paul@0 | 262 | li $v1, HFREQ_LIMIT |
paul@0 | 263 | sw $v1, 0($v0) /* PR1 = HFREQ_LIMIT */ |
paul@0 | 264 | |
paul@0 | 265 | /* Initialise Timer1 interrupt. */ |
paul@0 | 266 | |
paul@0 | 267 | la $v0, IFS0 |
paul@0 | 268 | li $v1, (1 << 4) |
paul@0 | 269 | sw $v1, CLR($v0) /* IFS0CLR: T1IF = 0 */ |
paul@0 | 270 | la $v0, IPC1 |
paul@0 | 271 | li $v1, (7 << 2) |
paul@0 | 272 | sw $v1, SET($v0) /* IPC1SET: T1IP = 7 */ |
paul@0 | 273 | la $v0, IPC1 |
paul@0 | 274 | li $v1, 3 |
paul@0 | 275 | sw $v1, SET($v0) /* IPC1SET: T1IS = 3 */ |
paul@0 | 276 | la $v0, IEC0 |
paul@0 | 277 | li $v1, (1 << 4) |
paul@0 | 278 | sw $v1, SET($v0) /* IEC0SET: T1IE = 1 */ |
paul@0 | 279 | |
paul@0 | 280 | /* Start timer. */ |
paul@0 | 281 | |
paul@0 | 282 | la $v0, T1CON |
paul@0 | 283 | li $v1, (1 << 15) /* ON = 1 */ |
paul@0 | 284 | sw $v1, SET($v0) /* T1CONSET: ON = 1 */ |
paul@0 | 285 | |
paul@0 | 286 | jr $ra |
paul@0 | 287 | nop |
paul@1 | 288 | |
paul@1 | 289 | |
paul@1 | 290 | |
paul@1 | 291 | /* Event routines. */ |
paul@1 | 292 | |
paul@1 | 293 | /* Start of, and within, the vertical front porch. */ |
paul@1 | 294 | |
paul@1 | 295 | vfp_start: |
paul@1 | 296 | /* Clear vsync. */ |
paul@1 | 297 | |
paul@1 | 298 | la $v0, PORTB |
paul@1 | 299 | la $v1, (1 << 10) /* PORTB<10> = RB10 */ |
paul@1 | 300 | sw $v1, CLR($v0) |
paul@1 | 301 | |
paul@1 | 302 | /* Enter the active region. */ |
paul@1 | 303 | |
paul@1 | 304 | la $s1, vfp_active |
paul@1 | 305 | |
paul@1 | 306 | vfp_active: |
paul@1 | 307 | /* Test for vsync. */ |
paul@1 | 308 | |
paul@1 | 309 | subu $v0, $s0, VSYNC_START |
paul@1 | 310 | bnez $v0, _vfp_active_ret |
paul@1 | 311 | nop |
paul@1 | 312 | |
paul@1 | 313 | /* Start the vsync region. */ |
paul@1 | 314 | |
paul@1 | 315 | la $s1, vsync_start |
paul@1 | 316 | |
paul@1 | 317 | _vfp_active_ret: |
paul@1 | 318 | jr $ra |
paul@1 | 319 | nop |
paul@1 | 320 | |
paul@1 | 321 | |
paul@1 | 322 | |
paul@1 | 323 | /* Start of, and within, vsync. */ |
paul@1 | 324 | |
paul@1 | 325 | vsync_start: |
paul@1 | 326 | /* Set vsync. */ |
paul@1 | 327 | |
paul@1 | 328 | la $v0, PORTB |
paul@1 | 329 | la $v1, (1 << 10) /* PORTB<10> = RB10 */ |
paul@1 | 330 | sw $v1, SET($v0) |
paul@1 | 331 | |
paul@1 | 332 | /* Enter the active region. */ |
paul@1 | 333 | |
paul@1 | 334 | la $s1, vsync_active |
paul@1 | 335 | |
paul@1 | 336 | vsync_active: |
paul@1 | 337 | /* Test for visible region. */ |
paul@1 | 338 | |
paul@1 | 339 | subu $v0, $s0, VISIBLE_START |
paul@1 | 340 | bnez $v0, _vsync_active_ret |
paul@1 | 341 | nop |
paul@1 | 342 | |
paul@1 | 343 | /* Start the visible region. */ |
paul@1 | 344 | |
paul@1 | 345 | la $s1, visible_start |
paul@1 | 346 | |
paul@1 | 347 | _vsync_active_ret: |
paul@1 | 348 | jr $ra |
paul@1 | 349 | nop |
paul@1 | 350 | |
paul@1 | 351 | |
paul@1 | 352 | |
paul@1 | 353 | /* Start of, and within, the visible period. */ |
paul@1 | 354 | |
paul@1 | 355 | visible_start: |
paul@1 | 356 | /* Clear vsync. */ |
paul@1 | 357 | |
paul@1 | 358 | la $v0, PORTB |
paul@1 | 359 | la $v1, (1 << 10) /* PORTB<10> = RB10 */ |
paul@1 | 360 | sw $v1, CLR($v0) |
paul@1 | 361 | |
paul@1 | 362 | /* Enter the active region. */ |
paul@1 | 363 | |
paul@1 | 364 | la $s1, visible_active |
paul@1 | 365 | |
paul@1 | 366 | visible_active: |
paul@1 | 367 | /* NOTE: Perform line activities. */ |
paul@1 | 368 | |
paul@1 | 369 | la $v0, PORTB |
paul@1 | 370 | la $v1, (1 << 11) /* PORTB<11> = RB11 */ |
paul@1 | 371 | sw $v1, INV($v0) |
paul@1 | 372 | |
paul@1 | 373 | /* Test for back porch. */ |
paul@1 | 374 | |
paul@1 | 375 | subu $v0, $s0, VBP_START |
paul@1 | 376 | bnez $v0, _visible_active_ret |
paul@1 | 377 | nop |
paul@1 | 378 | |
paul@1 | 379 | /* Start the back porch. */ |
paul@1 | 380 | |
paul@1 | 381 | la $s1, vbp_active |
paul@1 | 382 | |
paul@1 | 383 | _visible_active_ret: |
paul@1 | 384 | jr $ra |
paul@1 | 385 | nop |
paul@1 | 386 | |
paul@1 | 387 | |
paul@1 | 388 | |
paul@1 | 389 | /* Within the vertical back porch. */ |
paul@1 | 390 | |
paul@1 | 391 | vbp_active: |
paul@1 | 392 | /* Test for front porch. */ |
paul@1 | 393 | |
paul@1 | 394 | subu $v0, $s0, VBP_END |
paul@1 | 395 | bnez $v0, _vbp_active_ret |
paul@1 | 396 | nop |
paul@1 | 397 | |
paul@1 | 398 | /* Start the front porch. */ |
paul@1 | 399 | |
paul@1 | 400 | li $s0, 0 |
paul@1 | 401 | la $s1, vfp_start |
paul@1 | 402 | |
paul@1 | 403 | _vbp_active_ret: |
paul@1 | 404 | jr $ra |
paul@1 | 405 | nop |