[RFC][PATCH][FBDEV]: Setting fbcon's windows size

From: Antonino Daplas (adaplas@pol.net)
Date: Mon Jan 06 2003 - 09:54:37 EST


Hi,

In 2.5, in contrast with the 2.4 fbdev framework, any changes in the
fbdev layer will not reflect in the upper console layer, except during
initialization of fbcon. So using fbset to change the resolution will
produce unexpected results. If my understanding is correct, the
relationship between console and fbdev is now master (console) and slave
(fbdev). If this is true, then console changes must become visible to
fbcon/fbdev. This is easily accomplished by adding a csw->con_resize()
hook to fbcon.

<-- BEGIN -->
diff -Naur linux-2.5.54/drivers/video/console/fbcon.c linux/drivers/video/console/fbcon.c
--- linux-2.5.54/drivers/video/console/fbcon.c 2003-01-04 21:58:47.000000000 +0000
+++ linux/drivers/video/console/fbcon.c 2003-01-06 13:31:34.000000000 +0000
@@ -1871,6 +1871,25 @@
 }
 

+static int fbcon_resize(struct vc_data *vc, unsigned int width,
+ unsigned int height)
+{
+ struct display *p = &fb_display[vc->vc_num];
+ struct fb_info *info = p->fb_info;
+ struct fb_var_screeninfo var = info->var;
+ int err;
+
+ var.xres = width * vc->vc_font.width;
+ var.yres = height * vc->vc_font.height;
+ var.activate = FB_ACTIVATE_NOW;
+
+ err = fb_set_var(&var, info);
+
+ return (err || info->var.xres/vc->vc_font.width != width ||
+ info->var.yres/vc->vc_font.height != height) ?
+ -EINVAL : 0;
+}
+
 static int fbcon_switch(struct vc_data *vc)
 {
         int unit = vc->vc_num;
@@ -1920,6 +1939,7 @@
 
         info->currcon = unit;
         
+ fbcon_resize(vc, vc->vc_cols, vc->vc_rows);
         update_var(unit, info);
         
         if (vt_cons[unit]->vc_mode == KD_TEXT)
@@ -2537,6 +2557,7 @@
         .con_invert_region = fbcon_invert_region,
         .con_screen_pos = fbcon_screen_pos,
         .con_getxy = fbcon_getxy,
+ .con_resize = fbcon_resize,
 };
 
 int __init fb_console_init(void)

<-- END -->

The tty/console layer has several ioctl's that will allow changing of
the console window size (VT_RESIZE, VT_RESIZEX, TIOCSWINSZ, etc). So
using:

stty cols 128 rows 48

will change the fb resolution to 1024x768 if using an 8x16 font.

One advantage of this approach is that the changes are preserved per
console (in contrast to using fbset which sets all consoles).

This approach has one major problem though. In the 2.4 interface, we
have fbset that basically "assists" fbdev with the changes. The fbset
utility will fill up fb_var_screeninfo with correct information such as
video timings from /etc/fb.modes.

With the current approach, this "assistance" is gone. When a window size
change is requested, the fbdev driver is left on its own to find the
correct timing values for the video mode.

So, what's needed is a function that calculates timing parameters which
is generic enough to work with the most common monitors. One solution
is to use VESA's GTF (Generalized Timing Formula). Attached is a patch
that implements the formula.

The timings generated by GTF are different from the standard VESA
timings (DMT). However, it should work for GTF compliant monitors and
is also specifically formulated to work with older monitors as well.
Another advantage is that it can calculate the timings for any video
mode. It may not work for proprietary displays or TV displays.

One requirement of the GTF is that the monitor specs must be known, ie
info->monspecs must be valid. This can be filled up several ways:

1. VBE/DDC and EDID parsing (I see the beginnings of it already in
fbmon.c)

2. entered as a boot/module option

3. ?ioctl to upload monitor info to fbdev.

(As a side note, should we also add pixclock_min and pixclock_max to
info->monspecs?).

User-entered timings are always preferred, so these are validated
first. If the timings are not valid, then they will be computed. So,
here are 2 new functions:

1. fb_validate_mode(fb_var_screeninfo *var, fb_info *info)

2. fb_get_mode(u32 refresh, fb_var_screeninfo *var, fb_info *info)

It's in fb_get_mode() where the GTF is implemented. The 'refresh'
parameter is optional, and if == 0, the vertical refresh rate will be
maximized.

Anyway, using fb_get_mode(), I was able to generate working video modes from as low as
300x300@60 to as high as 1600x1200@85. I've also experimented with
unusual modes, such as 1600x480.

Comments?

Tony

diff -Naur linux-2.5.54/drivers/video/modedb.c linux/drivers/video/modedb.c
--- linux-2.5.54/drivers/video/modedb.c 2003-01-06 13:36:08.000000000 +0000
+++ linux/drivers/video/modedb.c 2003-01-06 13:34:50.000000000 +0000
@@ -423,4 +423,245 @@
     return 0;
 }
 
+#define FLYBACK 550
+#define V_FRONTPORCH 1
+#define H_OFFSET 40
+#define H_SCALEFACTOR 20
+#define H_BLANKSCALE 128
+#define H_GRADIENT 600
+
+/**
+ * fb_get_vblank - get vertical blank time
+ * @hfreq: horizontal freq
+ *
+ * DESCRIPTION:
+ * vblank = right_margin + vsync_len + left_margin
+ *
+ * given: right_margin = 1 (V_FRONTPORCH)
+ * vsync_len = 3
+ * flyback = 550
+ *
+ * flyback * hfreq
+ * left_margin = --------------- - vsync_len
+ * 1000000
+ */
+static u32 fb_get_vblank(u32 hfreq)
+{
+ u32 vblank;
+
+ vblank = (hfreq * FLYBACK)/1000;
+ vblank = (vblank + 500)/1000;
+ return (vblank + V_FRONTPORCH);
+}
+
+/**
+ * fb_get_hblank - get horizontal blank time
+ * @hfreq: horizontal freq
+ * @xres: horizontal resolution in pixels
+ *
+ * DESCRIPTION:
+ *
+ * xres * duty_cycle
+ * hblank = ------------------
+ * 100 - duty_cycle
+ *
+ * duty cycle = percent of htotal assigned to inactive display
+ * duty cycle = C - (M/Hfreq)
+ *
+ * where: C = ((offset - scale factor) * blank_scale)
+ * -------------------------------------- + scale factor
+ * 256
+ * M = blank_scale * gradient
+ *
+ */
+static u32 fb_get_hblank(u32 hfreq, u32 xres)
+{
+ u32 c_val, m_val, duty_cycle, hblank;
+
+ c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 +
+ H_SCALEFACTOR) * 1000;
+ m_val = (H_BLANKSCALE * H_GRADIENT)/256;
+ m_val = (m_val * 1000000)/hfreq;
+ duty_cycle = c_val - m_val;
+ hblank = (xres * duty_cycle)/(100000 - duty_cycle);
+ return (hblank);
+}
+
+/**
+ * fb_get_hfreq - estimate hsync
+ * @vfreq: vertical refresh rate
+ * @yres: vertical resolution
+ *
+ * DESCRIPTION:
+ *
+ * (yres + front_port) * vfreq * 1000000
+ * hfreq = -------------------------------------
+ * (1000000 - (vfreq * FLYBACK)
+ *
+ */
+
+static u32 fb_get_hfreq(u32 vfreq, u32 yres)
+{
+ u32 divisor, hfreq;
+
+ divisor = (1000000 - (vfreq * FLYBACK))/1000;
+ hfreq = (yres + V_FRONTPORCH) * vfreq * 1000;
+ return (hfreq/divisor);
+}
+
+/*
+ * fb_get_mode - calculates video mode using VESA GTF
+ * @refresh: if 0, maximize vertical refresh
+ * @var: pointer to fb_var_screeninfo
+ * @info: pointer to fb_info
+ *
+ * DESCRIPTION:
+ * Calculates video mode based on monitor specs using VESA GTF.
+ * The GTF is best for VESA GTF compliant monitors but is
+ * specifically formulated to work for older monitors as well.
+ *
+ * If @refresh==0, the function will attempt to maximize the
+ * refresh rate, otherwise, it will calculate timings based on
+ * this value. However, it's preferable to just clamp
+ * info->monspecs.vfmin/vfmax to desired refresh.
+ *
+ * All calculations are based on the VESA GTF Spreadsheet
+ * available at VESA's public ftp (http://www.vesa.org).
+ *
+ * NOTES:
+ * The timings generated by the GTF will be different from VESA
+ * DMT. It might be a good idea to keep a table of standard
+ * VESA modes as well. The GTF may also not work for some displays,
+ * such as, and especially, analog TV.
+ *
+ * REQUIRES:
+ * A valid info->monspecs, otherwise 'safe numbers' will be used.
+ */
+int fb_get_mode(u32 vrefresh, struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ u32 htotal = 0, vtotal, hfreq, vfreq = 0, hblank, vblank;
+ u32 dclk, interlace = 1, dscan = 1, yres = var->yres, xres = var->xres;
+ u32 hfmin, hfmax, vfmin, vfmax;
+
+ /*
+ * If monspecs are invalid, use values that are enough
+ * for 640x480@60
+ */
+ if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ info->monspecs.hfmax < info->monspecs.hfmin ||
+ info->monspecs.vfmax < info->monspecs.vfmin) {
+ hfmin = 29; hfmax = 30;
+ vfmin = 60; vfmax = 60;
+ } else {
+ hfmin = info->monspecs.hfmin;
+ hfmax = info->monspecs.hfmax;
+ vfmin = info->monspecs.vfmin;
+ vfmax = info->monspecs.vfmax;
+ }
+
+ if (var->vmode & FB_VMODE_INTERLACED) {
+ yres /= 2;
+ interlace = 2;
+ }
+ if (var->vmode & FB_VMODE_DOUBLE) {
+ yres *= 2;
+ dscan = 2;
+ }
+
+ if (vrefresh) {
+ vfreq = vrefresh;
+ hfreq = fb_get_hfreq(vfreq, yres);
+ vblank = fb_get_vblank(hfreq);
+ vtotal = yres + vblank;
+ } else {
+ hfreq = hfmax;
+ vblank = fb_get_vblank(hfreq);
+ vtotal = yres + vblank;
+ vfreq = hfreq/vtotal;
+ if (vfreq > vfmax) {
+ vfreq = vfmax;
+ hfreq = fb_get_hfreq(vfreq, yres);
+ vblank = fb_get_vblank(hfreq);
+ vtotal = yres + vblank;
+ }
+ }
+
+ if (vfreq < vfmin || vfreq > vfmax ||
+ hfreq < hfmin || hfreq > hfmax)
+ return -EINVAL;
+
+ hblank = fb_get_hblank(hfreq, xres);
+ htotal = xres + hblank;
+ dclk = htotal * hfreq;
+
+ var->pixclock = KHZ2PICOS(dclk/1000);
+ var->hsync_len = (htotal * 8)/100;
+ var->right_margin = (hblank/2) - var->hsync_len;
+ var->left_margin = hblank - var->right_margin - var->hsync_len;
+
+ var->vsync_len = (3 * interlace)/dscan;
+ var->lower_margin = (1 * interlace)/dscan;
+ var->upper_margin = (vblank * interlace)/dscan -
+ (var->vsync_len + var->lower_margin);
+
+ return 0;
+}
+
+/*
+ * fb_validate_mode - validates var against monitor capabilities
+ * @var: pointer to fb_var_screeninfo
+ * @info: pointer to fb_info
+ *
+ * DESCRIPTION:
+ * Validates video mode against monitor capabilities specified in
+ * info->monspecs.
+ *
+ * REQUIRES:
+ * A valid info->monspecs.
+ */
+int fb_validate_mode(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ u32 hfreq, vfreq, htotal, vtotal, pixclock;
+ u32 hfmin, hfmax, vfmin, vfmax;
+
+ /*
+ * If monspecs are invalid, use values that are enough
+ * for 640x480@60
+ */
+ if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ info->monspecs.hfmax < info->monspecs.hfmin ||
+ info->monspecs.vfmax < info->monspecs.vfmin) {
+ hfmin = 29; hfmax = 30;
+ vfmin = 60; vfmax = 60;
+ } else {
+ hfmin = info->monspecs.hfmin;
+ hfmax = info->monspecs.hfmax;
+ vfmin = info->monspecs.vfmin;
+ vfmax = info->monspecs.vfmax;
+ }
+
+ if (!var->pixclock)
+ return -EINVAL;
+ pixclock = PICOS2KHZ(var->pixclock) * 1000;
+
+ htotal = var->xres + var->right_margin + var->hsync_len +
+ var->left_margin;
+ vtotal = var->yres + var->lower_margin + var->vsync_len +
+ var->upper_margin;
+
+ if (var->vmode & FB_VMODE_INTERLACED)
+ vtotal /= 2;
+ if (var->vmode & FB_VMODE_DOUBLE)
+ vtotal *= 2;
+
+ hfreq = pixclock/htotal;
+ vfreq = hfreq/vtotal;
+
+ return (vfreq < vfmin || vfreq > vfmax ||
+ hfreq < hfmin || hfreq > hfmax) ?
+ -EINVAL : 0;
+}
+
 EXPORT_SYMBOL(__fb_try_mode);
+EXPORT_SYMBOL(fb_get_mode);
+EXPORT_SYMBOL(fb_validate_mode);
diff -Naur linux-2.5.54/include/linux/fb.h linux/include/linux/fb.h
--- linux-2.5.54/include/linux/fb.h 2003-01-06 13:36:22.000000000 +0000
+++ linux/include/linux/fb.h 2003-01-06 13:35:35.000000000 +0000
@@ -494,6 +494,10 @@
     u32 vmode;
 };
 
+extern int fb_get_mode(u32 refresh, struct fb_var_screeninfo *var,
+ struct fb_info *info);
+extern int fb_validate_mode(struct fb_var_screeninfo *var,
+ struct fb_info *info);
 #ifdef MODULE
 static inline int fb_find_mode(struct fb_var_screeninfo *var,
                                struct fb_info *info, const char *mode_option,

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Tue Jan 07 2003 - 22:00:32 EST