VGAPIC32

Annotated vga.S

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