1 /* 2 * Generate a VGA signal using a PIC32 microcontroller. 3 * 4 * Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "init.h" 21 #include "vga_display.h" 22 23 24 25 /* Display state. */ 26 27 VGA_Display(vga_display, 2); 28 29 /* Pixel data. */ 30 31 static const int ZERO_LENGTH = 1; 32 static const uint8_t zerodata[1] = {0}; 33 34 35 36 /* Initialise the state machine. */ 37 38 void init_vga(display_config_t *display_config, int line_channels, 39 int line_timer, int transfer_int_num) 40 { 41 /* Display parameters. */ 42 43 vga_display.line_channels = line_channels; 44 45 /* Initial state. */ 46 47 vga_display.state_handler = vbp_active; 48 vga_display.scanline = 0; 49 vga_display.line = 0; 50 51 /* Configure a general display timer to start line data transfer and for the 52 horizontal sync. */ 53 54 vga_display.line_timer = line_timer; 55 56 /* Configure a separate transfer interrupt condition, if indicated. 57 Otherwise, transfers are initiated by the line timer. */ 58 59 vga_display.transfer_int_num = transfer_int_num; 60 61 /* Configure a single window. */ 62 63 vga_display.window_end[0] = display_config->line_count; 64 vga_display.windows = 1; 65 66 /* Define the line length and count from this window. */ 67 68 vga_display.line_length = display_config->line_length; 69 vga_display.line_count = display_config->line_count; 70 71 /* Update the addresses used for the first window of the display. */ 72 73 vga_set_frame(display_config, 0); 74 75 /* Compute display properties. */ 76 77 vga_update_properties(); 78 } 79 80 /* Initialise a separate transfer timer if different from the general display 81 timer. */ 82 83 void init_vga_with_timers(display_config_t *display_config, int line_channels, 84 int line_timer, int transfer_timer) 85 { 86 /* Initialise the basic properties of the display. */ 87 88 init_vga(display_config, line_channels, line_timer, 89 transfer_timer ? timer_interrupt_number(transfer_timer) : -1); 90 91 /* Configure a line timer for horizontal sync and line data transfers. */ 92 93 /* The timers have no prescaling (0). */ 94 95 timer_init(line_timer, 0, vga_display.hfreq_limit); 96 97 /* Enable interrupt requests when the CPU needs to perform the transfer, as 98 opposed to the DMA channels doing so. */ 99 100 if (!line_channels) 101 timer_init_interrupt(line_timer, 7, 3); 102 103 timer_on(line_timer); 104 105 /* Configure a separate transfer timer, if indicated. */ 106 107 if (!line_channels || !transfer_timer || (transfer_timer == line_timer)) 108 return; 109 110 /* The timer wraps around immediately. */ 111 112 timer_init(transfer_timer, 0, 1); 113 timer_on(transfer_timer); 114 } 115 116 /* Update computed properties. */ 117 118 void vga_update_properties(void) 119 { 120 vga_display.line_multiplier = vga_display.scanlines / vga_display.line_count; 121 } 122 123 124 125 /* Configure the transfer of pixel data. */ 126 127 void vga_configure_transfer(uint32_t output) 128 { 129 vga_display.output = output; 130 131 if (vga_display.line_channels) 132 vga_configure_dma_transfer(output); 133 } 134 135 /* Configure DMA channels for the transfer of pixel data. */ 136 137 void vga_configure_dma_transfer(uint32_t output) 138 { 139 int dual_channel = vga_display.line_channels == 2; 140 int channel = 0; 141 142 /* Determine whether an initiating channel is used. */ 143 144 int initiating_channel = vga_display.transfer_int_num >= 0; 145 146 /* Determine the different interrupt conditions, with pixel data employing 147 any specified transfer condition or the line condition otherwise. */ 148 149 int line_int_num = timer_interrupt_number(vga_display.line_timer); 150 int transfer_int_num = initiating_channel ? 151 vga_display.transfer_int_num : line_int_num; 152 153 /* Where dual line channels are involved, put the first before any 154 initiating channel, chaining it to such a channel. */ 155 156 if (dual_channel) 157 { 158 vga_configure_line_channel(channel++, transfer_int_num, 159 initiating_channel ? dma_chain_next : dma_chain_none, 160 output); 161 } 162 163 /* Introduce a special initiating channel if a separate transfer interrupt 164 has been indicated. */ 165 166 if (initiating_channel) 167 { 168 vga_configure_zero_channel(channel++, line_int_num, 1, output); 169 } 170 171 /* A line channel is always configured, chaining it to any initiating 172 channel. */ 173 174 vga_configure_line_channel(channel++, transfer_int_num, 175 initiating_channel ? dma_chain_previous : dma_chain_none, 176 output); 177 178 /* A zero channel is always configured, chaining it to the preceding line 179 channel, with the same transfer interrupt initiating the transfer. */ 180 181 vga_configure_zero_channel(channel, transfer_int_num, 0, output); 182 } 183 184 void vga_configure_line_channel(int channel, int int_num, enum dma_chain chain, 185 uint32_t output) 186 { 187 dma_init(channel, 2); 188 189 /* If initiating, configure to always be enabled. */ 190 191 if (chain == dma_chain_none) 192 dma_set_auto_enable(channel, 1); 193 194 /* Chain to either the previous or next channel where a special initiating 195 channel is used. */ 196 197 else 198 dma_set_chaining(channel, chain); 199 200 /* Initiate DMA on the transfer interrupt, transferring line data to the 201 first byte of the output word. */ 202 203 dma_set_interrupt(channel, int_num, 1); 204 205 dma_set_destination(channel, HW_PHYSICAL(output), 1); 206 dma_set_cell(channel, vga_display.transfer_cell_size); 207 } 208 209 void vga_configure_zero_channel(int channel, int int_num, int initiating, 210 uint32_t output) 211 { 212 dma_init(channel, 3); 213 214 /* If initiating, configure to always be enabled. */ 215 216 if (initiating) 217 dma_set_auto_enable(channel, 1); 218 219 /* Otherwise, chain to the preceding channel and register prior transfer 220 events before the channel is activated. */ 221 222 else 223 { 224 dma_set_chaining(channel, dma_chain_previous); 225 dma_set_receive_events(channel, 1); 226 } 227 228 /* Transfer upon the indicated interrupt condition. */ 229 230 dma_set_interrupt(channel, int_num, 1); 231 dma_set_transfer(channel, PHYSICAL((uint32_t) zerodata), 232 ZERO_LENGTH, 233 HW_PHYSICAL(output), 1, 234 ZERO_LENGTH); 235 } 236 237 /* Configure output compare units for horizontal and vertical sync. */ 238 239 void vga_configure_sync(int hsync_unit, int vsync_unit) 240 { 241 /* Record the peripherals in use. */ 242 243 vga_display.hsync_unit = hsync_unit; 244 vga_display.vsync_unit = vsync_unit; 245 246 /* Horizontal sync. */ 247 248 /* Configure output compare in dual compare (continuous output) mode using 249 the timer as time base. The interrupt condition drives the first DMA 250 channel and is handled to drive the display state machine. */ 251 252 oc_init(hsync_unit, 0b101, vga_display.line_timer); 253 oc_set_pulse(hsync_unit, vga_display.hsync_end); 254 oc_set_pulse_end(hsync_unit, vga_display.hsync_start); 255 oc_init_interrupt(hsync_unit, 7, 3); 256 oc_on(hsync_unit); 257 258 /* Vertical sync. */ 259 260 /* Configure output compare in single compare (output driven low) mode using 261 the timer as time base. The unit is enabled later. It is only really used 262 to achieve precisely-timed level transitions in hardware. */ 263 264 oc_init(vsync_unit, 0b010, vga_display.line_timer); 265 oc_set_pulse(vsync_unit, 0); 266 } 267 268 269 270 /* Initialise display window details. */ 271 272 void vga_add_window(display_config_t *display_config) 273 { 274 if (vga_display.windows >= vga_display.max_windows) 275 return; 276 277 /* Set the end of the window. */ 278 279 vga_display.window_end[vga_display.windows] = 280 vga_display.window_end[vga_display.windows - 1] + 281 display_config->line_count; 282 283 /* Add the window height to the display height. */ 284 285 vga_display.line_count += display_config->line_count; 286 287 /* Add the window. */ 288 289 vga_set_frame(display_config, vga_display.windows); 290 vga_display.windows++; 291 292 /* Compute display properties. */ 293 294 vga_update_properties(); 295 } 296 297 /* Update the display addresses from the general display configuration. */ 298 299 void vga_set_frame(display_config_t *display_config, int window) 300 { 301 vga_wait_visible(); 302 303 vga_display.screen_start[window] = display_config->screen_start; 304 vga_display.screen_limit[window] = display_config->screen_limit; 305 vga_display.screen_size[window] = display_config->screen_size; 306 } 307 308 /* Wait for the visible region to be completed. */ 309 310 void vga_wait_visible(void) 311 { 312 while (((volatile void (*)()) vga_display.state_handler) == visible_active) 313 __asm__ __volatile__("wait"); 314 } 315 316 317 318 /* Display state machine interrupt handler. */ 319 320 void vga_interrupt_handler(void) 321 { 322 uint32_t ifs; 323 324 if (!vga_display.line_channels) 325 { 326 /* Check for a timer interrupt condition. */ 327 328 ifs = REG(TIMERIFS) & TIMER_INT_FLAGS(vga_display.line_timer, TxIF); 329 330 if (ifs) 331 { 332 vga_transfer_interrupt_handler(); 333 CLR_REG(TIMERIFS, ifs); 334 } 335 } 336 337 /* Check for a OC1 interrupt condition. */ 338 339 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 340 341 if (ifs) 342 { 343 vga_hsync_interrupt_handler(); 344 CLR_REG(OCIFS, ifs); 345 } 346 347 } 348 349 /* Display state machine interrupt handler. */ 350 351 void vga_hsync_interrupt_handler(void) 352 { 353 vga_display.scanline += 1; 354 vga_display.state_handler(); 355 } 356 357 /* Visible region pixel output handler, used when the CPU is responsible for 358 producing pixel output rather than the DMA channels. */ 359 360 void vga_transfer_interrupt_handler(void) 361 { 362 uint8_t *current, *end, *output; 363 364 if (vga_display.state_handler != visible_active) 365 return; 366 367 /* Generate the pixel signal. */ 368 369 output = (uint8_t *) vga_display.output; 370 end = vga_display.linedata + vga_display.line_length; 371 372 /* This is potentially not as efficient as loading words and shifting bytes 373 but it appears difficult to implement that approach without experiencing 374 data load exceptions. */ 375 376 for (current = vga_display.linedata; current < end; current++) 377 REG(output) = *current; 378 379 /* Reset the signal level. */ 380 381 REG(output) = 0; 382 } 383 384 385 386 /* Vertical back porch region. */ 387 388 void vbp_active(void) 389 { 390 if (vga_display.scanline < vga_display.visible_start) 391 return; 392 393 /* Enter the visible region. */ 394 395 vga_display.state_handler = visible_active; 396 397 /* Prepare to show the first window. */ 398 399 vga_display.window = 0; 400 vga_display.line = 0; 401 402 /* Set the line address. */ 403 404 vga_display.linedata = vga_display.screen_start[0]; 405 406 if (vga_display.line_channels) 407 start_visible(); 408 } 409 410 /* Visible region. */ 411 412 void visible_active(void) 413 { 414 if (vga_display.scanline < vga_display.vfp_start) 415 { 416 /* Update the line address and handle wraparound. */ 417 418 if (!(vga_display.scanline % vga_display.line_multiplier)) 419 { 420 vga_display.line += 1; 421 422 /* Move to the next window if the end has been reached. */ 423 424 if (vga_display.line == vga_display.window_end[vga_display.window]) 425 { 426 vga_display.window++; 427 428 if (vga_display.window == vga_display.windows) 429 { 430 vga_display.window = 0; 431 vga_display.line = 0; 432 } 433 434 vga_display.linedata = vga_display.screen_start[vga_display.window]; 435 } 436 else 437 { 438 vga_display.linedata += vga_display.line_length; 439 440 if (vga_display.linedata >= vga_display.screen_limit[vga_display.window]) 441 vga_display.linedata -= vga_display.screen_size[vga_display.window]; 442 } 443 } 444 445 if (vga_display.line_channels) 446 update_visible(); 447 return; 448 } 449 450 /* End the visible region. */ 451 452 vga_display.state_handler = vfp_active; 453 454 /* Disable the channel for the next line. */ 455 456 if (vga_display.line_channels) 457 stop_visible(); 458 } 459 460 /* Vertical front porch region. */ 461 462 void vfp_active(void) 463 { 464 if (vga_display.scanline < vga_display.vsync_start) 465 return; 466 467 /* Enter the vertical sync region. */ 468 469 vga_display.state_handler = vsync_active; 470 471 /* Bring vsync low when the next line starts. */ 472 473 vsync_low(); 474 } 475 476 /* Vertical sync region. */ 477 478 void vsync_active(void) 479 { 480 if (vga_display.scanline < vga_display.vsync_end) 481 return; 482 483 /* Start again at the top of the display. */ 484 485 vga_display.scanline = 0; 486 vga_display.state_handler = vbp_active; 487 488 /* Bring vsync high when the next line starts. */ 489 490 vsync_high(); 491 } 492 493 494 495 /* Enable the channels for the next line. */ 496 497 void start_visible(void) 498 { 499 update_visible(); 500 update_transfers(1); 501 } 502 503 /* Update the channels for the next line. */ 504 505 void update_visible(void) 506 { 507 uint32_t transfer_start = (uint32_t) vga_display.linedata; 508 uint32_t transfer_length = vga_display.line_length / 509 vga_display.line_channels; 510 511 /* Determine whether an initiating channel is used. */ 512 513 int initiating_channel = vga_display.transfer_int_num >= 0; 514 int channel = 0; 515 516 /* Update the source of a secondary line channel. */ 517 518 if (vga_display.line_channels == 2) 519 { 520 dma_set_source(channel, PHYSICAL(transfer_start), transfer_length); 521 transfer_start += transfer_length; 522 channel++; 523 } 524 525 /* Skip any initiating channel. */ 526 527 if (initiating_channel) 528 channel++; 529 530 /* Update the source of a line channel, with potentially updated start 531 address. */ 532 533 dma_set_source(channel, PHYSICAL(transfer_start), transfer_length); 534 } 535 536 /* Disable the channels for the next line. */ 537 538 void stop_visible(void) 539 { 540 update_transfers(0); 541 } 542 543 /* Enable or disable transfers. */ 544 545 void update_transfers(int enable) 546 { 547 void (*fn)() = enable ? dma_on : dma_off; 548 549 /* Determine whether an initiating channel is used. */ 550 551 int initiating_channel = vga_display.transfer_int_num >= 0; 552 int channel = 0; 553 554 /* Update line channels if no initiating channel is used. */ 555 556 if (vga_display.line_channels == 2) 557 { 558 if (!initiating_channel) 559 fn(channel); 560 channel++; 561 } 562 563 /* Update any initiating channel. */ 564 565 if (initiating_channel) 566 { 567 fn(channel); 568 channel++; 569 } 570 571 /* Update line channels if no initiating channel is used. */ 572 573 if (!initiating_channel) 574 fn(channel); 575 } 576 577 578 579 /* Bring vsync low (single compare, output driven low) when the next line 580 starts. */ 581 582 void vsync_low(void) 583 { 584 oc_init(vga_display.vsync_unit, 0b010, vga_display.line_timer); 585 oc_on(vga_display.vsync_unit); 586 } 587 588 /* Bring vsync high (single compare, output driven high) when the next line 589 starts. */ 590 591 void vsync_high(void) 592 { 593 oc_init(vga_display.vsync_unit, 0b001, vga_display.line_timer); 594 oc_on(vga_display.vsync_unit); 595 }