diff options
Diffstat (limited to 'xorg-server/hw/xfree86/ddc/interpret_edid.c')
-rw-r--r-- | xorg-server/hw/xfree86/ddc/interpret_edid.c | 1399 |
1 files changed, 714 insertions, 685 deletions
diff --git a/xorg-server/hw/xfree86/ddc/interpret_edid.c b/xorg-server/hw/xfree86/ddc/interpret_edid.c index d1001a21f..882a6b201 100644 --- a/xorg-server/hw/xfree86/ddc/interpret_edid.c +++ b/xorg-server/hw/xfree86/ddc/interpret_edid.c @@ -1,685 +1,714 @@ -/*
- * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE>
- * Copyright 2007 Red Hat, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *
- * interpret_edid.c: interpret a primary EDID block
- */
-
-#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 fetch_detailed_block(Uchar *c, struct edid_version *ver,
- struct detailed_monitor_section *det_mon);
-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
-find_ranges_section(struct detailed_monitor_section *det, void *ranges)
-{
- if (det->type == DS_RANGES && det->section.ranges.max_clock)
- *(struct monitor_ranges **)ranges = &det->section.ranges;
-}
-
-static void
-find_max_detailed_clock(struct detailed_monitor_section *det, void *ret)
-{
- if (det->type == DT) {
- *(int *)ret = max(*((int *)ret),
- det->section.d_timings.clock);
- }
-}
-
-static void
-handle_edid_quirks(xf86MonPtr m)
-{
- struct monitor_ranges *ranges = NULL;
-
- /*
- * 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.
- */
-
- /* Try to find Monitor Range and max clock, then re-set range value*/
- xf86ForEachDetailedBlock(m, find_ranges_section, &ranges);
- if (ranges && ranges->max_clock) {
- int clock = 0;
- xf86ForEachDetailedBlock(m, find_max_detailed_clock, &clock);
- if (clock && (ranges->max_clock * 1e6 < clock)) {
- xf86Msg(X_WARNING, "EDID timing clock %.2f exceeds claimed max "
- "%dMHz, fixing\n", clock / 1.0e6, ranges->max_clock);
- ranges->max_clock = (clock+999999)/1e6;
- }
- }
-}
-
-struct det_hv_parameter {
- int real_hsize;
- int real_vsize;
- float target_aspect;
-};
-
-static void handle_detailed_hvsize(struct detailed_monitor_section *det_mon,
- void *data)
-{
- struct det_hv_parameter *p = (struct det_hv_parameter *)data;
- float timing_aspect;
-
- if (det_mon->type == DT) {
- struct detailed_timings *timing;
- timing = &det_mon->section.d_timings;
-
- if (!timing->v_size)
- return;
-
- timing_aspect = (float)timing->h_size / timing->v_size;
- if (fabs(1 - (timing_aspect / p->target_aspect)) < 0.05) {
- p->real_hsize = max(p->real_hsize, timing->h_size);
- p->real_vsize = max(p->real_vsize, timing->v_size);
- }
- }
-}
-
-static void encode_aspect_ratio(xf86MonPtr m)
-{
- /*
- * some monitors encode the aspect ratio instead of the physical size.
- * try to find the largest detailed timing that matches that aspect
- * ratio and use that to fill in the feature section.
- */
- if ((m->features.hsize == 16 && m->features.vsize == 9) ||
- (m->features.hsize == 16 && m->features.vsize == 10) ||
- (m->features.hsize == 4 && m->features.vsize == 3) ||
- (m->features.hsize == 5 && m->features.vsize == 4)) {
-
- struct det_hv_parameter p;
- p.real_hsize = 0;
- p.real_vsize = 0;
- p.target_aspect = (float)m->features.hsize /m->features.vsize;
-
- xf86ForEachDetailedBlock(m, handle_detailed_hvsize, &p);
-
- if (!p.real_hsize || !p.real_vsize) {
- m->features.hsize = m->features.vsize = 0;
- } else if ((m->features.hsize * 10 == p.real_hsize) &&
- (m->features.vsize * 10 == p.real_vsize)) {
- /* exact match is just unlikely, should do a better check though */
- m->features.hsize = m->features.vsize = 0;
- } else {
- /* convert mm to cm */
- m->features.hsize = (p.real_hsize + 5) / 10;
- m->features.vsize = (p.real_vsize + 5) / 10;
- }
-
- xf86Msg(X_INFO, "Quirked EDID physical size to %dx%d cm\n",
- m->features.hsize, m->features.vsize);
- }
-}
-
-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);
- encode_aspect_ratio(m);
-
- return m;
-
- error:
- free(m);
- return NULL;
-}
-
-static int get_cea_detail_timing(Uchar *blk, xf86MonPtr mon,
- struct detailed_monitor_section *det_mon)
-{
- int dt_num;
- int dt_offset = ((struct cea_ext_body *)blk)->dt_offset;
-
- dt_num = 0;
-
- if (dt_offset < CEA_EXT_MIN_DATA_OFFSET)
- return dt_num;
-
- for (; dt_offset < (CEA_EXT_MAX_DATA_OFFSET - DET_TIMING_INFO_LEN) &&
- dt_num < CEA_EXT_DET_TIMING_NUM;
- _NEXT_DT_MD_SECTION(dt_offset)) {
-
- fetch_detailed_block(blk + dt_offset, &mon->ver, det_mon + dt_num);
- dt_num = dt_num + 1 ;
- }
-
- return dt_num;
-}
-
-static void handle_cea_detail_block(Uchar *ext, xf86MonPtr mon,
- handle_detailed_fn fn,
- void *data)
-{
- int i;
- struct detailed_monitor_section det_mon[CEA_EXT_DET_TIMING_NUM];
- int det_mon_num;
-
- det_mon_num = get_cea_detail_timing(ext, mon, det_mon);
-
- for (i = 0; i < det_mon_num; i++)
- fn(det_mon + i, data);
-}
-
-void xf86ForEachDetailedBlock(xf86MonPtr mon,
- handle_detailed_fn fn,
- void *data)
-{
- int i;
- Uchar *ext;
-
- if (mon == NULL)
- return;
-
- for (i = 0; i < DET_TIMINGS; i++)
- fn(mon->det_mon + i, data);
-
- for (i = 0; i < mon->no_sections; i++) {
- ext = mon->rawData + EDID1_LEN * (i + 1);
- switch (ext[EXT_TAG]){
- case CEA_EXT:
- handle_cea_detail_block(ext, mon, fn, data);
- break;
- case VTB_EXT:
- case DI_EXT:
- case LS_EXT:
- case MI_EXT:
- break;
- }
- }
-}
-
-static struct cea_data_block *
-extract_cea_data_block(Uchar *ext, int data_type)
-{
- struct cea_ext_body *cea;
- struct cea_data_block *data_collection;
- struct cea_data_block *data_end;
-
- cea = (struct cea_ext_body *)ext;
-
- if (cea->dt_offset <= CEA_EXT_MIN_DATA_OFFSET)
- return NULL;
-
- data_collection = &cea->data_collection;
- data_end = (struct cea_data_block *)(cea->dt_offset + ext);
-
- for ( ;data_collection < data_end;) {
-
- if (data_type == data_collection->tag) {
- return data_collection;
- }
- data_collection = (void *)((unsigned char *)data_collection +
- data_collection->len + 1);
- }
-
- return NULL;
-}
-
-static void handle_cea_video_block(Uchar *ext, handle_video_fn fn, void *data)
-{
- struct cea_video_block *video;
- struct cea_video_block *video_end;
- struct cea_data_block *data_collection;
-
- data_collection = extract_cea_data_block(ext, CEA_VIDEO_BLK);
- if (data_collection == NULL)
- return;
-
- video = &data_collection->u.video;
- video_end = (struct cea_video_block *)
- ((Uchar *)video + data_collection->len);
-
- for (; video < video_end; video = video + 1) {
- fn(video, data);
- }
-}
-
-void xf86ForEachVideoBlock(xf86MonPtr mon,
- handle_video_fn fn,
- void *data)
-{
- int i;
- Uchar *ext;
-
- if (mon == NULL)
- return;
-
- for (i = 0; i < mon->no_sections; i++) {
- ext = mon->rawData + EDID1_LEN * (i + 1);
- switch (ext[EXT_TAG]) {
- case CEA_EXT:
- handle_cea_video_block(ext, fn, data);
- break;
- case VTB_EXT:
- case DI_EXT:
- case LS_EXT:
- case MI_EXT:
- break;
- }
- }
-}
-
-xf86MonPtr
-xf86InterpretEEDID(int scrnIndex, Uchar *block)
-{
- xf86MonPtr m;
-
- m = xf86InterpretEDID(scrnIndex, block);
- if (!m)
- return NULL;
-
- /* extension parse */
-
- return m;
-}
-
-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->revision == 2 || v->revision == 3) {
- r->input_dfp = DFP;
- } else if (v->revision >= 4) {
- r->input_bpc = BPC;
- r->input_interface = DIGITAL_INTERFACE;
- }
- 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_cvt_timing_section(Uchar *c, struct cvt_timings *r)
-{
- int i;
-
- for (i = 0; i < 4; i++) {
- if (c[0] && c[1] && c[2]) {
- r[i].height = (c[0] + ((c[1] & 0xF0) << 8) + 1) * 2;
- switch (c[1] & 0xc0) {
- case 0x00: r[i].width = r[i].height * 4 / 3; break;
- case 0x40: r[i].width = r[i].height * 16 / 9; break;
- case 0x80: r[i].width = r[i].height * 16 / 10; break;
- case 0xc0: r[i].width = r[i].height * 15 / 9; break;
- }
- switch (c[2] & 0x60) {
- case 0x00: r[i].rate = 50; break;
- case 0x20: r[i].rate = 60; break;
- case 0x40: r[i].rate = 75; break;
- case 0x60: r[i].rate = 85; break;
- }
- r[i].rates = c[2] & 0x1f;
- } else {
- return;
- }
- c += 3;
- }
-}
-
-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 const unsigned char empty_block[18];
-
-static void
-fetch_detailed_block(Uchar *c, struct edid_version *ver,
- struct detailed_monitor_section *det_mon)
-{
- if (ver->version == 1 && ver->revision >= 1 && IS_MONITOR_DESC) {
- switch (MONITOR_DESC_TYPE) {
- case SERIAL_NUMBER:
- det_mon->type = DS_SERIAL;
- copy_string(c,det_mon->section.serial);
- break;
- case ASCII_STR:
- det_mon->type = DS_ASCII_STR;
- copy_string(c,det_mon->section.ascii_data);
- break;
- case MONITOR_RANGES:
- det_mon->type = DS_RANGES;
- get_monitor_ranges(c,&det_mon->section.ranges);
- break;
- case MONITOR_NAME:
- det_mon->type = DS_NAME;
- copy_string(c,det_mon->section.name);
- break;
- case ADD_COLOR_POINT:
- det_mon->type = DS_WHITE_P;
- get_whitepoint_section(c,det_mon->section.wp);
- break;
- case ADD_STD_TIMINGS:
- det_mon->type = DS_STD_TIMINGS;
- get_dst_timing_section(c,det_mon->section.std_t, ver);
- break;
- case COLOR_MANAGEMENT_DATA:
- det_mon->type = DS_CMD;
- break;
- case CVT_3BYTE_DATA:
- det_mon->type = DS_CVT;
- get_cvt_timing_section(c, det_mon->section.cvt);
- break;
- case ADD_EST_TIMINGS:
- det_mon->type = DS_EST_III;
- memcpy(det_mon->section.est_iii, c + 6, 6);
- break;
- case ADD_DUMMY:
- det_mon->type = DS_DUMMY;
- break;
- default:
- det_mon->type = DS_UNKOWN;
- break;
- }
- if (c[3] <= 0x0F && memcmp(c, empty_block, sizeof(empty_block))) {
- det_mon->type = DS_VENDOR + c[3];
- }
- } else {
- det_mon->type = DT;
- get_detailed_timing_section(c, &det_mon->section.d_timings);
- }
-}
-
-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++) {
- fetch_detailed_block(c, ver, det_mon + i);
- 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 + 5;
- 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;
- }
- if (HAVE_CVT) {
- r->max_clock_khz = MAX_CLOCK_KHZ;
- r->max_clock = r->max_clock_khz / 1000;
- r->maxwidth = MAXWIDTH;
- r->supported_aspect = SUPPORTED_ASPECT;
- r->preferred_aspect = PREFERRED_ASPECT;
- r->supported_blanking = SUPPORTED_BLANKING;
- r->supported_scaling = SUPPORTED_SCALING;
- r->preferred_refresh = PREFERRED_REFRESH;
- } else {
- r->max_clock_khz = 0;
- }
-}
-
-static void
-get_whitepoint_section(Uchar *c, struct whitePoints *wp)
-{
- wp[0].white_x = WHITEX1;
- wp[0].white_y = WHITEY1;
- wp[1].white_x = WHITEX2;
- wp[1].white_y = WHITEY2;
- wp[0].index = WHITE_INDEX1;
- wp[1].index = WHITE_INDEX2;
- wp[0].white_gamma = WHITE_GAMMA1;
- wp[1].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;
-}
-
-#define MAX_EDID_MINOR 4
-
-static Bool
-validate_version(int scrnIndex, struct edid_version *r)
-{
- if (r->version != 1) {
- xf86DrvMsg(scrnIndex, X_ERROR, "Unknown EDID version %d\n",
- r->version);
- return FALSE;
- }
-
- if (r->revision > MAX_EDID_MINOR)
- xf86DrvMsg(scrnIndex, X_WARNING,
- "Assuming version 1.%d is compatible with 1.%d\n",
- r->revision, MAX_EDID_MINOR);
-
- return TRUE;
-}
-
-/*
- * Returns true if HDMI, false if definitely not or unknown.
- */
-Bool
-xf86MonitorIsHDMI(xf86MonPtr mon)
-{
- int i = 0, version, offset;
- char *edid = NULL;
-
- if (!mon)
- return FALSE;
-
- if (!(mon->flags & EDID_COMPLETE_RAWDATA))
- return FALSE;
-
- if (!mon->no_sections)
- return FALSE;
-
- edid = (char *)mon->rawData;
- if (!edid)
- return FALSE;
-
- /* find the CEA extension block */
- for (i = 1; i <= mon->no_sections; i++)
- if (edid[i * 128] == 0x02)
- break;
- if (i == mon->no_sections + 1)
- return FALSE;
- edid += (i * 128);
-
- version = edid[1];
- offset = edid[2];
- if (version < 3 || offset < 4)
- return FALSE;
-
- /* walk the cea data blocks */
- for (i = 4; i < offset; i += (edid[i] & 0x1f) + 1) {
- char *x = edid + i;
-
- /* find a vendor specific block */
- if ((x[0] & 0xe0) >> 5 == 0x03) {
- int oui = (x[3] << 16) + (x[2] << 8) + x[1];
-
- /* find the HDMI vendor OUI */
- if (oui == 0x000c03)
- return TRUE;
- }
- }
-
- /* guess it's not HDMI after all */
- return FALSE;
-}
+/* + * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> + * Copyright 2007 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * interpret_edid.c: interpret a primary EDID block + */ + +#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 fetch_detailed_block(Uchar * c, struct edid_version *ver, + struct detailed_monitor_section *det_mon); +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 +find_ranges_section(struct detailed_monitor_section *det, void *ranges) +{ + if (det->type == DS_RANGES && det->section.ranges.max_clock) + *(struct monitor_ranges **) ranges = &det->section.ranges; +} + +static void +find_max_detailed_clock(struct detailed_monitor_section *det, void *ret) +{ + if (det->type == DT) { + *(int *) ret = max(*((int *) ret), det->section.d_timings.clock); + } +} + +static void +handle_edid_quirks(xf86MonPtr m) +{ + struct monitor_ranges *ranges = NULL; + + /* + * 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. + */ + + /* Try to find Monitor Range and max clock, then re-set range value */ + xf86ForEachDetailedBlock(m, find_ranges_section, &ranges); + if (ranges && ranges->max_clock) { + int clock = 0; + + xf86ForEachDetailedBlock(m, find_max_detailed_clock, &clock); + if (clock && (ranges->max_clock * 1e6 < clock)) { + xf86Msg(X_WARNING, "EDID timing clock %.2f exceeds claimed max " + "%dMHz, fixing\n", clock / 1.0e6, ranges->max_clock); + ranges->max_clock = (clock + 999999) / 1e6; + } + } +} + +struct det_hv_parameter { + int real_hsize; + int real_vsize; + float target_aspect; +}; + +static void +handle_detailed_hvsize(struct detailed_monitor_section *det_mon, void *data) +{ + struct det_hv_parameter *p = (struct det_hv_parameter *) data; + float timing_aspect; + + if (det_mon->type == DT) { + struct detailed_timings *timing; + + timing = &det_mon->section.d_timings; + + if (!timing->v_size) + return; + + timing_aspect = (float) timing->h_size / timing->v_size; + if (fabs(1 - (timing_aspect / p->target_aspect)) < 0.05) { + p->real_hsize = max(p->real_hsize, timing->h_size); + p->real_vsize = max(p->real_vsize, timing->v_size); + } + } +} + +static void +encode_aspect_ratio(xf86MonPtr m) +{ + /* + * some monitors encode the aspect ratio instead of the physical size. + * try to find the largest detailed timing that matches that aspect + * ratio and use that to fill in the feature section. + */ + if ((m->features.hsize == 16 && m->features.vsize == 9) || + (m->features.hsize == 16 && m->features.vsize == 10) || + (m->features.hsize == 4 && m->features.vsize == 3) || + (m->features.hsize == 5 && m->features.vsize == 4)) { + + struct det_hv_parameter p; + + p.real_hsize = 0; + p.real_vsize = 0; + p.target_aspect = (float) m->features.hsize / m->features.vsize; + + xf86ForEachDetailedBlock(m, handle_detailed_hvsize, &p); + + if (!p.real_hsize || !p.real_vsize) { + m->features.hsize = m->features.vsize = 0; + } + else if ((m->features.hsize * 10 == p.real_hsize) && + (m->features.vsize * 10 == p.real_vsize)) { + /* exact match is just unlikely, should do a better check though */ + m->features.hsize = m->features.vsize = 0; + } + else { + /* convert mm to cm */ + m->features.hsize = (p.real_hsize + 5) / 10; + m->features.vsize = (p.real_vsize + 5) / 10; + } + + xf86Msg(X_INFO, "Quirked EDID physical size to %dx%d cm\n", + m->features.hsize, m->features.vsize); + } +} + +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); + encode_aspect_ratio(m); + + return m; + + error: + free(m); + return NULL; +} + +static int +get_cea_detail_timing(Uchar * blk, xf86MonPtr mon, + struct detailed_monitor_section *det_mon) +{ + int dt_num; + int dt_offset = ((struct cea_ext_body *) blk)->dt_offset; + + dt_num = 0; + + if (dt_offset < CEA_EXT_MIN_DATA_OFFSET) + return dt_num; + + for (; dt_offset < (CEA_EXT_MAX_DATA_OFFSET - DET_TIMING_INFO_LEN) && + dt_num < CEA_EXT_DET_TIMING_NUM; _NEXT_DT_MD_SECTION(dt_offset)) { + + fetch_detailed_block(blk + dt_offset, &mon->ver, det_mon + dt_num); + dt_num = dt_num + 1; + } + + return dt_num; +} + +static void +handle_cea_detail_block(Uchar * ext, xf86MonPtr mon, + handle_detailed_fn fn, void *data) +{ + int i; + struct detailed_monitor_section det_mon[CEA_EXT_DET_TIMING_NUM]; + int det_mon_num; + + det_mon_num = get_cea_detail_timing(ext, mon, det_mon); + + for (i = 0; i < det_mon_num; i++) + fn(det_mon + i, data); +} + +void +xf86ForEachDetailedBlock(xf86MonPtr mon, handle_detailed_fn fn, void *data) +{ + int i; + Uchar *ext; + + if (mon == NULL) + return; + + for (i = 0; i < DET_TIMINGS; i++) + fn(mon->det_mon + i, data); + + for (i = 0; i < mon->no_sections; i++) { + ext = mon->rawData + EDID1_LEN * (i + 1); + switch (ext[EXT_TAG]) { + case CEA_EXT: + handle_cea_detail_block(ext, mon, fn, data); + break; + case VTB_EXT: + case DI_EXT: + case LS_EXT: + case MI_EXT: + break; + } + } +} + +static struct cea_data_block * +extract_cea_data_block(Uchar * ext, int data_type) +{ + struct cea_ext_body *cea; + struct cea_data_block *data_collection; + struct cea_data_block *data_end; + + cea = (struct cea_ext_body *) ext; + + if (cea->dt_offset <= CEA_EXT_MIN_DATA_OFFSET) + return NULL; + + data_collection = &cea->data_collection; + data_end = (struct cea_data_block *) (cea->dt_offset + ext); + + for (; data_collection < data_end;) { + + if (data_type == data_collection->tag) { + return data_collection; + } + data_collection = (void *) ((unsigned char *) data_collection + + data_collection->len + 1); + } + + return NULL; +} + +static void +handle_cea_video_block(Uchar * ext, handle_video_fn fn, void *data) +{ + struct cea_video_block *video; + struct cea_video_block *video_end; + struct cea_data_block *data_collection; + + data_collection = extract_cea_data_block(ext, CEA_VIDEO_BLK); + if (data_collection == NULL) + return; + + video = &data_collection->u.video; + video_end = (struct cea_video_block *) + ((Uchar *) video + data_collection->len); + + for (; video < video_end; video = video + 1) { + fn(video, data); + } +} + +void +xf86ForEachVideoBlock(xf86MonPtr mon, handle_video_fn fn, void *data) +{ + int i; + Uchar *ext; + + if (mon == NULL) + return; + + for (i = 0; i < mon->no_sections; i++) { + ext = mon->rawData + EDID1_LEN * (i + 1); + switch (ext[EXT_TAG]) { + case CEA_EXT: + handle_cea_video_block(ext, fn, data); + break; + case VTB_EXT: + case DI_EXT: + case LS_EXT: + case MI_EXT: + break; + } + } +} + +xf86MonPtr +xf86InterpretEEDID(int scrnIndex, Uchar * block) +{ + xf86MonPtr m; + + m = xf86InterpretEDID(scrnIndex, block); + if (!m) + return NULL; + + /* extension parse */ + + return m; +} + +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->revision == 2 || v->revision == 3) { + r->input_dfp = DFP; + } + else if (v->revision >= 4) { + r->input_bpc = BPC; + r->input_interface = DIGITAL_INTERFACE; + } + 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_cvt_timing_section(Uchar * c, struct cvt_timings *r) +{ + int i; + + for (i = 0; i < 4; i++) { + if (c[0] && c[1] && c[2]) { + r[i].height = (c[0] + ((c[1] & 0xF0) << 8) + 1) * 2; + switch (c[1] & 0xc0) { + case 0x00: + r[i].width = r[i].height * 4 / 3; + break; + case 0x40: + r[i].width = r[i].height * 16 / 9; + break; + case 0x80: + r[i].width = r[i].height * 16 / 10; + break; + case 0xc0: + r[i].width = r[i].height * 15 / 9; + break; + } + switch (c[2] & 0x60) { + case 0x00: + r[i].rate = 50; + break; + case 0x20: + r[i].rate = 60; + break; + case 0x40: + r[i].rate = 75; + break; + case 0x60: + r[i].rate = 85; + break; + } + r[i].rates = c[2] & 0x1f; + } + else { + return; + } + c += 3; + } +} + +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 const unsigned char empty_block[18]; + +static void +fetch_detailed_block(Uchar * c, struct edid_version *ver, + struct detailed_monitor_section *det_mon) +{ + if (ver->version == 1 && ver->revision >= 1 && IS_MONITOR_DESC) { + switch (MONITOR_DESC_TYPE) { + case SERIAL_NUMBER: + det_mon->type = DS_SERIAL; + copy_string(c, det_mon->section.serial); + break; + case ASCII_STR: + det_mon->type = DS_ASCII_STR; + copy_string(c, det_mon->section.ascii_data); + break; + case MONITOR_RANGES: + det_mon->type = DS_RANGES; + get_monitor_ranges(c, &det_mon->section.ranges); + break; + case MONITOR_NAME: + det_mon->type = DS_NAME; + copy_string(c, det_mon->section.name); + break; + case ADD_COLOR_POINT: + det_mon->type = DS_WHITE_P; + get_whitepoint_section(c, det_mon->section.wp); + break; + case ADD_STD_TIMINGS: + det_mon->type = DS_STD_TIMINGS; + get_dst_timing_section(c, det_mon->section.std_t, ver); + break; + case COLOR_MANAGEMENT_DATA: + det_mon->type = DS_CMD; + break; + case CVT_3BYTE_DATA: + det_mon->type = DS_CVT; + get_cvt_timing_section(c, det_mon->section.cvt); + break; + case ADD_EST_TIMINGS: + det_mon->type = DS_EST_III; + memcpy(det_mon->section.est_iii, c + 6, 6); + break; + case ADD_DUMMY: + det_mon->type = DS_DUMMY; + break; + default: + det_mon->type = DS_UNKOWN; + break; + } + if (c[3] <= 0x0F && memcmp(c, empty_block, sizeof(empty_block))) { + det_mon->type = DS_VENDOR + c[3]; + } + } + else { + det_mon->type = DT; + get_detailed_timing_section(c, &det_mon->section.d_timings); + } +} + +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++) { + fetch_detailed_block(c, ver, det_mon + i); + 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 + 5; + 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; + } + if (HAVE_CVT) { + r->max_clock_khz = MAX_CLOCK_KHZ; + r->max_clock = r->max_clock_khz / 1000; + r->maxwidth = MAXWIDTH; + r->supported_aspect = SUPPORTED_ASPECT; + r->preferred_aspect = PREFERRED_ASPECT; + r->supported_blanking = SUPPORTED_BLANKING; + r->supported_scaling = SUPPORTED_SCALING; + r->preferred_refresh = PREFERRED_REFRESH; + } + else { + r->max_clock_khz = 0; + } +} + +static void +get_whitepoint_section(Uchar * c, struct whitePoints *wp) +{ + wp[0].white_x = WHITEX1; + wp[0].white_y = WHITEY1; + wp[1].white_x = WHITEX2; + wp[1].white_y = WHITEY2; + wp[0].index = WHITE_INDEX1; + wp[1].index = WHITE_INDEX2; + wp[0].white_gamma = WHITE_GAMMA1; + wp[1].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; +} + +#define MAX_EDID_MINOR 4 + +static Bool +validate_version(int scrnIndex, struct edid_version *r) +{ + if (r->version != 1) { + xf86DrvMsg(scrnIndex, X_ERROR, "Unknown EDID version %d\n", r->version); + return FALSE; + } + + if (r->revision > MAX_EDID_MINOR) + xf86DrvMsg(scrnIndex, X_WARNING, + "Assuming version 1.%d is compatible with 1.%d\n", + r->revision, MAX_EDID_MINOR); + + return TRUE; +} + +/* + * Returns true if HDMI, false if definitely not or unknown. + */ +Bool +xf86MonitorIsHDMI(xf86MonPtr mon) +{ + int i = 0, version, offset; + char *edid = NULL; + + if (!mon) + return FALSE; + + if (!(mon->flags & EDID_COMPLETE_RAWDATA)) + return FALSE; + + if (!mon->no_sections) + return FALSE; + + edid = (char *) mon->rawData; + if (!edid) + return FALSE; + + /* find the CEA extension block */ + for (i = 1; i <= mon->no_sections; i++) + if (edid[i * 128] == 0x02) + break; + if (i == mon->no_sections + 1) + return FALSE; + edid += (i * 128); + + version = edid[1]; + offset = edid[2]; + if (version < 3 || offset < 4) + return FALSE; + + /* walk the cea data blocks */ + for (i = 4; i < offset; i += (edid[i] & 0x1f) + 1) { + char *x = edid + i; + + /* find a vendor specific block */ + if ((x[0] & 0xe0) >> 5 == 0x03) { + int oui = (x[3] << 16) + (x[2] << 8) + x[1]; + + /* find the HDMI vendor OUI */ + if (oui == 0x000c03) + return TRUE; + } + } + + /* guess it's not HDMI after all */ + return FALSE; +} |