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