2003-01-06 14:58:26

by Antonino A. Daplas

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

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,


2003-01-06 22:18:52

by James Simmons

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size


> 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.

Correct to the above. I applied your patch.

> 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).

Yeap. TIOCSWINSZ is per VC. VT_RESIZE and VT_RESIZEX affect all VCs.
They should work as well.

> 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.

I neved like the idea of fb.modes. We should be asking the hardware are
selves instead.Yes there are cases of really old hardware that lack this.
I think the below code will be usefull for these cases.

> 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.

Great!!!!

> 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)

Yeap. We can parse the EDID block for data about the limits of your
monitor!!!

> 2. entered as a boot/module option

Yuck! But I don't see much of a choose for modular drivers.

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

ioctl already exist for this. The only issue is fb_monspec good enough for
our needs.

> 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?

Nice. The only thing is i like to see monitor stuff end up in fbmon.c. I
will apply your patch.

2003-01-07 04:27:16

by Antonino A. Daplas

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size

On Tue, 2003-01-07 at 06:27, James Simmons wrote:

>
> > 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.
>
> I neved like the idea of fb.modes. We should be asking the hardware are
> selves instead.Yes there are cases of really old hardware that lack this.
> I think the below code will be usefull for these cases.
>
That's true. The VBEInfoBlock struct contains a far pointer to a list
of video modes supported, standard VESA DMT modes and OEM-specific
modes. This is function 0x4F00 of VBE which unfortunately is accessible
only in 16-bit mode. Maybe the EDID block also contains such
information?

Also, if the user wants to control the refresh rate, or use nonstandard
video modes, there's no choice but to have the timings generated even
for new hardware. That's where the GTF is useful.

> > 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.
>
> Great!!!!
>
> > 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)
>
> Yeap. We can parse the EDID block for data about the limits of your
> monitor!!!
>
> > 2. entered as a boot/module option
>
> Yuck! But I don't see much of a choose for modular drivers.

We do need to have the monitor's operational limits uploaded whatever
way and as early as possible so the user can boot to a high resolution
immediately. Using VBE/DDC and parsing the EDID block may not work with
new, but cheap monitors. I have one such monitor that is supposed to
support DDC2 but spits out a useless EDID block. So passing it as a
boot option may be useful. This is similar to XFree86 falling back to
/etc/X11/XF86Config's 'HorizSync' and 'VertRefresh' when the EDID info
is not available.

>
> > 3. ?ioctl to upload monitor info to fbdev.
> >
> > (As a side note, should we also add pixclock_min and pixclock_max to
> > info->monspecs?).
>
> ioctl already exist for this. The only issue is fb_monspec good enough for
> our needs.
>
What's the ioctl by the way?

The GTF only requires xres, yres and one of the three:

horizontal scan rate
vertical refresh rate
pixelclock.

in order to generate timings. So adding minimum and maximum pixelclock
fields in info->monspecs will be useful. Otherwise the GTF may generate
a pixelclock that is outside the graphics card's/monitor's capability.

Secondly, the GTF function assumes the following:

hsync_len = 8% of htotal
left_margin = 1/2 of inactive frame length
right margin = remainder of htotal

vsync_len = 3
lower_margin = 1
upper margin = remainder of vtotal.

Anyway, the most critical part when computing timings information is the
inactive frame length (htotal - xres, vtotal - yres), which is hblank
and vblank in the fb_get_mode() function.

Finally, some of the fixed numbers I used in the GTF function is
supposedly for a monitor with US specifications:

#define FLYBACK 550
#define V_FRONTPORCH 1
#define H_OFFSET 40
#define H_SCALEFACTOR 20
#define H_BLANKSCALE 128
#define H_GRADIENT 600

I'm not even sure about the meaning of some of them :-). We can add them
in the future if the above assumptions need to be changed.

Tony

PS: The GTF patch is erroneous. hfmin and hfmax must be in Hz and the
boolean logic is incorrect.

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:34:50.000000000 +0000
+++ linux/drivers/video/modedb.c 2003-01-07 03:29:59.000000000 +0000
@@ -547,10 +547,10 @@
* If monspecs are invalid, use values that are enough
* for 640x480@60
*/
- if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
info->monspecs.hfmax < info->monspecs.hfmin ||
info->monspecs.vfmax < info->monspecs.vfmin) {
- hfmin = 29; hfmax = 30;
+ hfmin = 29000; hfmax = 30000;
vfmin = 60; vfmax = 60;
} else {
hfmin = info->monspecs.hfmin;
@@ -628,10 +628,10 @@
* If monspecs are invalid, use values that are enough
* for 640x480@60
*/
- if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
info->monspecs.hfmax < info->monspecs.hfmin ||
info->monspecs.vfmax < info->monspecs.vfmin) {
- hfmin = 29; hfmax = 30;
+ hfmin = 29000; hfmax = 30000;
vfmin = 60; vfmax = 60;
} else {
hfmin = info->monspecs.hfmin;

2003-01-08 05:09:05

by Antonino A. Daplas

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size

Hi James,

Here's an improved GTF implementation. I was a bit delayed because I
was trying to find a way to do square roots without using floating
point. The diff is against linux-2.5.54 + your latest fbdev.diff.

static int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo
*var, struct fb_info *info);

if flags == 0: maximize timings
1: vrefresh rate driven calculation
2. hscan rate driven calculation
3. pixelclock driven calculation.

The parameter 'val' depends on the parameter 'flags': ignored if flags
== 0, in Hz if 1 and 2, in picoseconds if 3.

The flags are useful for cases such as a fixed-frequency monitor (pass
flags = 2) or for hardware which only have several sets of dotclocks to
use. In this case, it will run the GTF first, get the resulting
pixelclock to select a dotclock from the driver's own set, then rerun
the GTF using flags=3, and val = selected dotclock.

Probably still has precision errors, like converting KHZ to picoseconds,
but is more or less usable. Tested with i810fb and rivafb. (For rivafb,
I have to use a hacked version. The latest one does not work for the
riva128).

BTW, I downloaded the source code of read-edid, and it seems that the
following monitor limits are parsable from the EDID block: HorizSync,
VertRefresh, DotClock, and GTF capability. We may change info->monspecs
to match that. Also, the EDID contains a list of supported modes, but
there's only 4 of them(?).

Tony

diff -Naur linux-2.5.54/drivers/video/fbmon.c linux/drivers/video/fbmon.c
--- linux-2.5.54/drivers/video/fbmon.c 2003-01-08 04:19:48.000000000 +0000
+++ linux/drivers/video/fbmon.c 2003-01-08 04:18:11.000000000 +0000
@@ -289,7 +289,376 @@
}
#endif

+/*
+ * VESA Generalized Timing Formula (GTF)
+ */
+
+#define FLYBACK 550
+#define V_FRONTPORCH 1
+#define H_OFFSET 40
+#define H_SCALEFACTOR 20
+#define H_BLANKSCALE 128
+#define H_GRADIENT 600
+#define C_VAL 30
+#define M_VAL 300
+
+struct __fb_timings {
+ u32 dclk;
+ u32 hfreq;
+ u32 vfreq;
+ u32 hactive;
+ u32 vactive;
+ u32 hblank;
+ u32 vblank;
+ u32 htotal;
+ u32 vtotal;
+};
+
+/*
+ * a simple function to get the square root of integers
+ */
+static u32 fb_sqrt(int x)
+{
+ register int op, res, one;
+
+ op = x;
+ res = 0;
+
+ one = 1 << 30;
+ while (one > op) one >>= 2;
+
+ while (one != 0) {
+ if (op >= res + one) {
+ op = op - (res + one);
+ res = res + 2 * one;
+ }
+ res /= 2;
+ one /= 4;
+ }
+ return((u32) res);
+}
+
+/**
+ * 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_by_freq - get horizontal blank time given hfreq
+ * @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_by_hfreq(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_hblank_by_dclk - get horizontal blank time given pixelclock
+ * @dclk: pixelclock in Hz
+ * @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 * h_period)
+ *
+ * where: h_period = SQRT(100 - C + (0.4 * xres * M)/dclk) + C - 100
+ * -----------------------------------------------
+ * 2 * M
+ * M = 300;
+ * C = 30;
+
+ */
+static u32 fb_get_hblank_by_dclk(u32 dclk, u32 xres)
+{
+ u32 duty_cycle, h_period, hblank;;
+
+ dclk /= 1000;
+ h_period = 100 - C_VAL;
+ h_period *= h_period;
+ h_period += (M_VAL * xres * 2 * 1000)/(5 * dclk);
+ h_period *=10000;
+
+ h_period = fb_sqrt((int) h_period);
+ h_period -= (100 - C_VAL) * 100;
+ h_period *= 1000;
+ h_period /= 2 * M_VAL;
+
+ duty_cycle = C_VAL * 1000 - (M_VAL * h_period)/100;
+ hblank = (xres * duty_cycle)/(100000 - duty_cycle) + 8;
+ hblank &= ~15;
+ 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);
+}
+
+static void fb_timings_vfreq(struct __fb_timings *timings)
+{
+ timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive);
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->dclk = timings->htotal * timings->hfreq;
+}
+
+static void fb_timings_hfreq(struct __fb_timings *timings)
+{
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->vfreq = timings->hfreq/timings->vtotal;
+ timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->dclk = timings->htotal * timings->hfreq;
+}
+
+static void fb_timings_dclk(struct __fb_timings *timings)
+{
+ timings->hblank = fb_get_hblank_by_dclk(timings->dclk,
+ timings->hactive);
+ timings->htotal = timings->hactive + timings->hblank;
+ timings->hfreq = timings->dclk/timings->htotal;
+ timings->vblank = fb_get_vblank(timings->hfreq);
+ timings->vtotal = timings->vactive + timings->vblank;
+ timings->vfreq = timings->hfreq/timings->vtotal;
+}
+
+/*
+ * fb_get_mode - calculates video mode using VESA GTF
+ * @flags: if: 0 - maximize vertical refresh rate
+ * 1 - vrefresh-driven calculation;
+ * 2 - hscan-driven calculation;
+ * 3 - pixelclock-driven calculation;
+ * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock
+ * @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 @flag==0, the function will attempt to maximize the
+ * refresh rate. Otherwise, it will calculate timings based on
+ * the flag and accompanying value.
+ *
+ * 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(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct __fb_timings timings;
+ u32 interlace = 1, dscan = 1;
+ 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 = 29000; hfmax = 30000;
+ vfmin = 60; vfmax = 60;
+ } else {
+ hfmin = info->monspecs.hfmin;
+ hfmax = info->monspecs.hfmax;
+ vfmin = info->monspecs.vfmin;
+ vfmax = info->monspecs.vfmax;
+ }
+
+ memset(&timings, 0, sizeof(struct __fb_timings));
+ timings.hactive = var->xres;
+ timings.vactive = var->yres;
+ if (var->vmode & FB_VMODE_INTERLACED) {
+ timings.vactive /= 2;
+ interlace = 2;
+ }
+ if (var->vmode & FB_VMODE_DOUBLE) {
+ timings.vactive *= 2;
+ dscan = 2;
+ }
+
+ switch (flags) {
+ case 0: /* maximize refresh rate */
+ timings.hfreq = hfmax;
+ fb_timings_hfreq(&timings);
+ if (timings.vfreq > vfmax) {
+ timings.vfreq = vfmax;
+ fb_timings_vfreq(&timings);
+ }
+ break;
+ case 1: /* vrefresh driven */
+ timings.vfreq = val;
+ fb_timings_vfreq(&timings);
+ break;
+ case 2: /* hsync driven */
+ timings.hfreq = val;
+ fb_timings_hfreq(&timings);
+ break;
+ case 3: /* pixelclock driven */
+ timings.dclk = PICOS2KHZ(val) * 1000;
+ fb_timings_dclk(&timings);
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ if (timings.vfreq < vfmin || timings.vfreq > vfmax ||
+ timings.hfreq < hfmin || timings.hfreq > hfmax)
+ return -EINVAL;
+
+
+ var->pixclock = KHZ2PICOS(timings.dclk/1000);
+ var->hsync_len = (timings.htotal * 8)/100;
+ var->right_margin = (timings.hblank/2) - var->hsync_len;
+ var->left_margin = timings.hblank - var->right_margin - var->hsync_len;
+
+ var->vsync_len = (3 * interlace)/dscan;
+ var->lower_margin = (1 * interlace)/dscan;
+ var->upper_margin = (timings.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 = 29000; hfmax = 30000;
+ 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(parse_edid);
#ifdef CONFIG_PCI
EXPORT_SYMBOL(get_EDID);
#endif
+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-08 04:20:07.000000000 +0000
+++ linux/include/linux/fb.h 2003-01-08 04:19:26.000000000 +0000
@@ -468,6 +468,10 @@
extern int fbmon_valid_timings(u_int pixclock, u_int htotal, u_int vtotal,
const struct fb_info *fb_info);
extern int fbmon_dpms(const struct fb_info *fb_info);
+extern int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var,
+ struct fb_info *info);
+extern int fb_validate_mode(struct fb_var_screeninfo *var,
+ struct fb_info *info);

/* drivers/video/fbcmap.c */
extern int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp);

2003-01-10 15:40:49

by Antonino A. Daplas

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size

On Mon, 2003-01-06 at 22:54, Antonino Daplas wrote:
> 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.
>

James,

Attached is a diff against linux-2.5.54 + your latest fbdev.diff.
Hopefully it's the final installment for the GTF implementation.

The fb_get_mode() function checks for a bit in 'flags' (FB_IGNOREMON) so
it will generate GTF timings regardless of the validity of
info->monspecs. This way, drivers can still use GTF even if they don't
have the operational limits of the monitor. They'll just decide what is
a practical safe limit.

This particular code snippet can be inserted in xxxfb_check_var() after
rounding off var->xres and var->yres.

...
if (fb_validate_mode(var, info)) {
if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
!info->monspecs.dclkmax ||
info->monspecs.hfmax < info->monspecs.hfmin ||
info->monspecs.vfmax < info->monspecs.vfmin ||
info->monspecs.dclkmax < info->monspecs.dclkmin)
fb_get_mode(FB_IGNOREMON | FB_VSYNCTIMINGS, 60, var, info);
else
if (fb_get_mode(FB_MAXTIMINGS, 0, var, info))
return -EINVAL;
}
...

With the above, the driver will switch to a 60Hz refresh rate timings
(safe even for low-end monitors up to 1024x768) if monspecs is invalid.

Other changes: added a few more fields to fb_info.monspecs.

Tony

diff -Naur linux-2.5.54/drivers/video/fbmon.c linux/drivers/video/fbmon.c
--- linux-2.5.54/drivers/video/fbmon.c 2003-01-10 15:17:22.000000000 +0000
+++ linux/drivers/video/fbmon.c 2003-01-10 15:10:44.000000000 +0000
@@ -511,6 +511,9 @@
* refresh rate. Otherwise, it will calculate timings based on
* the flag and accompanying value.
*
+ * If FB_IGNOREMON bit is set in @flags, monitor specs will be
+ * ignored and @var will be filled with the calculated timings.
+ *
* All calculations are based on the VESA GTF Spreadsheet
* available at VESA's public ftp (http://www.vesa.org).
*
@@ -527,22 +530,27 @@
{
struct __fb_timings timings;
u32 interlace = 1, dscan = 1;
- u32 hfmin, hfmax, vfmin, vfmax;
+ u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax;

/*
* If monspecs are invalid, use values that are enough
* for 640x480@60
*/
- if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
+ !info->monspecs.dclkmax ||
info->monspecs.hfmax < info->monspecs.hfmin ||
- info->monspecs.vfmax < info->monspecs.vfmin) {
+ info->monspecs.vfmax < info->monspecs.vfmin ||
+ info->monspecs.dclkmax < info->monspecs.dclkmin) {
hfmin = 29000; hfmax = 30000;
vfmin = 60; vfmax = 60;
+ dclkmin = 0; dclkmax = 25000000;
} else {
hfmin = info->monspecs.hfmin;
hfmax = info->monspecs.hfmax;
vfmin = info->monspecs.vfmin;
vfmax = info->monspecs.vfmax;
+ dclkmin = info->monspecs.dclkmin;
+ dclkmax = info->monspecs.dclkmax;
}

memset(&timings, 0, sizeof(struct __fb_timings));
@@ -557,8 +565,8 @@
dscan = 2;
}

- switch (flags) {
- case 0: /* maximize refresh rate */
+ switch (flags & ~FB_IGNOREMON) {
+ case FB_MAXTIMINGS: /* maximize refresh rate */
timings.hfreq = hfmax;
fb_timings_hfreq(&timings);
if (timings.vfreq > vfmax) {
@@ -566,15 +574,15 @@
fb_timings_vfreq(&timings);
}
break;
- case 1: /* vrefresh driven */
+ case FB_VSYNCTIMINGS: /* vrefresh driven */
timings.vfreq = val;
fb_timings_vfreq(&timings);
break;
- case 2: /* hsync driven */
+ case FB_HSYNCTIMINGS: /* hsync driven */
timings.hfreq = val;
fb_timings_hfreq(&timings);
break;
- case 3: /* pixelclock driven */
+ case FB_DCLKTIMINGS: /* pixelclock driven */
timings.dclk = PICOS2KHZ(val) * 1000;
fb_timings_dclk(&timings);
break;
@@ -583,11 +591,12 @@

}

- if (timings.vfreq < vfmin || timings.vfreq > vfmax ||
- timings.hfreq < hfmin || timings.hfreq > hfmax)
+ if (!(flags & FB_IGNOREMON) &&
+ (timings.vfreq < vfmin || timings.vfreq > vfmax ||
+ timings.hfreq < hfmin || timings.hfreq > hfmax ||
+ timings.dclk < dclkmin || timings.dclk > dclkmax))
return -EINVAL;

-
var->pixclock = KHZ2PICOS(timings.dclk/1000);
var->hsync_len = (timings.htotal * 8)/100;
var->right_margin = (timings.hblank/2) - var->hsync_len;
@@ -616,22 +625,27 @@
int fb_validate_mode(struct fb_var_screeninfo *var, struct fb_info *info)
{
u32 hfreq, vfreq, htotal, vtotal, pixclock;
- u32 hfmin, hfmax, vfmin, vfmax;
+ u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax;

/*
* If monspecs are invalid, use values that are enough
* for 640x480@60
*/
- if ((!info->monspecs.hfmax && !info->monspecs.vfmax) ||
+ if (!info->monspecs.hfmax || !info->monspecs.vfmax ||
+ !info->monspecs.dclkmax ||
info->monspecs.hfmax < info->monspecs.hfmin ||
- info->monspecs.vfmax < info->monspecs.vfmin) {
+ info->monspecs.vfmax < info->monspecs.vfmin ||
+ info->monspecs.dclkmax < info->monspecs.dclkmin) {
hfmin = 29000; hfmax = 30000;
vfmin = 60; vfmax = 60;
+ dclkmin = 0; dclkmax = 25000000;
} else {
hfmin = info->monspecs.hfmin;
hfmax = info->monspecs.hfmax;
vfmin = info->monspecs.vfmin;
vfmax = info->monspecs.vfmax;
+ dclkmin = info->monspecs.dclkmin;
+ dclkmax = info->monspecs.dclkmax;
}

if (!var->pixclock)
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-10 15:17:37.000000000 +0000
+++ linux/include/linux/fb.h 2003-01-10 15:19:56.000000000 +0000
@@ -240,6 +240,9 @@
__u32 hfmax; /* hfreq upper limit (Hz) */
__u16 vfmin; /* vfreq lower limit (Hz) */
__u16 vfmax; /* vfreq upper limit (Hz) */
+ __u32 dclkmin; /* pixelclock lower limit (Hz) */
+ __u32 dclkmax; /* pixelclock upper limit (Hz) */
+ unsigned gtf : 1; /* supports GTF */
unsigned dpms : 1; /* supports DPMS */
};

@@ -465,6 +468,12 @@
extern int num_registered_fb;

/* drivers/video/fbmon.c */
+#define FB_MAXTIMINGS 0
+#define FB_VSYNCTIMINGS 1
+#define FB_HSYNCTIMINGS 2
+#define FB_DCLKTIMINGS 3
+#define FB_IGNOREMON 0x100
+
extern int fbmon_valid_timings(u_int pixclock, u_int htotal, u_int vtotal,
const struct fb_info *fb_info);
extern int fbmon_dpms(const struct fb_info *fb_info);

2003-01-10 18:09:46

by James Simmons

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size


> Here's an improved GTF implementation. I was a bit delayed because I
> was trying to find a way to do square roots without using floating
> point. The diff is against linux-2.5.54 + your latest fbdev.diff.

Applied.

> but is more or less usable. Tested with i810fb and rivafb. (For rivafb,
> I have to use a hacked version. The latest one does not work for the
> riva128).

What hack did you do? That is based on the latest riva driver from 2.4.2X.

> BTW, I downloaded the source code of read-edid, and it seems that the
> following monitor limits are parsable from the EDID block: HorizSync,
> VertRefresh, DotClock, and GTF capability. We may change info->monspecs
> to match that. Also, the EDID contains a list of supported modes, but
> there's only 4 of them(?).

I figured monspec would have to be improved. I'm looking into the EDID
info right now. I'm also looking at read-edid. Next I need to figure out
how to use i2c to get this info.



2003-01-10 23:51:31

by James Simmons

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size


> Hopefully it's the final installment for the GTF implementation.
>
> The fb_get_mode() function checks for a bit in 'flags' (FB_IGNOREMON) so
> it will generate GTF timings regardless of the validity of
> info->monspecs. This way, drivers can still use GTF even if they don't
> have the operational limits of the monitor. They'll just decide what is
> a practical safe limit.

Applied.

2003-01-11 05:12:37

by Antonino A. Daplas

[permalink] [raw]
Subject: Re: [Linux-fbdev-devel] [RFC][PATCH][FBDEV]: Setting fbcon's windows size

On Sat, 2003-01-11 at 02:18, James Simmons wrote:
>
> > Here's an improved GTF implementation. I was a bit delayed because I
> > was trying to find a way to do square roots without using floating
> > point. The diff is against linux-2.5.54 + your latest fbdev.diff.
>
> Applied.
>
> > but is more or less usable. Tested with i810fb and rivafb. (For rivafb,
> > I have to use a hacked version. The latest one does not work for the
> > riva128).
>
> What hack did you do? That is based on the latest riva driver from 2.4.2X.
>
I just combined the old riva_hw.c in linux-2.5.52 with the new fbdev.c
code. Not too sure why, either the newest riva_hw.c has dropped support
for old hardware, or we are using it incorrectly. If I'm to guess, the
new riva_hw.c did not come from linux-2.4.20, but probably from Xfree86?

> > BTW, I downloaded the source code of read-edid, and it seems that the
> > following monitor limits are parsable from the EDID block: HorizSync,
> > VertRefresh, DotClock, and GTF capability. We may change info->monspecs
> > to match that. Also, the EDID contains a list of supported modes, but
> > there's only 4 of them(?).
>
> I figured monspec would have to be improved. I'm looking into the EDID
> info right now. I'm also looking at read-edid. Next I need to figure out
> how to use i2c to get this info.
>
Good luck :-)

Tony