// **************************************************************************** // // VGA output // // file derived from the PicoVGA project // https://github.com/Panda381/PicoVGA // by Miroslav Nemecek // // **************************************************************************** #include "include.h" // base layer commands #define VGADARK(num,col) (((u32)(vga_offset_dark+BASE_OFFSET)<<27) | ((u32)(num)<<8) | (u32)(col)) // assemble control word of "dark" command #define VGACMD(jmp,num) (((u32)(jmp)<<27) | (u32)(num)) // assemble control word // scanline type u8 ScanlineType[MAXLINE]; // current videomode sVmode CurVmode; // copy of current videomode table volatile int ScanLine; // current scan line 1... volatile u32 Frame; // frame counter volatile int BufInx; // current buffer set (0..1) volatile Bool VSync; // current scan line is vsync or dark static u8* framebuffer; static int fbwidth; u32 LineBufHsBp[4]; // HSYNC ... back porch-1 ... IRQ command ... image command u32 LineBufFp; // front porch+1 u32 LineBufDark[2]; // HSYNC ... dark line u32 LineBufSync[10]; // vertical synchronization // control buffers (BufInx = 0 running CtrlBuf1 and preparing CtrlBuf2, BufInx = 1 running CtrlBuf2 and preparing CtrlBuf1) u32 CtrlBuf1[CBUF_MAX]; // base layer control pairs: u32 count, read address (must be terminated with [0,0]) u32 CtrlBuf2[CBUF_MAX]; // base layer control pairs: u32 count, read address (must be terminated with [0,0]) // next control buffer u32* CtrlBufNext; // saved integer divider state hw_divider_state_t DividerState; // process scanline buffers (will save integer divider state into DividerState) int __not_in_flash_func(VgaBufProcess)() { // Clear the interrupt request for DMA control channel dma_hw->ints0 = (1u << VGA_DMA_PIO0); // switch current buffer index // BufInx = 0 running CtrlBuf1 and preparing CtrlBuf2, BufInx = 1 running CtrlBuf2 and preparing CtrlBuf1 // bufinx = 0 was running CtrlBuf1, will run CtrlBuf2, will process CtrlBuf1 int bufinx = BufInx; BufInx = bufinx ^ 1; // update DMA control channels of base layer, and run it dma_channel_set_read_addr(VGA_DMA_CB0, CtrlBufNext, true); // save integer divider state hw_divider_save_state(&DividerState); // increment scanline int line = ScanLine; // current scanline line++; // new current scanline if (line > CurVmode.vtot) // last scanline? { Frame++; // increment frame counter line = 1; // restart scanline } ScanLine = line; // store new scanline int y0 = -1; u8 linetype = ScanlineType[line]; switch (linetype) { case LINE_IMG: // progressive image 0, 1, 2,... y0 = line - CurVmode.vfirst; if (CurVmode.dbly) y0 >>= 1; VSync = False; // not vsync break; default: VSync = True; // vsync break; } return bufinx; } // VGA DMA handler - called on end of every scanline extern "C" void __not_in_flash_func(VgaLine)() { // process scanline buffers (will save integer divider state into DividerState) int bufinx = VgaBufProcess(); // prepare buffers to be processed next u32* cbuf; // control buffer if (bufinx == 0) { cbuf = CtrlBuf1; } else { cbuf = CtrlBuf2; } CtrlBufNext = cbuf; // next rendered scanline int line = ScanLine; // current scanline line++; // next line to render if (line > CurVmode.vtot) line = 1; int y0; u8 linetype = ScanlineType[line]; #ifdef VGA_VSYNC if (linetype == LINE_VSYNC) { gpio_put(VGA_VSYNC, 0); } else { gpio_put(VGA_VSYNC, 1); } #endif switch (linetype) { case LINE_VSYNC: // long vertical sync *cbuf++ = 2; // send 2x u32 *cbuf++ = (u32)&LineBufSync[0]; // VSYNC break; case LINE_DARK: // dark line *cbuf++ = 2; // send 2x u32 *cbuf++ = (u32)LineBufDark; // dark break; case LINE_IMG: // progressive image 0, 1, 2,... y0 = line - CurVmode.vfirst; if (CurVmode.dbly) y0 >>= 1; // HSYNC + back porch *cbuf++ = 4; // send 4x u32 *cbuf++ = (u32)LineBufHsBp; // HSYNC + back porch // image data *cbuf++ = fbwidth/4; *cbuf++ = (u32)&framebuffer[y0*fbwidth]; // front porch *cbuf++ = 1; // send 1x u32 *cbuf++ = (u32)&LineBufFp; // front porch break; } *cbuf++ = 0; // end mark *cbuf++ = 0; // end mark // restore integer divider state hw_divider_restore_state(&DividerState); } // initialize VGA DMA // control blocks aliases: // +0x0 +0x4 +0x8 +0xC (Trigger) // 0x00 (alias 0): READ_ADDR WRITE_ADDR TRANS_COUNT CTRL_TRIG // 0x10 (alias 1): CTRL READ_ADDR WRITE_ADDR TRANS_COUNT_TRIG // 0x20 (alias 2): CTRL TRANS_COUNT READ_ADDR WRITE_ADDR_TRIG // 0x30 (alias 3): CTRL WRITE_ADDR TRANS_COUNT READ_ADDR_TRIG ... ! void VgaDmaInit() { dma_channel_config cfg; // ==== prepare DMA control channel // prepare DMA default config cfg = dma_channel_get_default_config(VGA_DMA_CB0); // increment address on read from memory channel_config_set_read_increment(&cfg, true); // increment address on write to DMA port channel_config_set_write_increment(&cfg, true); // each DMA transfered entry is 32-bits channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32); // write ring - wrap to 8-byte boundary (TRANS_COUNT and READ_ADDR_TRIG of data DMA) channel_config_set_ring(&cfg, true, 3); // DMA configure dma_channel_configure( VGA_DMA_CB0, // channel &cfg, // configuration &dma_hw->ch[VGA_DMA_PIO0].al3_transfer_count, // write address &CtrlBuf1[0], // read address - as first, control buffer 1 will be sent out 2, // number of transfers in u32 false // do not start yet ); // ==== prepare DMA data channel // prepare DMA default config cfg = dma_channel_get_default_config(VGA_DMA_PIO0); // increment address on read from memory channel_config_set_read_increment(&cfg, true); // do not increment address on write to PIO channel_config_set_write_increment(&cfg, false); // each DMA transfered entry is 32-bits channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32); // DMA data request for sending data to PIO channel_config_set_dreq(&cfg, pio_get_dreq(VGA_PIO, VGA_SM0, true)); // chain channel to DMA control block channel_config_set_chain_to(&cfg, VGA_DMA_CB0); // raise the IRQ flag when 0 is written to a trigger register (end of chain) channel_config_set_irq_quiet(&cfg, true); // set byte swapping channel_config_set_bswap(&cfg, true); // set high priority cfg.ctrl |= DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS; // DMA configure dma_channel_configure( VGA_DMA_PIO0, // channel &cfg, // configuration &VGA_PIO->txf[VGA_SM0], // write address NULL, // read address 0, // number of transfers in u32 false // do not start immediately ); // ==== initialize IRQ0, raised from base layer 0 // enable DMA channel IRQ0 dma_channel_set_irq0_enabled(VGA_DMA_PIO0, true); // set DMA IRQ handler irq_set_exclusive_handler(DMA_IRQ_0, VgaLine); // set highest IRQ priority irq_set_priority(DMA_IRQ_0, 0); } // initialize VGA PIO void VgaPioInit() { // clear PIO instruction memory pio_clear_instruction_memory(VGA_PIO); // configure main program instructions uint16_t ins[32]; // temporary buffer of program instructions memcpy(ins, &vga_program_instructions, vga_program.length*sizeof(uint16_t)); // copy program into buffer u16 cpp = (u16)CurVmode.cpp; // number of clocks per pixel ins[vga_offset_extra1] |= (cpp-2) << 8; // update waits ins[vga_offset_extra2] |= (cpp-2) << 8; // update waits // load main program into PIO's instruction memory struct pio_program prg; prg.instructions = ins; prg.length = vga_program.length; prg.origin = BASE_OFFSET; pio_add_program(VGA_PIO, &prg); // connect PIO to the pad for (int i = VGA_GPIO_FIRST; i < VGA_GPIO_LAST; i++) pio_gpio_init(VGA_PIO, i); pio_gpio_init(VGA_PIO, VGA_GPIO_SYNC); #if VGA_VSYNC gpio_init(VGA_VSYNC); #endif // negative HSYNC output if (!CurVmode.psync) gpio_set_outover(VGA_GPIO_SYNC, GPIO_OVERRIDE_INVERT); // set pin direction to output pio_sm_set_consecutive_pindirs(VGA_PIO, VGA_SM0, VGA_GPIO_FIRST, VGA_GPIO_OUTNUM, true); pio_sm_set_consecutive_pindirs(VGA_PIO, VGA_SM0, VGA_GPIO_SYNC, 1, true); #if VGA_VSYNC gpio_set_dir(VGA_VSYNC, GPIO_OUT); #endif // get default config pio_sm_config cfg = pio_get_default_sm_config(); // map state machine's OUT and MOV pins sm_config_set_out_pins(&cfg, VGA_GPIO_FIRST, VGA_GPIO_OUTNUM); // join FIFO to send only sm_config_set_fifo_join(&cfg, PIO_FIFO_JOIN_TX); // PIO clock divider sm_config_set_clkdiv(&cfg, CurVmode.div); // shift left, autopull, pull threshold sm_config_set_out_shift(&cfg, false, true, 32); // base layer 0 // set wrap sm_config_set_wrap(&cfg, vga_wrap_target+BASE_OFFSET, vga_wrap+BASE_OFFSET); // set sideset pins of base layer sm_config_set_sideset(&cfg, 1, false, false); sm_config_set_sideset_pins(&cfg, VGA_GPIO_SYNC); // initialize state machine pio_sm_init(VGA_PIO, VGA_SM0, vga_offset_entry+BASE_OFFSET, &cfg); } // initialize scanline buffers void VgaBufInit() { // init HSYNC..back porch buffer // hsync must be min. 3 // hback must be min. 13 LineBufHsBp[0] = BYTESWAP(VGACMD(vga_offset_sync+BASE_OFFSET,CurVmode.hsync-3)); // HSYNC LineBufHsBp[1] = BYTESWAP(VGADARK(CurVmode.hback-4-1-9,0)); // back porch - 1 - 9 LineBufHsBp[2] = BYTESWAP(VGACMD(vga_offset_irqset+BASE_OFFSET,0)); // IRQ command (takes 9 clock cycles) LineBufHsBp[3] = BYTESWAP(VGACMD(vga_offset_output+BASE_OFFSET, CurVmode.width - 2)); // missing 2 clock cycles after last pixel // init front porch buffer // hfront must be min. 4 LineBufFp = BYTESWAP(VGADARK(CurVmode.hfront-4,0)); // front porch // init dark line LineBufDark[0] = BYTESWAP(VGACMD(vga_offset_sync+BASE_OFFSET,CurVmode.hsync-3)); // HSYNC LineBufDark[1] = BYTESWAP(VGADARK(CurVmode.htot-CurVmode.hsync-4,0)); // dark line // VGA mode // vertical synchronization #ifdef VGA_VSYNC // if VSYNC line LineBufSync[0] = BYTESWAP(VGACMD(vga_offset_sync+BASE_OFFSET,CurVmode.hsync-3)); // HSYNC LineBufSync[1] = BYTESWAP(VGADARK(CurVmode.htot-CurVmode.hsync-4,0)); // dark line #else // hsync must be min. 4 LineBufSync[0] = BYTESWAP(VGACMD(vga_offset_sync+BASE_OFFSET,CurVmode.htot-CurVmode.hsync-3)); // invert dark line LineBufSync[1] = BYTESWAP(VGADARK(CurVmode.hsync-4,0)); // invert HSYNC #endif // control blocks - initialize to VSYNC CtrlBuf1[0] = 2; // send 2x u32 CtrlBuf1[1] = (u32)&LineBufSync[0]; // VSYNC CtrlBuf2[0] = 2; // send 2x u32 CtrlBuf2[1] = (u32)&LineBufSync[0]; // VSYNC CtrlBuf1[2] = 0; // stop mark CtrlBuf1[3] = 0; // stop mark CtrlBuf2[2] = 0; // stop mark CtrlBuf2[3] = 0; // stop mark } // terminate VGA service void VgaTerm() { // abort DMA channels dma_channel_abort(VGA_DMA_PIO0); // pre-abort, could be chaining right now dma_channel_abort(VGA_DMA_CB0); // disable IRQ0 from DMA0 irq_set_enabled(DMA_IRQ_0, false); dma_channel_set_irq0_enabled(VGA_DMA_PIO0, false); // Clear the interrupt request for DMA control channel dma_hw->ints0 = (1u << VGA_DMA_PIO0); // stop all state machines pio_set_sm_mask_enabled(VGA_PIO, VGA_SMALL, false); // restart state machine pio_restart_sm_mask(VGA_PIO, VGA_SMALL); // clear FIFOs pio_sm_clear_fifos(VGA_PIO, VGA_SM0); CtrlBufNext = NULL; // clear PIO instruction memory pio_clear_instruction_memory(VGA_PIO); } // initialize scanline type table void ScanlineTypeInit(const sVmode* v) { u8* d = ScanlineType; int i, k; // line 0 is not used *d++ = LINE_DARK; // progressive mode (VGA 525) // vertical sync (VGA 2) for (i = v->vsync; i > 0; i--) *d++ = LINE_VSYNC; // dark (VGA 33) for (i = v->vback; i > 0; i--) *d++ = LINE_DARK; // image (VGA 480) for (i = v->vact; i > 0; i--) *d++ = LINE_IMG; // dark (VGA 10) for (i = v->vfront; i > 0; i--) *d++ = LINE_DARK; } // initialize videomode (returns False on bad configuration) void VgaInit(const sVmode* vmode, u8* buf, int width, int height, int stride) { int i; framebuffer = buf; fbwidth = width; // stop old state VgaTerm(); // initialize scanline type table ScanlineTypeInit(vmode); // save current videomode memcpy(&CurVmode, vmode, sizeof(sVmode)); // initialize parameters ScanLine = 1; // currently processed scanline BufInx = 0; // at first, control buffer 1 will be sent out CtrlBufNext = CtrlBuf2; // initialize VGA PIO VgaPioInit(); // initialize scanline buffers VgaBufInit(); // initialize DMA VgaDmaInit(); // enable DMA IRQ irq_set_enabled(DMA_IRQ_0, true); // start DMA with base layer 0 dma_channel_start(VGA_DMA_CB0); // run state machines pio_enable_sm_mask_in_sync(VGA_PIO, B0); } // wait for VSync scanline void WaitVSync() { // wait for end of VSync while (VSync) { __dmb(); } // wait for start of VSync while (!VSync) { __dmb(); } }