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