1 | /* |
---|
2 | * Copyright (c) 1992-1998 Michael A. Cooper. |
---|
3 | * This software may be freely used and distributed provided it is not |
---|
4 | * sold for profit or used in part or in whole for commercial gain |
---|
5 | * without prior written agreement, and the author is credited |
---|
6 | * appropriately. |
---|
7 | */ |
---|
8 | |
---|
9 | #ifndef lint |
---|
10 | static char *RCSid = "$Revision: 1.1.1.1 $"; |
---|
11 | #endif |
---|
12 | |
---|
13 | /* |
---|
14 | * Common SysInfo EDID functions |
---|
15 | */ |
---|
16 | |
---|
17 | #include "defs.h" |
---|
18 | #include "edid.h" |
---|
19 | |
---|
20 | static u_char EdidHeader[] = EDID_HEADER; |
---|
21 | |
---|
22 | /* |
---|
23 | * Established Timings I, II, III in decreasing order (bit 7->0) |
---|
24 | */ |
---|
25 | static char *EstTimingI[] = { |
---|
26 | "720x400@70", "720x400@88", "640x480@60", "640x480@67", |
---|
27 | "640x480@72", "640x480@75", "800x600@56", "800x600@60", NULL |
---|
28 | }; |
---|
29 | static char *EstTimingII[] = { |
---|
30 | "800x600@72", "800x600@75", "832x624@75", "1024x768@87", |
---|
31 | "1024x768@60", "1024x768@70", "1024x768@75", "1280x1024@75", NULL |
---|
32 | }; |
---|
33 | static char *EstTimingIII[] = { |
---|
34 | "1152x870@75", NULL |
---|
35 | }; |
---|
36 | |
---|
37 | /* |
---|
38 | * Is Header an EDID Header? |
---|
39 | * Return TRUE if yes or FALSE if not. |
---|
40 | */ |
---|
41 | static int EdidIsHeader(Header) |
---|
42 | u_char *Header; |
---|
43 | { |
---|
44 | register int i; |
---|
45 | int Max; |
---|
46 | |
---|
47 | if (!Header) |
---|
48 | return(FALSE); |
---|
49 | |
---|
50 | Max = sizeof(EdidHeader) / sizeof(u_char); |
---|
51 | for (i = 0; i < Max; ++i) |
---|
52 | if (Header[i] != EdidHeader[i]) |
---|
53 | return(FALSE); |
---|
54 | |
---|
55 | return(TRUE); |
---|
56 | } |
---|
57 | |
---|
58 | /* |
---|
59 | * Get the Manufacturer ID from Edid. |
---|
60 | */ |
---|
61 | static char *EdidGetMan(Edid) |
---|
62 | EDIDv1_t *Edid; |
---|
63 | { |
---|
64 | static char Buff[4]; |
---|
65 | |
---|
66 | if (!Edid) |
---|
67 | return((char *) NULL); |
---|
68 | |
---|
69 | if (Edid->ManIDc1 == 0 && Edid->ManIDc2 == 0 && Edid->ManIDc3 == 0) { |
---|
70 | SImsg(SIM_DBG, "EDID: GetMan: No Manufacturer ID present."); |
---|
71 | return((char *) NULL); |
---|
72 | } |
---|
73 | |
---|
74 | (void) snprintf(Buff, sizeof(Buff), "%c%c%c", |
---|
75 | 'A' + Edid->ManIDc1 - 1, 'A' + Edid->ManIDc2 - 1, |
---|
76 | 'A' + Edid->ManIDc3 - 1); |
---|
77 | |
---|
78 | return(Buff); |
---|
79 | } |
---|
80 | |
---|
81 | /* |
---|
82 | * Get the Product ID from Edid. |
---|
83 | */ |
---|
84 | static char *EdidGetProductID(Edid) |
---|
85 | EDIDv1_t *Edid; |
---|
86 | { |
---|
87 | static char Buff[32]; |
---|
88 | |
---|
89 | if (!Edid) |
---|
90 | return((char *) NULL); |
---|
91 | |
---|
92 | if (Edid->ProductIdCode[0] == 0 && Edid->ProductIdCode[1] == 0) { |
---|
93 | SImsg(SIM_DBG, "EDID: GetProductID: No Product ID present."); |
---|
94 | return((char *) NULL); |
---|
95 | } |
---|
96 | |
---|
97 | /* |
---|
98 | * Stored LSB |
---|
99 | */ |
---|
100 | (void) snprintf(Buff, sizeof(Buff), "%02X%02X", |
---|
101 | Edid->ProductIdCode[1], Edid->ProductIdCode[0]); |
---|
102 | |
---|
103 | return(Buff); |
---|
104 | } |
---|
105 | |
---|
106 | /* |
---|
107 | * Get the Serial from Edid. |
---|
108 | */ |
---|
109 | static char *EdidGetSerial(Edid) |
---|
110 | EDIDv1_t *Edid; |
---|
111 | { |
---|
112 | static char Buff[32]; |
---|
113 | |
---|
114 | if (!Edid) |
---|
115 | return((char *) NULL); |
---|
116 | |
---|
117 | if (Edid->Serial[0] == 0 && Edid->Serial[1] == 0 && |
---|
118 | Edid->Serial[2] == 0 && Edid->Serial[3] == 0) { |
---|
119 | SImsg(SIM_DBG, "EDID: GetSerial: No Serial ID present."); |
---|
120 | return((char *) NULL); |
---|
121 | } |
---|
122 | |
---|
123 | if (Edid->Serial[0] == 0x1 && Edid->Serial[1] == 0x1 && |
---|
124 | Edid->Serial[2] == 0x1 && Edid->Serial[3] == 0x1) { |
---|
125 | SImsg(SIM_DBG, "EDID: GetSerial: Serial ID unused."); |
---|
126 | return((char *) NULL); |
---|
127 | } |
---|
128 | |
---|
129 | (void) snprintf(Buff, sizeof(Buff), "%02X%02X%02X%02X", |
---|
130 | Edid->Serial[0], Edid->Serial[1], |
---|
131 | Edid->Serial[2], Edid->Serial[3]); |
---|
132 | |
---|
133 | return(Buff); |
---|
134 | } |
---|
135 | |
---|
136 | /* |
---|
137 | * Set Monitor Range info into DevInfo |
---|
138 | */ |
---|
139 | static int EdidSetRange(DevInfo, Range) |
---|
140 | DevInfo_t *DevInfo; |
---|
141 | MonRange_t *Range; |
---|
142 | { |
---|
143 | if (!DevInfo || !Range) |
---|
144 | return(-1); |
---|
145 | |
---|
146 | if (Range->MinVertical) |
---|
147 | AddDevDesc(DevInfo, itoa(Range->MinVertical), |
---|
148 | "RG: Minimum Vertical Rate (Hz)", DA_APPEND); |
---|
149 | if (Range->MaxVertical) |
---|
150 | AddDevDesc(DevInfo, itoa(Range->MaxVertical), |
---|
151 | "RG: Maximum Vertical Rate (Hz)", DA_APPEND); |
---|
152 | if (Range->MinHorizontal) |
---|
153 | AddDevDesc(DevInfo, itoa(Range->MinHorizontal), |
---|
154 | "RG: Minimum Horizontal Rate (KHz)", DA_APPEND); |
---|
155 | if (Range->MaxHorizontal) |
---|
156 | AddDevDesc(DevInfo, itoa(Range->MaxHorizontal), |
---|
157 | "RG: Maximum Horizontal Rate (KHz)", DA_APPEND); |
---|
158 | if (Range->MaxPixelClock) |
---|
159 | AddDevDesc(DevInfo, itoa(Range->MaxPixelClock * 10), |
---|
160 | "RG: Maximum Pixel Clock (MHz)", DA_APPEND); |
---|
161 | |
---|
162 | return(0); |
---|
163 | } |
---|
164 | |
---|
165 | /* |
---|
166 | * Set specific Established Timing Info |
---|
167 | */ |
---|
168 | static int EdidSetEstTiming(DevInfo, Timing, EstTimings) |
---|
169 | DevInfo_t *DevInfo; |
---|
170 | u_char Timing; |
---|
171 | char **EstTimings; |
---|
172 | { |
---|
173 | register int i; |
---|
174 | register int e; |
---|
175 | u_char mask; |
---|
176 | |
---|
177 | for (i = 1, e = 0, mask = 1; i < 8 && EstTimings[e]; ++i, ++e) { |
---|
178 | if (Timing & mask) |
---|
179 | DevAddRes(DevInfo, EstTimings[e]); |
---|
180 | mask <<= 1; |
---|
181 | } |
---|
182 | |
---|
183 | return(0); |
---|
184 | } |
---|
185 | |
---|
186 | /* |
---|
187 | * Set Established Timing Info |
---|
188 | */ |
---|
189 | static int EdidSetEstTimings(DevInfo, Timings, NumTimings) |
---|
190 | DevInfo_t *DevInfo; |
---|
191 | u_char *Timings; |
---|
192 | size_t NumTimings; |
---|
193 | { |
---|
194 | if (NumTimings >= 1) |
---|
195 | EdidSetEstTiming(DevInfo, Timings[0], EstTimingI); |
---|
196 | if (NumTimings >= 2) |
---|
197 | EdidSetEstTiming(DevInfo, Timings[1], EstTimingII); |
---|
198 | if (NumTimings >= 3) |
---|
199 | EdidSetEstTiming(DevInfo, Timings[2], EstTimingIII); |
---|
200 | |
---|
201 | return(0); |
---|
202 | } |
---|
203 | |
---|
204 | /* |
---|
205 | * Set Standard Timing Info |
---|
206 | */ |
---|
207 | static int EdidSetStdTimings(DevInfo, Timings, NumTimings) |
---|
208 | DevInfo_t *DevInfo; |
---|
209 | StdTiming_t *Timings; |
---|
210 | size_t NumTimings; |
---|
211 | { |
---|
212 | static char Buff[32]; |
---|
213 | register int t; |
---|
214 | register StdTiming_t *Ptr; |
---|
215 | int HorPixels; |
---|
216 | int VerPixels; |
---|
217 | float Multi; |
---|
218 | |
---|
219 | for (t = 0, Ptr = Timings; t < NumTimings; ++t, ++Ptr) { |
---|
220 | if (Ptr->HorActivePixels == 0 || |
---|
221 | Ptr->HorActivePixels == 0x1) /* Unused? */ |
---|
222 | continue; |
---|
223 | /* |
---|
224 | * Get the Aspect Ratio and use it as a multiplier |
---|
225 | */ |
---|
226 | switch (Ptr->Aspect) { |
---|
227 | case 0: Multi = (float)1 / (float)1; break; |
---|
228 | case 1: Multi = (float)3 / (float)4; break; |
---|
229 | case 2: Multi = (float)4 / (float)5; break; |
---|
230 | case 3: Multi = (float)9 / (float)16; break; |
---|
231 | default: |
---|
232 | SImsg(SIM_DBG, "%s: Unknown Aspect Ratio type: %d", |
---|
233 | DevInfo->Name, Ptr->Aspect); |
---|
234 | continue; |
---|
235 | } |
---|
236 | /* |
---|
237 | * Magic formulas as specified by VESA EDID standard. |
---|
238 | */ |
---|
239 | HorPixels = ((Ptr->HorActivePixels - 1) * 8) + 256; |
---|
240 | VerPixels = (int)( (float)HorPixels * (float)Multi ); |
---|
241 | (void) snprintf(Buff, sizeof(Buff), "%dx%d@%d", |
---|
242 | HorPixels, VerPixels, Ptr->RefreshRate + 60); |
---|
243 | |
---|
244 | DevAddRes(DevInfo, strdup(Buff)); |
---|
245 | } |
---|
246 | |
---|
247 | return(0); |
---|
248 | } |
---|
249 | |
---|
250 | /* |
---|
251 | * Set Detailed Timing Info |
---|
252 | */ |
---|
253 | static int EdidSetDetail(DevInfo, Details, NumDetails) |
---|
254 | DevInfo_t *DevInfo; |
---|
255 | Detail_t *Details; |
---|
256 | size_t NumDetails; |
---|
257 | { |
---|
258 | register Detail_t *Ptr; |
---|
259 | register char *cp; |
---|
260 | register int Blk; |
---|
261 | char *String = NULL; |
---|
262 | char *Model = NULL; |
---|
263 | char *Serial = NULL; |
---|
264 | |
---|
265 | if (!DevInfo || !Details) |
---|
266 | return(-1); |
---|
267 | |
---|
268 | for (Blk = 0, Ptr = Details; Blk < NumDetails; ++Blk, ++Ptr) { |
---|
269 | if (Ptr->Flag1 != 0) { |
---|
270 | SImsg(SIM_DBG, "%s: EDID: Detail Block %d has Flag1=0x%x", |
---|
271 | DevInfo->Name, Blk, Ptr->Flag1); |
---|
272 | /* |
---|
273 | * This must be a Detailed Timing Descriptor |
---|
274 | * Skip for now. |
---|
275 | */ |
---|
276 | continue; |
---|
277 | } else { |
---|
278 | switch (Ptr->DataType) { |
---|
279 | case MDD_NAME: |
---|
280 | cp = CleanString(Ptr->Data, sizeof(Ptr->Data), 0); |
---|
281 | if (cp && strlen(cp)) |
---|
282 | DevInfo->Model = Model = cp; |
---|
283 | break; |
---|
284 | case MDD_SERIAL: |
---|
285 | cp = CleanString(Ptr->Data, sizeof(Ptr->Data), 0); |
---|
286 | if (cp && strlen(cp)) |
---|
287 | DevInfo->Serial = Serial = cp; |
---|
288 | break; |
---|
289 | case MDD_STRING: |
---|
290 | /* Not sure what's stored here so we'll print out on debug */ |
---|
291 | cp = CleanString(Ptr->Data, sizeof(Ptr->Data), 0); |
---|
292 | if (cp && strlen(cp)) |
---|
293 | String = cp; |
---|
294 | break; |
---|
295 | case MDD_RANGE: |
---|
296 | EdidSetRange(DevInfo, (MonRange_t *) Ptr->Data); |
---|
297 | break; |
---|
298 | case MDD_STDTIMING: |
---|
299 | EdidSetStdTimings(DevInfo, (StdTiming_t *) Ptr->Data, 6); |
---|
300 | break; |
---|
301 | default: |
---|
302 | if (Ptr->DataType > 0x00 && Ptr->DataType <= 0x0F) |
---|
303 | SImsg(SIM_DBG, |
---|
304 | "%s: EDID: Ignoring manufacturer DataType=0x%X", |
---|
305 | DevInfo->Name, Ptr->DataType); |
---|
306 | else |
---|
307 | SImsg(SIM_DBG, "%s: EDID: Ignoring Detail DataType=0x%X", |
---|
308 | DevInfo->Name, Ptr->DataType); |
---|
309 | } |
---|
310 | } |
---|
311 | } |
---|
312 | |
---|
313 | SImsg(SIM_DBG, "%s: EDID: Model=<%s> Serial=<%s> String=<%s>", |
---|
314 | DevInfo->Name, PRTS(Model), PRTS(Serial), PRTS(String)); |
---|
315 | |
---|
316 | return(0); |
---|
317 | } |
---|
318 | |
---|
319 | /* |
---|
320 | * Decode EDID Version 1 |
---|
321 | */ |
---|
322 | static int EdidV1Decode(Query) |
---|
323 | Query_t *Query; |
---|
324 | { |
---|
325 | static EDIDv1_t Edid; |
---|
326 | static char Buff[256]; |
---|
327 | DevInfo_t *DevInfo = NULL; |
---|
328 | Define_t *Define; |
---|
329 | char *DevName = NULL; |
---|
330 | char *ManID = NULL; |
---|
331 | char *ProductID = NULL; |
---|
332 | register char *cp; |
---|
333 | Monitor_t *Mon = NULL; |
---|
334 | |
---|
335 | if (!Query || Query->DataLen != EDID_V1_SIZE || !Query->Data || |
---|
336 | !Query->DevInfo) |
---|
337 | return(-1); |
---|
338 | |
---|
339 | /* |
---|
340 | * Setup what vars we're going to use |
---|
341 | */ |
---|
342 | DevInfo = Query->DevInfo; |
---|
343 | if (DevInfo && DevInfo->Name) |
---|
344 | DevName = DevInfo->Name; |
---|
345 | else |
---|
346 | DevName = "unknown"; |
---|
347 | if (DevInfo->DevSpec) |
---|
348 | Mon = (Monitor_t *) DevInfo->DevSpec; |
---|
349 | else { |
---|
350 | Mon = NewMonitor(NULL); |
---|
351 | DevInfo->DevSpec = (void *) Mon; |
---|
352 | } |
---|
353 | |
---|
354 | (void) memcpy(&Edid, Query->Data, EDID_V1_SIZE); |
---|
355 | |
---|
356 | /* |
---|
357 | * It's possible to have EDID data that's not really EDID. |
---|
358 | */ |
---|
359 | if (!EdidIsHeader(Edid.Header)) { |
---|
360 | SImsg(SIM_DBG, "%s: EDID present, but header is not EDID.", DevName); |
---|
361 | return(-1); |
---|
362 | } |
---|
363 | |
---|
364 | SImsg(SIM_DBG, "%s: EDID present: Version %u Revision %u ExtFlag=%d", |
---|
365 | DevName, Edid.EDIDversion, Edid.EDIDrevision, Edid.ExtFlag); |
---|
366 | |
---|
367 | (void) snprintf(Buff, sizeof(Buff), "%u", Edid.EDIDversion); |
---|
368 | AddDevDesc(DevInfo, Buff, "EDID Version", DA_APPEND); |
---|
369 | (void) snprintf(Buff, sizeof(Buff), "%u", Edid.EDIDrevision); |
---|
370 | AddDevDesc(DevInfo, Buff, "EDID Revision", DA_APPEND); |
---|
371 | |
---|
372 | /* |
---|
373 | * Get the Manufacturer and Product ID codes |
---|
374 | */ |
---|
375 | ManID = EdidGetMan(&Edid); |
---|
376 | ProductID = EdidGetProductID(&Edid); |
---|
377 | SImsg(SIM_DBG, "%s: EDID ManID=<%s> ProductID=<%s>", |
---|
378 | DevName, PRTS(ManID), PRTS(ProductID)); |
---|
379 | /* |
---|
380 | * See if we can look up translated names for ManID and ProductID |
---|
381 | */ |
---|
382 | if (ManID) { |
---|
383 | AddDevDesc(DevInfo, ManID, "EDID Manufacturer ID", DA_APPEND); |
---|
384 | if(Define = DefGet("EISAvendors", ManID, 0, 0)) |
---|
385 | DevInfo->Vendor = Define->ValStr1; |
---|
386 | else |
---|
387 | SImsg(SIM_DBG, "%s: `%s' is not in `EISAvendors' in config/*.cf", |
---|
388 | DevName, ManID); |
---|
389 | } |
---|
390 | if (ProductID) |
---|
391 | AddDevDesc(DevInfo, ProductID, "EDID Product ID", DA_APPEND); |
---|
392 | if (ProductID && ManID) { |
---|
393 | (void) snprintf(Buff, sizeof(Buff), "%s%s", ManID, ProductID); |
---|
394 | if (Define = DefGet("EISAdevices", Buff, 0, 0)) |
---|
395 | DevInfo->Model = Define->ValStr1; |
---|
396 | else |
---|
397 | SImsg(SIM_DBG, "%s: `%s' is not in `EISAdevices' in config/*.cf", |
---|
398 | DevName, Buff); |
---|
399 | } |
---|
400 | |
---|
401 | if (!DevInfo->Vendor) |
---|
402 | DevInfo->Vendor = strdup(ManID); |
---|
403 | if (!DevInfo->Model) |
---|
404 | DevInfo->Model = strdup(ProductID); |
---|
405 | |
---|
406 | if (cp = EdidGetSerial(&Edid)) |
---|
407 | DevInfo->Serial = strdup(cp); |
---|
408 | |
---|
409 | (void) EdidSetEstTimings(DevInfo, Edid.EstTimings, |
---|
410 | sizeof(Edid.EstTimings)/sizeof(u_char)); |
---|
411 | (void) EdidSetStdTimings(DevInfo, Edid.StdTimings, |
---|
412 | sizeof(Edid.StdTimings)/sizeof(StdTiming_t)); |
---|
413 | (void) EdidSetDetail(DevInfo, Edid.Details, |
---|
414 | sizeof(Edid.Details)/sizeof(Detail_t)); |
---|
415 | |
---|
416 | /* |
---|
417 | * Get Manufacture date (MM/YYYY) |
---|
418 | */ |
---|
419 | if (Edid.ManWeek && Edid.ManYear) { |
---|
420 | (void) snprintf(Buff, sizeof(Buff), "%d/%d", |
---|
421 | (int)(Edid.ManWeek / 4.33), Edid.ManYear + 1990); |
---|
422 | AddDevDesc(DevInfo, Buff, "Manufacture Date", DA_APPEND); |
---|
423 | } |
---|
424 | |
---|
425 | if (Edid.HorImageSize && Edid.VerImageSize) { |
---|
426 | Mon->MaxHorSize = Edid.HorImageSize; |
---|
427 | Mon->MaxVerSize = Edid.VerImageSize; |
---|
428 | } |
---|
429 | |
---|
430 | /* |
---|
431 | * Determine Class Type |
---|
432 | */ |
---|
433 | switch (Edid.DisplayType) { |
---|
434 | case 0: DevInfo->ClassType = CT_MONO; break; |
---|
435 | case 1: DevInfo->ClassType = CT_RGBCOLOR; break; |
---|
436 | case 2: DevInfo->ClassType = CT_NONRGBCOLOR; break; |
---|
437 | default: |
---|
438 | SImsg(SIM_DBG, "%s: %d is an unknown DisplayType value from EDID.", |
---|
439 | DevInfo->Name, Edid.DisplayType); |
---|
440 | } |
---|
441 | |
---|
442 | AddDevDesc(DevInfo, (Edid.SigType) ? "Digital" : "Analog", |
---|
443 | "Signal Input Type", DA_APPEND); |
---|
444 | |
---|
445 | if (Edid.DpmsStandBy) |
---|
446 | AddDevDesc(DevInfo, "Stand-By", "DPMS Support", DA_APPEND); |
---|
447 | if (Edid.DpmsSuspend) |
---|
448 | AddDevDesc(DevInfo, "Suspend", "DPMS Support", DA_APPEND); |
---|
449 | if (Edid.DpmsActiveOff) |
---|
450 | AddDevDesc(DevInfo, "Active-Off", "DPMS Support", DA_APPEND); |
---|
451 | |
---|
452 | if (Edid.StdColorSpace) |
---|
453 | AddDevDesc(DevInfo, "Uses standard default color space", |
---|
454 | NULL, DA_APPEND); |
---|
455 | |
---|
456 | if (Edid.HasPrefTiming) |
---|
457 | AddDevDesc(DevInfo, "Preferred Timing in 1st Detail Block", |
---|
458 | "Has", DA_APPEND); |
---|
459 | |
---|
460 | if (Edid.HasGtf) |
---|
461 | AddDevDesc(DevInfo, "GTF Standard Timings", "Has", DA_APPEND); |
---|
462 | |
---|
463 | return(0); |
---|
464 | } |
---|
465 | |
---|
466 | /* |
---|
467 | * Decode an EDID structure and set Query->DevInfo with what we find. |
---|
468 | */ |
---|
469 | extern int EdidDecode(Query) |
---|
470 | Query_t *Query; |
---|
471 | { |
---|
472 | if (!Query) |
---|
473 | return(-1); |
---|
474 | |
---|
475 | /* |
---|
476 | * We only support V1 (128-byte) EDID right now |
---|
477 | */ |
---|
478 | if (Query->DataLen == EDID_V1_SIZE) |
---|
479 | return EdidV1Decode(Query); |
---|
480 | else { |
---|
481 | SImsg(SIM_DBG, "%s: EdidDecode: Unknown EDID Structure size: %d", |
---|
482 | (Query->DevInfo && Query->DevInfo->Name) ? |
---|
483 | Query->DevInfo->Name : "unknown", Query->DataLen); |
---|
484 | return(-1); |
---|
485 | } |
---|
486 | } |
---|