// **************************************************************************** // // VGA videomodes // // file derived from the PicoVGA project // https://github.com/Panda381/PicoVGA // by Miroslav Nemecek // // **************************************************************************** #include "include.h" sVmode Vmode; // videomode setup sVgaCfg Cfg; // required configuration /* http://martin.hinner.info/vga/pal.html VGA system (525 lines total): time 0: - line 1, 2: (2) vertical sync - line 3..35: (33) dark - line 36..515: (480) image lines 0..479 - line 516..525: (10) dark */ // === Monitor videomodes // EGA 8:5 640x400 (5:4 500x400, 4:3 528x400, 16:9 704x400), vert. 70 Hz, hor. 31.4685 kHz, pixel clock 25.175 MHz const sVideo VideoEGA = { // horizontal .htot= 31.77781f, // total scanline in [us] .hfront= 0.63556f, // H front porch (after image, before HSYNC) in [us] .hsync= 3.81334f, // H sync pulse in [us] .hback= 1.90667f, // H back porch (after HSYNC, before image) in [us] .hfull= 25.42224f, // H full visible in [us] // vertical .vtot=449, // total scanlines (both subframes) .vmax=400, // maximal height // frame .vsync=2, // V sync (half-)pulses .vpost=0, // V sync post half-pulses .vback=35, // V back porch (after VSYNC, before image) .vact=400, // active visible scanlines .vfront=12, // V front porch (after image, before VSYNC) .vpre=0, // V sync pre half-pulses // flags .psync=False, // positive synchronization }; // VGA 4:3 640x480 (16:9 848x480), vert. 60 Hz, hor. 31.4685 kHz, pixel clock 25.175 MHz const sVideo VideoVGA = { // horizontal .htot= 31.77781f, // total scanline in [us] (800 pixels) .hfront= 0.63556f, // H front porch (after image, before HSYNC) in [us] (16 pixels) .hsync= 3.81334f, // H sync pulse in [us] (96 pixels) .hback= 1.90667f, // H back porch (after HSYNC, before image) in [us] (48 pixels) .hfull= 25.42224f, // H full visible in [us] (640 pixels) // vertical .vtot=525, // total scanlines (both subframes) .vmax=480, // maximal height // frame .vsync=2, // V sync (half-)pulses .vpost=0, // V sync post half-pulses .vback=33, // V back porch (after VSYNC, before image) .vact=480, // active visible scanlines .vfront=10, // V front porch (after image, before VSYNC) .vpre=0, // V sync pre half-pulses // flags .psync=False, // positive synchronization }; // timings const sVideo* VideoResTab[DEV_MAX*RES_MAX] = { // DEV_VGA &VideoEGA, // RES_ZX = 0, // 256x192 &VideoVGA, // RES_CGA, // 320x200 &VideoVGA, // RES_QVGA, // 320x240 &VideoEGA, // RES_EGA, // 528x400 &VideoVGA, // RES_VGA, // 640x480 }; // required resolution width x height const u16 VideoResReq[RES_MAX*2] = { 256, 192, // RES_ZX = 0, // 256x192 320, 200, // RES_CGA, // 320x200 320, 240, // RES_QVGA, // 320x240 512, 400, // RES_EGA, // 512x400 640, 480, // RES_VGA, // 640x480 }; // Search PLL setup // reqkhz ... required output frequency in kHz // input ... PLL input frequency in kHz (default 12000, or use clock_get_hz(clk_ref)/1000) // vcomin ... minimal VCO frequency in kHz (default 400000) // vcomax ... maximal VCO frequency in kHz (default 1600000) // lowvco ... prefer low VCO (lower power but more jiter) // outputs: // outkhz ... output achieved frequency in kHz (0=not found) // outvco ... output VCO frequency in kHz // outfbdiv ... output fbdiv (16..320) // outpd1 ... output postdiv1 (1..7) // outpd2 ... output postdiv2 (1..7) // Returns true if precise frequency has been found, or near frequency used otherwise. bool vcocalc(u32 reqkhz, u32 input, u32 vcomin, u32 vcomax, bool lowvco, u32* outkhz, u32* outvco, u16* outfbdiv, u8* outpd1, u8* outpd2) { u32 khz, vco, margin; u16 fbdiv; u8 pd1, pd2; u32 margin_best = 100000; *outkhz = 0; // fbdiv loop fbdiv = lowvco ? 16 : 320; for (;;) { // get current vco vco = fbdiv * input; // check vco range if ((vco >= vcomin) && (vco <= vcomax)) { // pd1 loop for (pd1 = 7; pd1 >= 1; pd1--) { // pd2 loop for (pd2 = pd1; pd2 >= 1; pd2--) { // current output frequency khz = vco / (pd1 * pd2); // check best frequency margin = abs((int)(khz - reqkhz)); if (margin < margin_best) { margin_best = margin; *outkhz = khz; *outvco = vco; *outfbdiv = fbdiv; *outpd1 = pd1; *outpd2 = pd2; } } } } // shift fbdiv if (lowvco) { fbdiv++; if (fbdiv > 320) break; } else { fbdiv--; if (fbdiv < 16) break; } } // check precise frequency return (*outkhz == reqkhz) && (*outvco == *outkhz * *outpd1 * *outpd2); } // find sysclock setup (use set_sys_clock_pll to set sysclock) // reqkhz ... required frequency in kHz // outputs: // outkhz ... output achieved frequency in kHz (0=not found) // outvco ... output VCO frequency in kHz // outfbdiv ... output fbdiv (16..320) // outpd1 ... output postdiv1 (1..7) // outpd2 ... output postdiv2 (1..7) // Returns true if precise frequency has been found, or near frequency used otherwise. bool FindSysClock(u32 reqkhz, u32* outkhz, u32* outvco, u16* outfbdiv, u8* outpd1, u8* outpd2) { // get reference frequency in kHz (should be 12 MHz) u32 input = clock_get_hz(clk_ref)/1000; // find PLL setup return vcocalc(reqkhz, input, 400000, 1600000, false, outkhz, outvco, outfbdiv, outpd1, outpd2); } // initialize default VGA configuration void VgaCfgDef(sVgaCfg* cfg) { cfg->width = 640; // width in pixels cfg->height = 480; // height in lines cfg->wfull = 0; // width of full screen, corresponding to 'hfull' time (0=use 'width' parameter) cfg->video = &VideoVGA; // used video timings uint freq = clock_get_hz(clk_sys)/1000; cfg->freq = freq; // required minimal system frequency in kHz (real frequency can be higher) cfg->fmax = 270000; // maximal system frequency in kHz (limit resolution if needed) cfg->dbly = False; // double in Y direction cfg->lockfreq = False; // lock required frequency, do not change it } // calculate videomode setup // cfg ... required configuration // vmode ... destination videomode setup for driver void VgaCfg(const sVgaCfg* cfg, sVmode* vmode) { // prepare minimal and maximal clocks per pixel int mincpp = 2; int maxcpp = 17; // prepare full width int w = cfg->width; // required width int wfull = cfg->wfull; // full width if (wfull == 0) wfull = w; // use required width as 100% width // prepare maximal active time and maximal pixels const sVideo* v = cfg->video; float hmax = v->htot - v->hfront - v->hsync - v->hback; float hfull = v->hfull; int wmax = (int)(wfull*hmax/hfull + 0.001f); // calculate cpp from required frequency (rounded down), limit minimal cpp u32 freq = cfg->freq; int cpp = (int)(freq*hfull/1000/wfull + 0.1f); if (cpp < mincpp) cpp = mincpp; // recalculate frequency if not locked if (!cfg->lockfreq) { int freq2 = (int)(cpp*wfull*1000/hfull + 0.5f) + 200; if (freq2 < freq) { cpp++; freq2 = (int)(cpp*wfull*1000/hfull + 0.5f) + 200; } if (freq2 >= freq) freq = freq2; if (freq > cfg->fmax) freq = cfg->fmax; } // find sysclock setup (use set_sys_clock_pll to set sysclock) u32 vco; u16 fbdiv; u8 pd1, pd2; FindSysClock(freq, &freq, &vco, &fbdiv, &pd1, &pd2); vmode->freq = freq; vmode->vco = vco; vmode->fbdiv = fbdiv; vmode->pd1 = pd1; vmode->pd2 = pd2; // calculate divisor cpp = (int)(freq*hfull/1000/wfull + 0.2f); int div = 1; while (cpp > maxcpp) { div++; cpp = (int)(freq*hfull/1000/wfull/div + 0.2f); } vmode->div = div; vmode->cpp = cpp; // calculate new full resolution and max resolution wfull = (int)(freq*hfull/1000/cpp/div + 0.4f); wmax = (int)(freq*hmax/1000/cpp/div + 0.4f); // limit resolution if (w > wmax) w = wmax; w = ALIGN4(w); vmode->width = w; // active width vmode->wfull = wfull; // width of full screen (image should be full visible) vmode->wmax = wmax; // maximal width (can be > wfull) // horizontal timings int hwidth = w*cpp; // active width in state machine clocks int htot = (int)(freq*v->htot/1000/div + 0.5f); // total state machine clocks per line int hsync = (int)(freq*v->hsync/1000/div + 0.5f); // H sync pulse in state machine clocks (min. 4) if (hsync < 4) { htot -= 4 - hsync; hsync = 4; } int hfront = (int)(freq*v->hfront/1000/div + 0.5f); // H front porch in state machine clocks (min. 2) int hback = (int)(freq*v->hback/1000/div + 0.5f); // H back porch in state machine clocks (min. 13) int d = htot - hfront - hsync - hback - hwidth; // difference hfront += d/2; hback += (d < 0) ? (d-1)/2 : (d+1)/2; if (hfront < 4) { hback -= 4 - hfront; hfront = 4; } if (hback < 13) { hfront -= 13 - hback; hback = 13; if (hfront < 2) hfront = 2; } htot = hfront + hsync + hback + hwidth; // total state machine clocks per line vmode->htot = (u16)htot; // total state machine clocks per line vmode->hfront = (u16)hfront; // H front porch in state machine clocks (min. 2) vmode->hsync = (u16)hsync; // H sync pulse in state machine clocks (min. 4) vmode->hback = (u16)hback; // H back porch in state machine clocks (min. 13) // vertical timings int h = cfg->height; // required height if (cfg->dbly) h *= 2; // use double lines vmode->vmax = v->vmax; // maximal height if (h > v->vmax) h = v->vmax; // limit height if (cfg->dbly) h &= ~1; // must be even number if double lines int vact = h; // active lines in progress mode if (cfg->dbly) h /= 2; // return double lines to single lines vmode->height = h; // vertical timings vmode->vtot = v->vtot; // total scanlines vmode->vact = vact; // active scanlines int dh = vact - v->vact; // difference vmode->vsync = v->vsync; // V sync (half-)pulses vmode->vpost = v->vpost; // V sync post (half-)pulses vmode->vback = v->vback - dh/2; // V back porch (after VSYNC, before image) vmode->vfront = v->vfront - ((dh < 0) ? (dh-1)/2 : (dh+1)/2); // V front porch (after image, before VSYNC) vmode->vpre = v->vpre; // V sync pre (half-)pulses // frequency vmode->hfreq = vmode->freq * 1000.0f / vmode->div / vmode->htot; vmode->vfreq = vmode->hfreq / vmode->vtot; // flags vmode->lockfreq = cfg->lockfreq; // lock current frequency, do not change it vmode->dbly = cfg->dbly; // double scanlines vmode->psync = v->psync; // positive synchronization // first active scanline vmode->vfirst = vmode->vsync + vmode->vback + 1; } // initialize videomode // dev ... device DEV_* // res ... resolution RES_* const sVmode* Video(u8 dev, u8 res) { // prepare timings structure if (dev >= DEV_MAX) dev = DEV_VGA; if (res >= RES_MAX) res = RES_MAX-1; const sVideo* v = VideoResTab[dev*RES_MAX + res]; // required resolution u16 w = VideoResReq[res*2]; u16 h = VideoResReq[res*2+1]; if (h > v->vmax) h = v->vmax; // setup videomode VgaCfgDef(&Cfg); // get default configuration Cfg.video = v; // video timings Cfg.width = w; // screen width Cfg.height = h; // screen height Cfg.dbly = h <= v->vmax/2; // double scanlines VgaCfg(&Cfg, &Vmode); // calculate videomode setup // initialize system clock set_sys_clock_pll(Vmode.vco*1000, Vmode.pd1, Vmode.pd2); return &Vmode; }