/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include <project.h>

// Get the resolution from the Video Controller instance.
#define VGA_RES_X VideoCtrl_1_H_RES
#define VGA_RES_Y VideoCtrl_1_V_RES
// Our buffer will be one bit per pixel so we only need 1/8th for our horizontal dimension.
#define VGA_X_FACTOR 8
// We don't have enough memory so we are going to duplicate the vertical lines
// to save on memory requirements.
#define VGA_Y_FACTOR 2
// This is our final dimmensions for our frame buffers.
#define VGA_X_BYTES (VGA_RES_X/VGA_X_FACTOR)
#define VGA_Y_BYTES (VGA_RES_Y/VGA_Y_FACTOR)

// Define our frame buffers making sure the X dimension is continuous in memory.
//
// CPU frame, by default this will be on 0x1FFF8000 Code SRAM space (i.e. section .ram)
uint8 cframe[VGA_Y_BYTES][VGA_X_BYTES];
// DMA frame, Declare the DMA video frame  to go in our new section .ram2 located at 0x2000000
uint8 dframe[VGA_Y_BYTES][VGA_X_BYTES] __attribute__ ((section(".ram2")));

// Declare our DMA channel and our DMA Transaction Descriptor.
uint8 dmaCh, dmaTd;

// Set up a refresh signal so the CPU can refresh the DMA buffer.
volatile int refresh = 1;

// ScanLine Interrupt
//
// This gets called everytime our DMA transfer is done, so we can setup the next line
// or if we are on the last line we will refresh the dma frame with the current cpu frame.
CY_ISR(ScanLine)
{
    // Get our line count from both status registers
    // LINE_CNT_HI holds the upper 2 bits
    // LINE_CNT_LO holds the lower 8 bits
    uint16 line = ((LINE_CNT_HI_Status<<8))|LINE_CNT_LO_Status;

    //
    // Hack,
    // I guess our line counter is a bit slow.
    line = (line-1)&0x03ff;

    // Only process every other line.
    if ((line % VGA_Y_FACTOR) == 0)
    {
        // Check if we are within the visible area.
        if (line < VGA_RES_Y)
        {
            // Update the next DMA transfer for the next line.
            // adusting the line by the Y skip factor.
            CY_SET_REG16(CY_DMA_TDMEM_STRUCT_PTR[dmaTd].TD1, LO16((uint32) dframe[line / VGA_Y_FACTOR]));
        }
        else if (line == VGA_RES_Y)
        {
            // On the last line since we are going to enter vertical sync
            // Indicate the CPU that it's ok to refresh the screen.
            refresh = 1;
        }
    }
}

int main()
{

    CyGlobalIntEnable; /* Enable global interrupts. */

    //
    // DMA setup
    //
    // Alocate a transaction descriptor.
    dmaTd = CyDmaTdAllocate();
    // Initialize the DMA channel to transfer from the dframe base address to the control base address.
    // This indicates the high 16 bit address that will apply to the low addresses set on the TD.
    dmaCh = DMA_DmaInitialize(1, 0, HI16((uint32) dframe), HI16(CYDEV_PERIPH_BASE));
    // Configure the transaction descriptor for the first transfer.
    // Transder VGA_X_BYTES with auto increment and signalling the end of the transfer.
    // use the single transaction descriptor as our next TD as well.
    CyDmaTdSetConfiguration(dmaTd, VGA_X_BYTES, dmaTd, DMA__TD_TERMOUT_EN | TD_INC_SRC_ADR);
    // Set the destination address to be our DMA_OUT control register in the schematic.
    CyDmaTdSetAddress(dmaTd, LO16((uint32) dframe[0]), LO16((uint32) DMA_OUT_Control_PTR));
    // Set the channel transaction descriptor that we just configured.
    CyDmaChSetInitialTd(dmaCh, dmaTd);
    // Finally enable the DMA channel.
    // This will start the first transfer and call the interrupt after every line.
    CyDmaChEnable(dmaCh, 1);

    //
    // Interrup Setup.
    //
    // Set our interrupt for SCANLINE to the interrupt function declared above.
    SCANLINE_StartEx(ScanLine);

    // Lets just setup something to display in here.
    // for now just setup a border to see if we get it all in frame.
    // lest set it as a define, so we can easily compile out the code
    // when we don't need this test.
#define TEST_BORDER 1
#if TEST_BORDER
    int x = 0, y = 0;
    for (y = 0; y < VGA_Y_BYTES; y++)
    {
        for (x = 0; x < VGA_X_BYTES; x++)
        {
            // On the first and last line we are setting all the pixels on.
            if ((y == 0) || (y == (VGA_Y_BYTES-1)))
            {
                cframe[y][x] = 0xff;
            }
            else
            {
                // On the rest of the lines.
                if (x == 0)
                {
                    // Set the highest nibble for the left border.
                    cframe[y][x] = 0xf0;
                }
                else if (x == (VGA_X_BYTES-1))
                {
                    // Set the lowest nibble for the right border.
                    cframe[y][x] = 0x0f;
                }
                else
                {
                    // The rest is all blank (or background color.
                    cframe[y][x] = 0x00;
                }
            }                
        }
    }
#endif

    // We could update the CPU frame buffer (cframe) within the for loop.
    // like for example implement a Pong game.
    // The DMA interrupt and hardware will take care to update the DMA frame buffer.
    for(;;)
    {
        // Refresh the screen when the interrupt sets the refresh bit on.
        if (refresh)
        {
            refresh = 0;
            CyDmaChDisable(dmaCh);
            memcpy(*dframe, *cframe, VGA_Y_BYTES * VGA_X_BYTES);
            CY_SET_REG16(CY_DMA_TDMEM_STRUCT_PTR[dmaTd].TD1, LO16((uint32) dframe[0]));
            CyDmaChEnable(dmaCh, 1);
        }
        else
        {
            // Here we can put code that modifies the CPU frame when we are not busy updating
            // the DMA buffer.
        }
    }
}

/* [] END OF FILE */
