CommonPIC32

lib/vga_display.c

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