1706f2543Smrg/*
2706f2543Smrg * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE>
3706f2543Smrg * Copyright 2007 Red Hat, Inc.
4706f2543Smrg *
5706f2543Smrg * Permission is hereby granted, free of charge, to any person obtaining a
6706f2543Smrg * copy of this software and associated documentation files (the "Software"),
7706f2543Smrg * to deal in the Software without restriction, including without limitation
8706f2543Smrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9706f2543Smrg * and/or sell copies of the Software, and to permit persons to whom the
10706f2543Smrg * Software is furnished to do so, subject to the following conditions:
11706f2543Smrg *
12706f2543Smrg * The above copyright notice and this permission notice (including the next
13706f2543Smrg * paragraph) shall be included in all copies or substantial portions of the
14706f2543Smrg * Software.
15706f2543Smrg *
16706f2543Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17706f2543Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18706f2543Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19706f2543Smrg * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20706f2543Smrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21706f2543Smrg * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22706f2543Smrg * DEALINGS IN THE SOFTWARE.
23706f2543Smrg *
24706f2543Smrg * interpret_edid.c: interpret a primary EDID block
25706f2543Smrg */
26706f2543Smrg
27706f2543Smrg#ifdef HAVE_XORG_CONFIG_H
28706f2543Smrg#include <xorg-config.h>
29706f2543Smrg#endif
30706f2543Smrg
31706f2543Smrg#include "misc.h"
32706f2543Smrg#include "xf86.h"
33706f2543Smrg#include "xf86_OSproc.h"
34706f2543Smrg#define _PARSE_EDID_
35706f2543Smrg#include "xf86DDC.h"
36706f2543Smrg#include <string.h>
37706f2543Smrg
38706f2543Smrgstatic void get_vendor_section(Uchar*, struct vendor *);
39706f2543Smrgstatic void get_version_section(Uchar*, struct edid_version *);
40706f2543Smrgstatic void get_display_section(Uchar*, struct disp_features *,
41706f2543Smrg				struct edid_version *);
42706f2543Smrgstatic void get_established_timing_section(Uchar*, struct established_timings *);
43706f2543Smrgstatic void get_std_timing_section(Uchar*, struct std_timings *,
44706f2543Smrg				   struct edid_version *);
45706f2543Smrgstatic void fetch_detailed_block(Uchar *c, struct edid_version *ver,
46706f2543Smrg                                 struct detailed_monitor_section *det_mon);
47706f2543Smrgstatic void get_dt_md_section(Uchar *, struct edid_version *,
48706f2543Smrg			      struct detailed_monitor_section *det_mon);
49706f2543Smrgstatic void copy_string(Uchar *, Uchar *);
50706f2543Smrgstatic void get_dst_timing_section(Uchar *, struct std_timings *,
51706f2543Smrg				   struct edid_version *);
52706f2543Smrgstatic void get_monitor_ranges(Uchar *, struct monitor_ranges *);
53706f2543Smrgstatic void get_whitepoint_section(Uchar *, struct whitePoints *);
54706f2543Smrgstatic void get_detailed_timing_section(Uchar*, struct 	detailed_timings *);
55706f2543Smrgstatic Bool validate_version(int scrnIndex, struct edid_version *);
56706f2543Smrg
57706f2543Smrgstatic void
58706f2543Smrgfind_ranges_section(struct detailed_monitor_section *det, void *ranges)
59706f2543Smrg{
60706f2543Smrg   if (det->type == DS_RANGES && det->section.ranges.max_clock)
61706f2543Smrg       *(struct monitor_ranges **)ranges = &det->section.ranges;
62706f2543Smrg}
63706f2543Smrg
64706f2543Smrgstatic void
65706f2543Smrgfind_max_detailed_clock(struct detailed_monitor_section *det, void *ret)
66706f2543Smrg{
67706f2543Smrg    if (det->type == DT) {
68706f2543Smrg        *(int *)ret = max(*((int *)ret),
69706f2543Smrg                          det->section.d_timings.clock);
70706f2543Smrg    }
71706f2543Smrg}
72706f2543Smrg
73706f2543Smrgstatic void
74706f2543Smrghandle_edid_quirks(xf86MonPtr m)
75706f2543Smrg{
76706f2543Smrg    struct monitor_ranges *ranges = NULL;
77706f2543Smrg
78706f2543Smrg    /*
79706f2543Smrg     * max_clock is only encoded in EDID in tens of MHz, so occasionally we
80706f2543Smrg     * find a monitor claiming a max of 160 with a mode requiring 162, or
81706f2543Smrg     * similar.  Strictly we should refuse to round up too far, but let's
82706f2543Smrg     * see how well this works.
83706f2543Smrg     */
84706f2543Smrg
85706f2543Smrg    /* Try to find Monitor Range and max clock, then re-set range value*/
86706f2543Smrg    xf86ForEachDetailedBlock(m, find_ranges_section, &ranges);
87706f2543Smrg    if (ranges && ranges->max_clock) {
88706f2543Smrg        int clock = 0;
89706f2543Smrg        xf86ForEachDetailedBlock(m, find_max_detailed_clock, &clock);
90706f2543Smrg        if (clock && (ranges->max_clock * 1e6 < clock)) {
91706f2543Smrg            xf86Msg(X_WARNING, "EDID timing clock %.2f exceeds claimed max "
92706f2543Smrg                    "%dMHz, fixing\n", clock / 1.0e6, ranges->max_clock);
93706f2543Smrg            ranges->max_clock = (clock+999999)/1e6;
94706f2543Smrg        }
95706f2543Smrg    }
96706f2543Smrg}
97706f2543Smrg
98706f2543Smrgstruct det_hv_parameter {
99706f2543Smrg    int real_hsize;
100706f2543Smrg    int real_vsize;
101706f2543Smrg    float target_aspect;
102706f2543Smrg};
103706f2543Smrg
104706f2543Smrgstatic void handle_detailed_hvsize(struct detailed_monitor_section *det_mon,
105706f2543Smrg                                   void *data)
106706f2543Smrg{
107706f2543Smrg    struct det_hv_parameter *p = (struct det_hv_parameter *)data;
108706f2543Smrg    float timing_aspect;
109706f2543Smrg
110706f2543Smrg    if (det_mon->type == DT) {
111706f2543Smrg        struct detailed_timings *timing;
112706f2543Smrg        timing = &det_mon->section.d_timings;
113706f2543Smrg
114706f2543Smrg        if (!timing->v_size)
115706f2543Smrg            return;
116706f2543Smrg
117706f2543Smrg        timing_aspect = (float)timing->h_size / timing->v_size;
118706f2543Smrg        if (fabs(1 - (timing_aspect / p->target_aspect)) < 0.05) {
119706f2543Smrg            p->real_hsize = max(p->real_hsize, timing->h_size);
120706f2543Smrg            p->real_vsize = max(p->real_vsize, timing->v_size);
121706f2543Smrg        }
122706f2543Smrg    }
123706f2543Smrg}
124706f2543Smrg
125706f2543Smrgstatic void encode_aspect_ratio(xf86MonPtr m)
126706f2543Smrg{
127706f2543Smrg    /*
128706f2543Smrg     * some monitors encode the aspect ratio instead of the physical size.
129706f2543Smrg     * try to find the largest detailed timing that matches that aspect
130706f2543Smrg     * ratio and use that to fill in the feature section.
131706f2543Smrg     */
132706f2543Smrg    if ((m->features.hsize == 16 && m->features.vsize == 9) ||
133706f2543Smrg	(m->features.hsize == 16 && m->features.vsize == 10) ||
134706f2543Smrg	(m->features.hsize == 4 && m->features.vsize == 3) ||
135706f2543Smrg	(m->features.hsize == 5 && m->features.vsize == 4)) {
136706f2543Smrg
137706f2543Smrg        struct det_hv_parameter p;
138706f2543Smrg        p.real_hsize = 0;
139706f2543Smrg        p.real_vsize = 0;
140706f2543Smrg        p.target_aspect = (float)m->features.hsize /m->features.vsize;
141706f2543Smrg
142706f2543Smrg        xf86ForEachDetailedBlock(m, handle_detailed_hvsize, &p);
143706f2543Smrg
144706f2543Smrg	if (!p.real_hsize || !p.real_vsize) {
145706f2543Smrg	    m->features.hsize = m->features.vsize = 0;
146706f2543Smrg	} else if ((m->features.hsize * 10 == p.real_hsize) &&
147706f2543Smrg		   (m->features.vsize * 10 == p.real_vsize)) {
148706f2543Smrg	    /* exact match is just unlikely, should do a better check though */
149706f2543Smrg	    m->features.hsize = m->features.vsize = 0;
150706f2543Smrg	} else {
151706f2543Smrg	    /* convert mm to cm */
152706f2543Smrg	    m->features.hsize = (p.real_hsize + 5) / 10;
153706f2543Smrg	    m->features.vsize = (p.real_vsize + 5) / 10;
154706f2543Smrg	}
155706f2543Smrg
156706f2543Smrg	xf86Msg(X_INFO, "Quirked EDID physical size to %dx%d cm\n",
157706f2543Smrg		m->features.hsize, m->features.vsize);
158706f2543Smrg    }
159706f2543Smrg}
160706f2543Smrg
161706f2543Smrgxf86MonPtr
162706f2543Smrgxf86InterpretEDID(int scrnIndex, Uchar *block)
163706f2543Smrg{
164706f2543Smrg    xf86MonPtr m;
165706f2543Smrg
166706f2543Smrg    if (!block) return NULL;
167706f2543Smrg    if (! (m = xnfcalloc(sizeof(xf86Monitor),1))) return NULL;
168706f2543Smrg    m->scrnIndex = scrnIndex;
169706f2543Smrg    m->rawData = block;
170706f2543Smrg
171706f2543Smrg    get_vendor_section(SECTION(VENDOR_SECTION,block),&m->vendor);
172706f2543Smrg    get_version_section(SECTION(VERSION_SECTION,block),&m->ver);
173706f2543Smrg    if (!validate_version(scrnIndex, &m->ver)) goto error;
174706f2543Smrg    get_display_section(SECTION(DISPLAY_SECTION,block),&m->features,
175706f2543Smrg			&m->ver);
176706f2543Smrg    get_established_timing_section(SECTION(ESTABLISHED_TIMING_SECTION,block),
177706f2543Smrg				   &m->timings1);
178706f2543Smrg    get_std_timing_section(SECTION(STD_TIMING_SECTION,block),m->timings2,
179706f2543Smrg			   &m->ver);
180706f2543Smrg    get_dt_md_section(SECTION(DET_TIMING_SECTION,block),&m->ver, m->det_mon);
181706f2543Smrg    m->no_sections = (int)*(char *)SECTION(NO_EDID,block);
182706f2543Smrg
183706f2543Smrg    handle_edid_quirks(m);
184706f2543Smrg    encode_aspect_ratio(m);
185706f2543Smrg
186706f2543Smrg    return m;
187706f2543Smrg
188706f2543Smrg error:
189706f2543Smrg    free(m);
190706f2543Smrg    return NULL;
191706f2543Smrg}
192706f2543Smrg
193706f2543Smrgstatic int get_cea_detail_timing(Uchar *blk, xf86MonPtr mon,
194706f2543Smrg                                 struct detailed_monitor_section *det_mon)
195706f2543Smrg{
196706f2543Smrg    int dt_num;
197706f2543Smrg    int dt_offset = ((struct cea_ext_body *)blk)->dt_offset;
198706f2543Smrg
199706f2543Smrg    dt_num = 0;
200706f2543Smrg
201706f2543Smrg    if (dt_offset < CEA_EXT_MIN_DATA_OFFSET)
202706f2543Smrg        return dt_num;
203706f2543Smrg
204706f2543Smrg    for (; dt_offset < (CEA_EXT_MAX_DATA_OFFSET - DET_TIMING_INFO_LEN) &&
205706f2543Smrg           dt_num < CEA_EXT_DET_TIMING_NUM;
206706f2543Smrg	   _NEXT_DT_MD_SECTION(dt_offset)) {
207706f2543Smrg
208706f2543Smrg        fetch_detailed_block(blk + dt_offset, &mon->ver, det_mon + dt_num);
209706f2543Smrg        dt_num = dt_num + 1 ;
210706f2543Smrg    }
211706f2543Smrg
212706f2543Smrg    return dt_num;
213706f2543Smrg}
214706f2543Smrg
215706f2543Smrgstatic void handle_cea_detail_block(Uchar *ext, xf86MonPtr mon,
216706f2543Smrg                                    handle_detailed_fn fn,
217706f2543Smrg                                    void *data)
218706f2543Smrg{
219706f2543Smrg    int i;
220706f2543Smrg    struct detailed_monitor_section det_mon[CEA_EXT_DET_TIMING_NUM];
221706f2543Smrg    int det_mon_num;
222706f2543Smrg
223706f2543Smrg    det_mon_num = get_cea_detail_timing(ext, mon, det_mon);
224706f2543Smrg
225706f2543Smrg    for (i = 0; i < det_mon_num; i++)
226706f2543Smrg        fn(det_mon + i, data);
227706f2543Smrg}
228706f2543Smrg
229706f2543Smrgvoid xf86ForEachDetailedBlock(xf86MonPtr mon,
230706f2543Smrg                              handle_detailed_fn fn,
231706f2543Smrg                              void *data)
232706f2543Smrg{
233706f2543Smrg    int i;
234706f2543Smrg    Uchar *ext;
235706f2543Smrg
236706f2543Smrg    if (mon == NULL)
237706f2543Smrg        return;
238706f2543Smrg
239706f2543Smrg    for (i = 0; i < DET_TIMINGS; i++)
240706f2543Smrg        fn(mon->det_mon + i, data);
241706f2543Smrg
242706f2543Smrg    for (i = 0; i < mon->no_sections; i++) {
243706f2543Smrg        ext = mon->rawData + EDID1_LEN * (i + 1);
244706f2543Smrg        switch (ext[EXT_TAG]){
245706f2543Smrg        case CEA_EXT:
246706f2543Smrg            handle_cea_detail_block(ext, mon, fn, data);
247706f2543Smrg            break;
248706f2543Smrg        case VTB_EXT:
249706f2543Smrg        case DI_EXT:
250706f2543Smrg        case LS_EXT:
251706f2543Smrg        case MI_EXT:
252706f2543Smrg	    break;
253706f2543Smrg        }
254706f2543Smrg    }
255706f2543Smrg}
256706f2543Smrg
257706f2543Smrgstatic struct cea_data_block *
258706f2543Smrgextract_cea_data_block(Uchar *ext, int data_type)
259706f2543Smrg{
260706f2543Smrg    struct cea_ext_body *cea;
261706f2543Smrg    struct cea_data_block *data_collection;
262706f2543Smrg    struct cea_data_block *data_end;
263706f2543Smrg
264706f2543Smrg    cea = (struct cea_ext_body *)ext;
265706f2543Smrg
266706f2543Smrg    if (cea->dt_offset <= CEA_EXT_MIN_DATA_OFFSET)
267706f2543Smrg        return NULL;
268706f2543Smrg
269706f2543Smrg    data_collection = &cea->data_collection;
270706f2543Smrg    data_end = (struct cea_data_block *)(cea->dt_offset + ext);
271706f2543Smrg
272706f2543Smrg    for ( ;data_collection < data_end;) {
273706f2543Smrg
274706f2543Smrg	if (data_type == data_collection->tag) {
275706f2543Smrg	    return data_collection;
276706f2543Smrg	}
277706f2543Smrg	data_collection = (void *)((unsigned char *)data_collection +
278706f2543Smrg	    data_collection->len + 1);
279706f2543Smrg    }
280706f2543Smrg
281706f2543Smrg    return NULL;
282706f2543Smrg}
283706f2543Smrg
284706f2543Smrgstatic void handle_cea_video_block(Uchar *ext, handle_video_fn fn, void *data)
285706f2543Smrg{
286706f2543Smrg    struct cea_video_block *video;
287706f2543Smrg    struct cea_video_block *video_end;
288706f2543Smrg    struct cea_data_block *data_collection;
289706f2543Smrg
290706f2543Smrg    data_collection = extract_cea_data_block(ext, CEA_VIDEO_BLK);
291706f2543Smrg    if (data_collection == NULL)
292706f2543Smrg        return;
293706f2543Smrg
294706f2543Smrg    video = &data_collection->u.video;
295706f2543Smrg    video_end = (struct cea_video_block *)
296706f2543Smrg	((Uchar *)video + data_collection->len);
297706f2543Smrg
298706f2543Smrg    for (; video < video_end; video = video + 1) {
299706f2543Smrg	fn(video, data);
300706f2543Smrg    }
301706f2543Smrg}
302706f2543Smrg
303706f2543Smrgvoid xf86ForEachVideoBlock(xf86MonPtr mon,
304706f2543Smrg	                   handle_video_fn fn,
305706f2543Smrg                           void *data)
306706f2543Smrg{
307706f2543Smrg    int i;
308706f2543Smrg    Uchar *ext;
309706f2543Smrg
310706f2543Smrg    if (mon == NULL)
311706f2543Smrg	return;
312706f2543Smrg
313706f2543Smrg    for (i = 0; i < mon->no_sections; i++) {
314706f2543Smrg	ext = mon->rawData + EDID1_LEN * (i + 1);
315706f2543Smrg	switch (ext[EXT_TAG]) {
316706f2543Smrg	case CEA_EXT:
317706f2543Smrg	    handle_cea_video_block(ext, fn, data);
318706f2543Smrg	    break;
319706f2543Smrg	case VTB_EXT:
320706f2543Smrg	case DI_EXT:
321706f2543Smrg	case LS_EXT:
322706f2543Smrg	case MI_EXT:
323706f2543Smrg	    break;
324706f2543Smrg	}
325706f2543Smrg    }
326706f2543Smrg}
327706f2543Smrg
328706f2543Smrgxf86MonPtr
329706f2543Smrgxf86InterpretEEDID(int scrnIndex, Uchar *block)
330706f2543Smrg{
331706f2543Smrg    xf86MonPtr m;
332706f2543Smrg
333706f2543Smrg    m = xf86InterpretEDID(scrnIndex, block);
334706f2543Smrg    if (!m)
335706f2543Smrg	return NULL;
336706f2543Smrg
337706f2543Smrg    /* extension parse */
338706f2543Smrg
339706f2543Smrg    return m;
340706f2543Smrg}
341706f2543Smrg
342706f2543Smrgstatic void
343706f2543Smrgget_vendor_section(Uchar *c, struct vendor *r)
344706f2543Smrg{
345706f2543Smrg    r->name[0] = L1;
346706f2543Smrg    r->name[1] = L2;
347706f2543Smrg    r->name[2] = L3;
348706f2543Smrg    r->name[3] = '\0';
349706f2543Smrg
350706f2543Smrg    r->prod_id = PROD_ID;
351706f2543Smrg    r->serial  = SERIAL_NO;
352706f2543Smrg    r->week    = WEEK;
353706f2543Smrg    r->year    = YEAR;
354706f2543Smrg}
355706f2543Smrg
356706f2543Smrgstatic void
357706f2543Smrgget_version_section(Uchar *c, struct edid_version *r)
358706f2543Smrg{
359706f2543Smrg    r->version  = VERSION;
360706f2543Smrg    r->revision = REVISION;
361706f2543Smrg}
362706f2543Smrg
363706f2543Smrgstatic void
364706f2543Smrgget_display_section(Uchar *c, struct disp_features *r,
365706f2543Smrg		    struct edid_version *v)
366706f2543Smrg{
367706f2543Smrg    r->input_type = INPUT_TYPE;
368706f2543Smrg    if (!DIGITAL(r->input_type)) {
369706f2543Smrg	r->input_voltage = INPUT_VOLTAGE;
370706f2543Smrg	r->input_setup = SETUP;
371706f2543Smrg	r->input_sync = SYNC;
372706f2543Smrg    } else if (v->revision == 2 || v->revision == 3) {
373706f2543Smrg	r->input_dfp = DFP;
374706f2543Smrg    } else if (v->revision >= 4) {
375706f2543Smrg	r->input_bpc = BPC;
376706f2543Smrg	r->input_interface = DIGITAL_INTERFACE;
377706f2543Smrg    }
378706f2543Smrg    r->hsize = HSIZE_MAX;
379706f2543Smrg    r->vsize = VSIZE_MAX;
380706f2543Smrg    r->gamma = GAMMA;
381706f2543Smrg    r->dpms =  DPMS;
382706f2543Smrg    r->display_type = DISPLAY_TYPE;
383706f2543Smrg    r->msc = MSC;
384706f2543Smrg    r->redx = REDX;
385706f2543Smrg    r->redy = REDY;
386706f2543Smrg    r->greenx = GREENX;
387706f2543Smrg    r->greeny = GREENY;
388706f2543Smrg    r->bluex = BLUEX;
389706f2543Smrg    r->bluey = BLUEY;
390706f2543Smrg    r->whitex = WHITEX;
391706f2543Smrg    r->whitey = WHITEY;
392706f2543Smrg}
393706f2543Smrg
394706f2543Smrgstatic void
395706f2543Smrgget_established_timing_section(Uchar *c, struct established_timings *r)
396706f2543Smrg{
397706f2543Smrg    r->t1 = T1;
398706f2543Smrg    r->t2 = T2;
399706f2543Smrg    r->t_manu = T_MANU;
400706f2543Smrg}
401706f2543Smrg
402706f2543Smrgstatic void
403706f2543Smrgget_cvt_timing_section(Uchar *c, struct cvt_timings *r)
404706f2543Smrg{
405706f2543Smrg    int i;
406706f2543Smrg
407706f2543Smrg    for (i = 0; i < 4; i++) {
408706f2543Smrg	if (c[0] && c[1] && c[2]) {
409706f2543Smrg	    r[i].height = (c[0] + ((c[1] & 0xF0) << 8) + 1) * 2;
410706f2543Smrg	    switch (c[1] & 0xc0) {
411706f2543Smrg		case 0x00: r[i].width = r[i].height * 4 / 3; break;
412706f2543Smrg		case 0x40: r[i].width = r[i].height * 16 / 9; break;
413706f2543Smrg		case 0x80: r[i].width = r[i].height * 16 / 10; break;
414706f2543Smrg		case 0xc0: r[i].width = r[i].height * 15 / 9; break;
415706f2543Smrg	    }
416706f2543Smrg	    switch (c[2] & 0x60) {
417706f2543Smrg		case 0x00: r[i].rate = 50; break;
418706f2543Smrg		case 0x20: r[i].rate = 60; break;
419706f2543Smrg		case 0x40: r[i].rate = 75; break;
420706f2543Smrg		case 0x60: r[i].rate = 85; break;
421706f2543Smrg	    }
422706f2543Smrg	    r[i].rates = c[2] & 0x1f;
423706f2543Smrg	} else {
424706f2543Smrg	    return;
425706f2543Smrg	}
426706f2543Smrg	c += 3;
427706f2543Smrg    }
428706f2543Smrg}
429706f2543Smrg
430706f2543Smrgstatic void
431706f2543Smrgget_std_timing_section(Uchar *c, struct std_timings *r,
432706f2543Smrg		       struct edid_version *v)
433706f2543Smrg{
434706f2543Smrg    int i;
435706f2543Smrg
436706f2543Smrg    for (i=0;i<STD_TIMINGS;i++){
437706f2543Smrg	if (VALID_TIMING) {
438706f2543Smrg	    r[i].hsize = HSIZE1;
439706f2543Smrg	    VSIZE1(r[i].vsize);
440706f2543Smrg	    r[i].refresh = REFRESH_R;
441706f2543Smrg	    r[i].id = STD_TIMING_ID;
442706f2543Smrg	} else {
443706f2543Smrg	    r[i].hsize = r[i].vsize = r[i].refresh = r[i].id = 0;
444706f2543Smrg	}
445706f2543Smrg	NEXT_STD_TIMING;
446706f2543Smrg    }
447706f2543Smrg}
448706f2543Smrg
449706f2543Smrgstatic const unsigned char empty_block[18];
450706f2543Smrg
451706f2543Smrgstatic void
452706f2543Smrgfetch_detailed_block(Uchar *c, struct edid_version *ver,
453706f2543Smrg                     struct detailed_monitor_section *det_mon)
454706f2543Smrg{
455706f2543Smrg    if (ver->version == 1 && ver->revision >= 1 && IS_MONITOR_DESC) {
456706f2543Smrg        switch (MONITOR_DESC_TYPE) {
457706f2543Smrg        case SERIAL_NUMBER:
458706f2543Smrg            det_mon->type = DS_SERIAL;
459706f2543Smrg            copy_string(c,det_mon->section.serial);
460706f2543Smrg            break;
461706f2543Smrg        case ASCII_STR:
462706f2543Smrg            det_mon->type = DS_ASCII_STR;
463706f2543Smrg            copy_string(c,det_mon->section.ascii_data);
464706f2543Smrg            break;
465706f2543Smrg        case MONITOR_RANGES:
466706f2543Smrg            det_mon->type = DS_RANGES;
467706f2543Smrg            get_monitor_ranges(c,&det_mon->section.ranges);
468706f2543Smrg            break;
469706f2543Smrg        case MONITOR_NAME:
470706f2543Smrg            det_mon->type = DS_NAME;
471706f2543Smrg            copy_string(c,det_mon->section.name);
472706f2543Smrg            break;
473706f2543Smrg        case ADD_COLOR_POINT:
474706f2543Smrg            det_mon->type = DS_WHITE_P;
475706f2543Smrg            get_whitepoint_section(c,det_mon->section.wp);
476706f2543Smrg            break;
477706f2543Smrg        case ADD_STD_TIMINGS:
478706f2543Smrg            det_mon->type = DS_STD_TIMINGS;
479706f2543Smrg            get_dst_timing_section(c,det_mon->section.std_t, ver);
480706f2543Smrg            break;
481706f2543Smrg        case COLOR_MANAGEMENT_DATA:
482706f2543Smrg            det_mon->type = DS_CMD;
483706f2543Smrg            break;
484706f2543Smrg        case CVT_3BYTE_DATA:
485706f2543Smrg            det_mon->type = DS_CVT;
486706f2543Smrg            get_cvt_timing_section(c, det_mon->section.cvt);
487706f2543Smrg            break;
488706f2543Smrg        case ADD_EST_TIMINGS:
489706f2543Smrg            det_mon->type = DS_EST_III;
490706f2543Smrg	    memcpy(det_mon->section.est_iii, c + 6, 6);
491706f2543Smrg            break;
492706f2543Smrg        case ADD_DUMMY:
493706f2543Smrg            det_mon->type = DS_DUMMY;
494706f2543Smrg            break;
495706f2543Smrg        default:
496706f2543Smrg            det_mon->type = DS_UNKOWN;
497706f2543Smrg            break;
498706f2543Smrg        }
499706f2543Smrg	if (c[3] <= 0x0F && memcmp(c, empty_block, sizeof(empty_block))) {
500706f2543Smrg            det_mon->type = DS_VENDOR + c[3];
501706f2543Smrg        }
502706f2543Smrg    } else {
503706f2543Smrg        det_mon->type = DT;
504706f2543Smrg        get_detailed_timing_section(c, &det_mon->section.d_timings);
505706f2543Smrg    }
506706f2543Smrg}
507706f2543Smrg
508706f2543Smrgstatic void
509706f2543Smrgget_dt_md_section(Uchar *c, struct edid_version *ver,
510706f2543Smrg		  struct detailed_monitor_section *det_mon)
511706f2543Smrg{
512706f2543Smrg    int i;
513706f2543Smrg
514706f2543Smrg    for (i=0; i < DET_TIMINGS; i++) {
515706f2543Smrg        fetch_detailed_block(c, ver, det_mon + i);
516706f2543Smrg        NEXT_DT_MD_SECTION;
517706f2543Smrg    }
518706f2543Smrg}
519706f2543Smrg
520706f2543Smrgstatic void
521706f2543Smrgcopy_string(Uchar *c, Uchar *s)
522706f2543Smrg{
523706f2543Smrg  int i;
524706f2543Smrg  c = c + 5;
525706f2543Smrg  for (i = 0; (i < 13 && *c != 0x0A); i++)
526706f2543Smrg    *(s++) = *(c++);
527706f2543Smrg  *s = 0;
528706f2543Smrg  while (i-- && (*--s == 0x20)) *s = 0;
529706f2543Smrg}
530706f2543Smrg
531706f2543Smrgstatic void
532706f2543Smrgget_dst_timing_section(Uchar *c, struct std_timings *t,
533706f2543Smrg		       struct edid_version *v)
534706f2543Smrg{
535706f2543Smrg  int j;
536706f2543Smrg    c = c + 5;
537706f2543Smrg    for (j = 0; j < 5; j++) {
538706f2543Smrg	t[j].hsize = HSIZE1;
539706f2543Smrg	VSIZE1(t[j].vsize);
540706f2543Smrg	t[j].refresh = REFRESH_R;
541706f2543Smrg	t[j].id = STD_TIMING_ID;
542706f2543Smrg	NEXT_STD_TIMING;
543706f2543Smrg    }
544706f2543Smrg}
545706f2543Smrg
546706f2543Smrgstatic void
547706f2543Smrgget_monitor_ranges(Uchar *c, struct monitor_ranges *r)
548706f2543Smrg{
549706f2543Smrg    r->min_v = MIN_V;
550706f2543Smrg    r->max_v = MAX_V;
551706f2543Smrg    r->min_h = MIN_H;
552706f2543Smrg    r->max_h = MAX_H;
553706f2543Smrg    r->max_clock = 0;
554706f2543Smrg    if(MAX_CLOCK != 0xff) /* is specified? */
555706f2543Smrg	r->max_clock = MAX_CLOCK * 10 + 5;
556706f2543Smrg    if (HAVE_2ND_GTF) {
557706f2543Smrg	r->gtf_2nd_f = F_2ND_GTF;
558706f2543Smrg	r->gtf_2nd_c = C_2ND_GTF;
559706f2543Smrg	r->gtf_2nd_m = M_2ND_GTF;
560706f2543Smrg	r->gtf_2nd_k = K_2ND_GTF;
561706f2543Smrg	r->gtf_2nd_j = J_2ND_GTF;
562706f2543Smrg    } else {
563706f2543Smrg	r->gtf_2nd_f = 0;
564706f2543Smrg    }
565706f2543Smrg    if (HAVE_CVT) {
566706f2543Smrg	r->max_clock_khz = MAX_CLOCK_KHZ;
567706f2543Smrg	r->max_clock = r->max_clock_khz / 1000;
568706f2543Smrg	r->maxwidth = MAXWIDTH;
569706f2543Smrg	r->supported_aspect = SUPPORTED_ASPECT;
570706f2543Smrg	r->preferred_aspect = PREFERRED_ASPECT;
571706f2543Smrg	r->supported_blanking = SUPPORTED_BLANKING;
572706f2543Smrg	r->supported_scaling = SUPPORTED_SCALING;
573706f2543Smrg	r->preferred_refresh = PREFERRED_REFRESH;
574706f2543Smrg    } else {
575706f2543Smrg	r->max_clock_khz = 0;
576706f2543Smrg    }
577706f2543Smrg}
578706f2543Smrg
579706f2543Smrgstatic void
580706f2543Smrgget_whitepoint_section(Uchar *c, struct whitePoints *wp)
581706f2543Smrg{
582706f2543Smrg    wp[0].white_x = WHITEX1;
583706f2543Smrg    wp[0].white_y = WHITEY1;
584706f2543Smrg    wp[1].white_x = WHITEX2;
585706f2543Smrg    wp[1].white_y = WHITEY2;
586706f2543Smrg    wp[0].index  = WHITE_INDEX1;
587706f2543Smrg    wp[1].index  = WHITE_INDEX2;
588706f2543Smrg    wp[0].white_gamma  = WHITE_GAMMA1;
589706f2543Smrg    wp[1].white_gamma  = WHITE_GAMMA2;
590706f2543Smrg}
591706f2543Smrg
592706f2543Smrgstatic void
593706f2543Smrgget_detailed_timing_section(Uchar *c, struct detailed_timings *r)
594706f2543Smrg{
595706f2543Smrg  r->clock = PIXEL_CLOCK;
596706f2543Smrg  r->h_active = H_ACTIVE;
597706f2543Smrg  r->h_blanking = H_BLANK;
598706f2543Smrg  r->v_active = V_ACTIVE;
599706f2543Smrg  r->v_blanking = V_BLANK;
600706f2543Smrg  r->h_sync_off = H_SYNC_OFF;
601706f2543Smrg  r->h_sync_width = H_SYNC_WIDTH;
602706f2543Smrg  r->v_sync_off = V_SYNC_OFF;
603706f2543Smrg  r->v_sync_width = V_SYNC_WIDTH;
604706f2543Smrg  r->h_size = H_SIZE;
605706f2543Smrg  r->v_size = V_SIZE;
606706f2543Smrg  r->h_border = H_BORDER;
607706f2543Smrg  r->v_border = V_BORDER;
608706f2543Smrg  r->interlaced = INTERLACED;
609706f2543Smrg  r->stereo = STEREO;
610706f2543Smrg  r->stereo_1 = STEREO1;
611706f2543Smrg  r->sync = SYNC_T;
612706f2543Smrg  r->misc = MISC;
613706f2543Smrg}
614706f2543Smrg
615706f2543Smrg#define MAX_EDID_MINOR 4
616706f2543Smrg
617706f2543Smrgstatic Bool
618706f2543Smrgvalidate_version(int scrnIndex, struct edid_version *r)
619706f2543Smrg{
620706f2543Smrg    if (r->version != 1) {
621706f2543Smrg	xf86DrvMsg(scrnIndex, X_ERROR, "Unknown EDID version %d\n",
622706f2543Smrg		   r->version);
623706f2543Smrg	return FALSE;
624706f2543Smrg    }
625706f2543Smrg
626706f2543Smrg    if (r->revision > MAX_EDID_MINOR)
627706f2543Smrg	xf86DrvMsg(scrnIndex, X_WARNING,
628706f2543Smrg		   "Assuming version 1.%d is compatible with 1.%d\n",
629706f2543Smrg		   r->revision, MAX_EDID_MINOR);
630706f2543Smrg
631706f2543Smrg    return TRUE;
632706f2543Smrg}
633706f2543Smrg
634706f2543Smrg/*
635706f2543Smrg * Returns true if HDMI, false if definitely not or unknown.
636706f2543Smrg */
637706f2543SmrgBool
638706f2543Smrgxf86MonitorIsHDMI(xf86MonPtr mon)
639706f2543Smrg{
640706f2543Smrg    int i = 0, version, offset;
641706f2543Smrg    char *edid = NULL;
642706f2543Smrg
643706f2543Smrg    if (!mon)
644706f2543Smrg       return FALSE;
645706f2543Smrg
646706f2543Smrg    if (!(mon->flags & EDID_COMPLETE_RAWDATA))
647706f2543Smrg       return FALSE;
648706f2543Smrg
649706f2543Smrg    if (!mon->no_sections)
650706f2543Smrg       return FALSE;
651706f2543Smrg
652706f2543Smrg    edid = (char *)mon->rawData;
653706f2543Smrg    if (!edid)
654706f2543Smrg       return FALSE;
655706f2543Smrg
656706f2543Smrg    /* find the CEA extension block */
657706f2543Smrg    for (i = 1; i <= mon->no_sections; i++)
658706f2543Smrg       if (edid[i * 128] == 0x02)
659706f2543Smrg           break;
660706f2543Smrg    if (i == mon->no_sections + 1)
661706f2543Smrg       return FALSE;
662706f2543Smrg    edid += (i * 128);
663706f2543Smrg
664706f2543Smrg    version = edid[1];
665706f2543Smrg    offset = edid[2];
666706f2543Smrg    if (version < 3 || offset < 4)
667706f2543Smrg       return FALSE;
668706f2543Smrg
669706f2543Smrg    /* walk the cea data blocks */
670706f2543Smrg    for (i = 4; i < offset; i += (edid[i] & 0x1f) + 1) {
671706f2543Smrg       char *x = edid + i;
672706f2543Smrg
673706f2543Smrg       /* find a vendor specific block */
674706f2543Smrg       if ((x[0] & 0xe0) >> 5 == 0x03) {
675706f2543Smrg           int oui = (x[3] << 16) + (x[2] << 8) + x[1];
676706f2543Smrg
677706f2543Smrg           /* find the HDMI vendor OUI */
678706f2543Smrg           if (oui == 0x000c03)
679706f2543Smrg               return TRUE;
680706f2543Smrg       }
681706f2543Smrg    }
682706f2543Smrg
683706f2543Smrg    /* guess it's not HDMI after all */
684706f2543Smrg    return FALSE;
685706f2543Smrg}
686