Base EDID only lets you specify the maximum dotclock in tens of MHz, which is too fuzzy for some monitors. 1600x1200@60 is just over 160MHz, but if the monitor really can't handle any mode at 170MHz, then 160 is more correct. Fix up the EDID block before the driver can see it in this case, so we don't spuriously reject modes.
320 lines
7.9 KiB
C
320 lines
7.9 KiB
C
|
|
/* interpret_edid.c: interpret a primary EDID block
|
|
*
|
|
* Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE>
|
|
*/
|
|
#ifdef HAVE_XORG_CONFIG_H
|
|
#include <xorg-config.h>
|
|
#endif
|
|
|
|
#include "misc.h"
|
|
#include "xf86.h"
|
|
#include "xf86_OSproc.h"
|
|
#define _PARSE_EDID_
|
|
#include "xf86DDC.h"
|
|
#include <string.h>
|
|
|
|
static void get_vendor_section(Uchar*, struct vendor *);
|
|
static void get_version_section(Uchar*, struct edid_version *);
|
|
static void get_display_section(Uchar*, struct disp_features *,
|
|
struct edid_version *);
|
|
static void get_established_timing_section(Uchar*, struct established_timings *);
|
|
static void get_std_timing_section(Uchar*, struct std_timings *,
|
|
struct edid_version *);
|
|
static void get_dt_md_section(Uchar *, struct edid_version *,
|
|
struct detailed_monitor_section *det_mon);
|
|
static void copy_string(Uchar *, Uchar *);
|
|
static void get_dst_timing_section(Uchar *, struct std_timings *,
|
|
struct edid_version *);
|
|
static void get_monitor_ranges(Uchar *, struct monitor_ranges *);
|
|
static void get_whitepoint_section(Uchar *, struct whitePoints *);
|
|
static void get_detailed_timing_section(Uchar*, struct detailed_timings *);
|
|
static Bool validate_version(int scrnIndex, struct edid_version *);
|
|
|
|
static void
|
|
handle_edid_quirks(xf86MonPtr m)
|
|
{
|
|
int i, j;
|
|
struct detailed_timings *preferred_timing;
|
|
struct monitor_ranges *ranges;
|
|
|
|
/*
|
|
* max_clock is only encoded in EDID in tens of MHz, so occasionally we
|
|
* find a monitor claiming a max of 160 with a mode requiring 162, or
|
|
* similar. Strictly we should refuse to round up too far, but let's
|
|
* see how well this works.
|
|
*/
|
|
for (i = 0; i < 4; i++) {
|
|
if (m->det_mon[i].type == DS_RANGES) {
|
|
ranges = &m->det_mon[i].section.ranges;
|
|
for (j = 0; j < 4; j++) {
|
|
if (m->det_mon[j].type == DT) {
|
|
preferred_timing = &m->det_mon[j].section.d_timings;
|
|
if (!ranges->max_clock) continue; /* zero is legal */
|
|
if (ranges->max_clock * 1000000 < preferred_timing->clock) {
|
|
xf86Msg(X_WARNING,
|
|
"EDID preferred timing clock %.2fMHz exceeds "
|
|
"claimed max %dMHz, fixing\n",
|
|
preferred_timing->clock / 1.0e6,
|
|
ranges->max_clock);
|
|
ranges->max_clock =
|
|
(preferred_timing->clock+999999)/1000000;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xf86MonPtr
|
|
xf86InterpretEDID(int scrnIndex, Uchar *block)
|
|
{
|
|
xf86MonPtr m;
|
|
|
|
if (!block) return NULL;
|
|
if (! (m = xnfcalloc(sizeof(xf86Monitor),1))) return NULL;
|
|
m->scrnIndex = scrnIndex;
|
|
m->rawData = block;
|
|
|
|
get_vendor_section(SECTION(VENDOR_SECTION,block),&m->vendor);
|
|
get_version_section(SECTION(VERSION_SECTION,block),&m->ver);
|
|
if (!validate_version(scrnIndex, &m->ver)) goto error;
|
|
get_display_section(SECTION(DISPLAY_SECTION,block),&m->features,
|
|
&m->ver);
|
|
get_established_timing_section(SECTION(ESTABLISHED_TIMING_SECTION,block),
|
|
&m->timings1);
|
|
get_std_timing_section(SECTION(STD_TIMING_SECTION,block),m->timings2,
|
|
&m->ver);
|
|
get_dt_md_section(SECTION(DET_TIMING_SECTION,block),&m->ver, m->det_mon);
|
|
m->no_sections = (int)*(char *)SECTION(NO_EDID,block);
|
|
|
|
handle_edid_quirks(m);
|
|
|
|
return (m);
|
|
|
|
error:
|
|
xfree(m);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
get_vendor_section(Uchar *c, struct vendor *r)
|
|
{
|
|
r->name[0] = L1;
|
|
r->name[1] = L2;
|
|
r->name[2] = L3;
|
|
r->name[3] = '\0';
|
|
|
|
r->prod_id = PROD_ID;
|
|
r->serial = SERIAL_NO;
|
|
r->week = WEEK;
|
|
r->year = YEAR;
|
|
}
|
|
|
|
static void
|
|
get_version_section(Uchar *c, struct edid_version *r)
|
|
{
|
|
r->version = VERSION;
|
|
r->revision = REVISION;
|
|
}
|
|
|
|
static void
|
|
get_display_section(Uchar *c, struct disp_features *r,
|
|
struct edid_version *v)
|
|
{
|
|
r->input_type = INPUT_TYPE;
|
|
if (!DIGITAL(r->input_type)) {
|
|
r->input_voltage = INPUT_VOLTAGE;
|
|
r->input_setup = SETUP;
|
|
r->input_sync = SYNC;
|
|
} else if (v->version > 1 || v->revision > 2)
|
|
r->input_dfp = DFP;
|
|
r->hsize = HSIZE_MAX;
|
|
r->vsize = VSIZE_MAX;
|
|
r->gamma = GAMMA;
|
|
r->dpms = DPMS;
|
|
r->display_type = DISPLAY_TYPE;
|
|
r->msc = MSC;
|
|
r->redx = REDX;
|
|
r->redy = REDY;
|
|
r->greenx = GREENX;
|
|
r->greeny = GREENY;
|
|
r->bluex = BLUEX;
|
|
r->bluey = BLUEY;
|
|
r->whitex = WHITEX;
|
|
r->whitey = WHITEY;
|
|
}
|
|
|
|
static void
|
|
get_established_timing_section(Uchar *c, struct established_timings *r)
|
|
{
|
|
r->t1 = T1;
|
|
r->t2 = T2;
|
|
r->t_manu = T_MANU;
|
|
}
|
|
|
|
static void
|
|
get_std_timing_section(Uchar *c, struct std_timings *r,
|
|
struct edid_version *v)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<STD_TIMINGS;i++){
|
|
if (VALID_TIMING) {
|
|
r[i].hsize = HSIZE1;
|
|
VSIZE1(r[i].vsize);
|
|
r[i].refresh = REFRESH_R;
|
|
r[i].id = STD_TIMING_ID;
|
|
} else {
|
|
r[i].hsize = r[i].vsize = r[i].refresh = r[i].id = 0;
|
|
}
|
|
NEXT_STD_TIMING;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_dt_md_section(Uchar *c, struct edid_version *ver,
|
|
struct detailed_monitor_section *det_mon)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<DET_TIMINGS;i++) {
|
|
if (ver->version == 1 && ver->revision >= 1 && IS_MONITOR_DESC) {
|
|
|
|
switch (MONITOR_DESC_TYPE) {
|
|
case SERIAL_NUMBER:
|
|
det_mon[i].type = DS_SERIAL;
|
|
copy_string(c,det_mon[i].section.serial);
|
|
break;
|
|
case ASCII_STR:
|
|
det_mon[i].type = DS_ASCII_STR;
|
|
copy_string(c,det_mon[i].section.ascii_data);
|
|
break;
|
|
case MONITOR_RANGES:
|
|
det_mon[i].type = DS_RANGES;
|
|
get_monitor_ranges(c,&det_mon[i].section.ranges);
|
|
break;
|
|
case MONITOR_NAME:
|
|
det_mon[i].type = DS_NAME;
|
|
copy_string(c,det_mon[i].section.name);
|
|
break;
|
|
case ADD_COLOR_POINT:
|
|
det_mon[i].type = DS_WHITE_P;
|
|
get_whitepoint_section(c,det_mon[i].section.wp);
|
|
break;
|
|
case ADD_STD_TIMINGS:
|
|
det_mon[i].type = DS_STD_TIMINGS;
|
|
get_dst_timing_section(c,det_mon[i].section.std_t, ver);
|
|
break;
|
|
case ADD_DUMMY:
|
|
det_mon[i].type = DS_DUMMY;
|
|
break;
|
|
default:
|
|
det_mon[i].type = DS_UNKOWN;
|
|
break;
|
|
}
|
|
} else {
|
|
det_mon[i].type = DT;
|
|
get_detailed_timing_section(c,&det_mon[i].section.d_timings);
|
|
}
|
|
NEXT_DT_MD_SECTION;
|
|
}
|
|
}
|
|
|
|
static void
|
|
copy_string(Uchar *c, Uchar *s)
|
|
{
|
|
int i;
|
|
c = c + 5;
|
|
for (i = 0; (i < 13 && *c != 0x0A); i++)
|
|
*(s++) = *(c++);
|
|
*s = 0;
|
|
while (i-- && (*--s == 0x20)) *s = 0;
|
|
}
|
|
|
|
static void
|
|
get_dst_timing_section(Uchar *c, struct std_timings *t,
|
|
struct edid_version *v)
|
|
{
|
|
int j;
|
|
c = c + 5;
|
|
for (j = 0; j < 5; j++) {
|
|
t[j].hsize = HSIZE1;
|
|
VSIZE1(t[j].vsize);
|
|
t[j].refresh = REFRESH_R;
|
|
t[j].id = STD_TIMING_ID;
|
|
NEXT_STD_TIMING;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_monitor_ranges(Uchar *c, struct monitor_ranges *r)
|
|
{
|
|
r->min_v = MIN_V;
|
|
r->max_v = MAX_V;
|
|
r->min_h = MIN_H;
|
|
r->max_h = MAX_H;
|
|
r->max_clock = 0;
|
|
if(MAX_CLOCK != 0xff) /* is specified? */
|
|
r->max_clock = MAX_CLOCK * 10;
|
|
if (HAVE_2ND_GTF) {
|
|
r->gtf_2nd_f = F_2ND_GTF;
|
|
r->gtf_2nd_c = C_2ND_GTF;
|
|
r->gtf_2nd_m = M_2ND_GTF;
|
|
r->gtf_2nd_k = K_2ND_GTF;
|
|
r->gtf_2nd_j = J_2ND_GTF;
|
|
} else
|
|
r->gtf_2nd_f = 0;
|
|
}
|
|
|
|
static void
|
|
get_whitepoint_section(Uchar *c, struct whitePoints *wp)
|
|
{
|
|
wp[1].white_x = WHITEX1;
|
|
wp[1].white_y = WHITEY1;
|
|
wp[2].white_x = WHITEX2;
|
|
wp[2].white_y = WHITEY2;
|
|
wp[1].index = WHITE_INDEX1;
|
|
wp[2].index = WHITE_INDEX2;
|
|
wp[1].white_gamma = WHITE_GAMMA1;
|
|
wp[2].white_gamma = WHITE_GAMMA2;
|
|
}
|
|
|
|
static void
|
|
get_detailed_timing_section(Uchar *c, struct detailed_timings *r)
|
|
{
|
|
r->clock = PIXEL_CLOCK;
|
|
r->h_active = H_ACTIVE;
|
|
r->h_blanking = H_BLANK;
|
|
r->v_active = V_ACTIVE;
|
|
r->v_blanking = V_BLANK;
|
|
r->h_sync_off = H_SYNC_OFF;
|
|
r->h_sync_width = H_SYNC_WIDTH;
|
|
r->v_sync_off = V_SYNC_OFF;
|
|
r->v_sync_width = V_SYNC_WIDTH;
|
|
r->h_size = H_SIZE;
|
|
r->v_size = V_SIZE;
|
|
r->h_border = H_BORDER;
|
|
r->v_border = V_BORDER;
|
|
r->interlaced = INTERLACED;
|
|
r->stereo = STEREO;
|
|
r->stereo_1 = STEREO1;
|
|
r->sync = SYNC_T;
|
|
r->misc = MISC;
|
|
}
|
|
|
|
|
|
static Bool
|
|
validate_version(int scrnIndex, struct edid_version *r)
|
|
{
|
|
if (r->version != 1)
|
|
return FALSE;
|
|
if (r->revision > 3) {
|
|
xf86DrvMsg(scrnIndex, X_ERROR,"EDID Version 1.%i not yet supported\n",
|
|
r->revision);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|