root/i2c-tools/trunk/eeprom/ddcmon

Revision 5164, 13.1 kB (checked in by khali, 8 months ago)

Update the FSF address.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 # Copyright (C) 2004-2005  Jean Delvare <khali@linux-fr.org>
4 #
5 # Parts inspired from decode-edid.
6 # Copyright (C) 2003-2004  Jean Delvare <khali@linux-fr.org>
7 #
8 # Parts inspired from the ddcmon driver and sensors' print_ddcmon function.
9 # Copyright (C) 1998-2004  Mark D. Studebaker
10 #
11 # Parts inspired from the fbmon driver (Linux 2.6.10).
12 # Copyright (C) 2002  James Simmons <jsimmons@users.sf.net>
13 #
14 #    This program is free software; you can redistribute it and/or modify
15 #    it under the terms of the GNU General Public License as published by
16 #    the Free Software Foundation; either version 2 of the License, or
17 #    (at your option) any later version.
18 #
19 #    This program is distributed in the hope that it will be useful,
20 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
21 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 #    GNU General Public License for more details.
23 #
24 #    You should have received a copy of the GNU General Public License
25 #    along with this program; if not, write to the Free Software
26 #    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 #    MA 02110-1301 USA.
28 #
29 # Version 1.0  2005-01-04  Jean Delvare <khali@linux-fr.org>
30 #
31 # This script is a replacement for the now deprecated ddcmon kernel driver.
32 # Instead of having a dedicated driver, it is better to reuse the standard
33 # eeprom driver and implement the EDID-specific code in user-space.
34 #
35 # EDID (Extended Display Identification Data) is a VESA standard which
36 # allows storing (on manufacturer's side) and retrieving (on user's side)
37 # of configuration information about displays, such as manufacturer,
38 # serial number, physical dimensions and allowed horizontal and vertical
39 # refresh rates.
40 #
41 # Syntax: ddcmon [bus [address]]
42 # Address can be given in decimal or hexadecimal (with a 0x prefix).
43 # If no address is given, default is 0x50.
44 # Bus number must be given in decimal. If no bus number is given,
45 # try them all.
46
47 use strict;
48 use Fcntl qw(:DEFAULT :seek);
49 use vars qw(@standard_scales);
50
51 @standard_scales = ([1, 1], [3, 4], [4, 5], [16, 9]);
52
53 # Make sure the eeprom module is loaded.
54 # For non-modular kernels, we can't help.
55 if (-r '/proc/modules')
56 {
57         my $found = 0;
58         open(MODULES, '/proc/modules');
59         while (!$found && defined ($_ = <MODULES>))
60         {
61                 $found++ if m/^eeprom\s+/;
62         }
63         close(MODULES);
64
65         unless ($found)
66         {
67                 print STDERR
68                         "This script requires the eeprom module to be loaded.\n";
69                 exit 1;
70         }
71 }
72
73 # Only used for sysfs
74 sub rawread
75 {
76         my $filename = shift;
77         my $length = shift;
78         my $offset = shift || 0;
79         my $bytes = '';
80        
81         sysopen(FH, $filename, O_RDONLY)
82                 or die "Can't open $filename";
83         if ($offset)
84         {
85                 sysseek(FH, $offset, SEEK_SET)
86                         or die "Can't seek in $filename";
87         }
88
89         $offset = 0;
90         while ($length)
91         {
92                 my $r = sysread(FH, $bytes, $length, $offset);
93                 die "Can't read $filename"
94                         unless defined($r);
95                 die "Unexpected EOF in $filename"
96                         unless $r;
97                 $offset += $r;
98                 $length -= $r;
99         }
100         close(FH);
101
102         return $bytes;
103 }
104
105 sub get_edid_sysfs
106 {
107         my ($bus, $addr) = @_;
108
109         my @bytes = unpack("C*", rawread("/sys/bus/i2c/devices/$bus-00$addr/eeprom", 128));
110
111         return \@bytes;
112 }
113
114 sub get_edid_procfs
115 {
116         my ($bus, $addr) = @_;
117
118         my @bytes;
119
120         for (my $i = 0 ; $i < 0x80; $i += 0x10)
121         {
122                 my $filename = sprintf("/proc/sys/dev/sensors/eeprom-i2c-\%s-\%s/\%02x",
123                                        $bus, $addr, $i);
124                 open(EEDATA, $filename)
125                         or die "Can't read $filename";
126                 push @bytes, split(/\s+/, <EEDATA>);
127                 close(EEDATA);
128         }
129
130         return \@bytes;
131 }
132
133 sub print_line
134 {
135         my $label = shift;
136         my $pattern = shift;
137
138         printf("\%-24s$pattern\n", $label.':', @_);
139 }
140
141 sub extract_byte
142 {
143         my ($bytes, $offset) = @_;
144        
145         return $bytes->[$offset];
146 }
147
148 sub extract_word
149 {
150         my ($bytes, $offset) = @_;
151        
152         return ($bytes->[$offset]
153               | ($bytes->[$offset+1] << 8));
154 }
155
156 sub extract_manufacturer
157 {
158         my ($bytes, $offset) = @_;
159         my $i = ($bytes->[$offset+1] | ($bytes->[$offset] << 8));
160
161         return sprintf('%c%c%c',
162                        (($i >> 10) & 0x1f) + ord('A') - 1,
163                        (($i >> 5) & 0x1f) + ord('A') - 1,
164                        ($i & 0x1f) + ord('A') - 1);
165 }
166
167 sub extract_sesquiword
168 {
169         my ($bytes, $offset) = @_;
170        
171         return ($bytes->[$offset]
172               | ($bytes->[$offset+1] << 8)
173               | ($bytes->[$offset+2] << 16));
174 }
175
176 sub extract_dword
177 {
178         my ($bytes, $offset) = @_;
179        
180         return ($bytes->[$offset]
181               | ($bytes->[$offset+1] << 8)
182               | ($bytes->[$offset+2] << 16)
183               | ($bytes->[$offset+3] << 24));
184 }
185
186 sub extract_display_input
187 {
188         my ($bytes, $offset) = @_;
189
190         my @voltage = ('0.700V/0.300V', '0.714V/0.286V',
191                        '1.000V/0.400V', '0.700V/0.000V');
192
193         return 'Digital'
194                 if ($bytes->[$offset] & 0x80);
195
196         return 'Analog ('.$voltage[($bytes->[$offset] & 0x60) >> 5].')';
197 }
198
199 sub extract_dpms
200 {
201         my ($bytes, $offset) = @_;
202
203         my @supported;
204
205         push @supported, 'Active Off' if ($bytes->[$offset] & 0x20);
206         push @supported, 'Suspend' if ($bytes->[$offset] & 0x40);
207         push @supported, 'Standby' if ($bytes->[$offset] & 0x80);
208
209         return join(', ', @supported)
210                 if (@supported);
211
212         return 'None supported';
213 }
214
215 sub extract_color_mode
216 {
217         my ($bytes, $offset) = @_;
218
219         my @mode = ('Monochrome', 'RGB Multicolor', 'Non-RGB Multicolor');
220
221         return $mode[($bytes->[$offset] >> 3) & 0x03];
222 }
223  
224 sub good_signature
225 {
226         my $bytes = shift;
227        
228         return $bytes->[0] == 0x00
229             && $bytes->[1] == 0xff
230             && $bytes->[2] == 0xff
231             && $bytes->[3] == 0xff
232             && $bytes->[4] == 0xff
233             && $bytes->[5] == 0xff
234             && $bytes->[6] == 0xff
235             && $bytes->[7] == 0x00;
236 }
237
238 sub verify_checksum
239 {
240         my $bytes = shift;
241         my $cs;
242
243         for (my $i = 0, $cs = 0; $i < 0x80; $i++)
244         {
245                 $cs += $bytes->[$i];
246         }
247        
248         return (($cs & 0xff) == 0 ? 'OK' : 'Not OK');
249 }
250
251 sub add_timing
252 {
253         my ($timings, $new) = @_;
254
255         my $mode = sprintf('%ux%u@%u%s', $new->[0], $new->[1],
256                         $new->[2], defined ($new->[3]) &&
257                         $new->[3] eq 'interlaced' ? 'i' : '');
258
259         $timings->{$mode} = $new;
260 }
261
262 sub add_standard_timing
263 {
264         my ($timings, $byte0, $byte1) = @_;
265
266         # Unused slot
267         return if ($byte0 == $byte1)
268                && ($byte0 == 0x01 || $byte0 == 0x00 || $byte0 == 0x20);
269
270         my $width = ($byte0 + 31) * 8;
271         my $height = $width * $standard_scales[$byte1 >> 6]->[0]
272                             / $standard_scales[$byte1 >> 6]->[1];
273         my $refresh = 60 + ($byte1 & 0x3f);
274
275         add_timing($timings, [$width, $height, $refresh]);
276 }
277
278 sub sort_timings
279 {
280         # First order by width
281         return -1 if  $a->[0] < $b->[0];
282         return 1 if  $a->[0] > $b->[0];
283
284         # Second by height
285         return -1 if  $a->[1] < $b->[1];
286         return 1 if  $a->[1] > $b->[1];
287
288         # Third by frequency
289         # Interlaced modes count for half their frequency
290         my $freq_a = $a->[2];
291         my $freq_b = $b->[2];
292         $freq_a /= 2 if defined $a->[3] && $a->[3] eq 'interlaced';
293         $freq_b /= 2 if defined $b->[3] && $b->[3] eq 'interlaced';
294         return -1 if $freq_a < $freq_b;
295         return 1 if $freq_a > $freq_b;
296
297         return 0;
298 }
299
300 sub print_timings
301 {
302         my ($bytes, $timings) = @_;
303
304         # Established Timings
305         my @established =
306         (
307                 [720, 400, 70],
308                 [720, 400, 88],
309                 [640, 480, 60],
310                 [640, 480, 67],
311                 [640, 480, 72],
312                 [640, 480, 75],
313                 [800, 600, 56],
314                 [800, 600, 60],
315                 [800, 600, 72],
316                 [800, 600, 75],
317                 [832, 624, 75],
318                 [1024, 768, 87, 'interlaced'],
319                 [1024, 768, 60],
320                 [1024, 768, 70],
321                 [1024, 768, 75],
322                 [1280, 1024, 75],
323                 undef, undef, undef,
324                 [1152, 870, 75],
325         );
326         my $temp = extract_sesquiword($bytes, 0x23);
327         for (my $i = 0; $i < 24; $i++)
328         {
329                 next unless defined($established[$i]);
330                 add_timing($timings, $established[$i])
331                         if ($temp & (1 << $i));
332         }
333
334         # Standard Timings
335         for (my $i = 0x26; $i < 0x36; $i += 2)
336         {
337                 add_standard_timing($timings, $bytes->[$i], $bytes->[$i+1]);
338         }
339
340         foreach my $v (sort sort_timings values(%{$timings}))
341         {
342                 print_line("Timing", '%ux%u @ %u Hz%s',
343                            $v->[0], $v->[1], $v->[2],
344                            defined($v->[3]) ? ' ('.$v->[3].')' : '');
345         }
346 }
347
348 sub extract_string
349 {
350         my ($bytes, $offset) = @_;
351         my $string = '';
352
353         for (my $i = 5; $i < 18; $i++)
354         {
355                 last if $bytes->[$offset+$i] == 0x0a
356                      || $bytes->[$offset+$i] == 0x00;
357                 $string .= chr($bytes->[$offset+$i])
358                         if ($bytes->[$offset+$i] >= 32
359                         && $bytes->[$offset+$i] < 127);
360         }
361         $string =~ s/\s+$//;
362
363         return $string;
364 }
365
366 # Some blocks contain different information:
367 #   0x00, 0x00, 0x00, 0xfa: Additional standard timings block
368 #   0x00, 0x00, 0x00, 0xfc: Monitor block
369 #   0x00, 0x00, 0x00, 0xfd: Limits block
370 #   0x00, 0x00, 0x00, 0xfe: Ascii block
371 #   0x00, 0x00, 0x00, 0xff: Serial block
372 # Return a reference to a hash containing all information.
373 sub extract_detailed_timings
374 {
375         my ($bytes) = @_;
376
377         my %info = ('timings' => {});
378
379         for (my $offset = 0x36; $offset < 0x7e; $offset += 18)
380         {
381                 if ($bytes->[$offset] == 0x00
382                  && $bytes->[$offset+1] == 0x00
383                  && $bytes->[$offset+2] == 0x00
384                  && $bytes->[$offset+4] == 0x00)
385                 {
386                         if ($bytes->[$offset+3] == 0xfa)
387                         {
388                                 for (my $i = $offset + 5; $i < $offset + 17; $i += 2)
389                                 {
390                                         add_standard_timing($info{'timings'},
391                                                             $bytes->[$i],
392                                                             $bytes->[$i+1]);
393                                 }
394                         }
395
396                         elsif ($bytes->[$offset+3] == 0xfc)
397                         {
398                                 $info{'monitor'} .= extract_string($bytes, $offset);
399                         }
400
401                         elsif ($bytes->[$offset+3] == 0xfd)
402                         {
403                                 $info{'limits'}{'vsync_min'} = $bytes->[$offset+5];
404                                 $info{'limits'}{'vsync_max'} = $bytes->[$offset+6];
405                                 $info{'limits'}{'hsync_min'} = $bytes->[$offset+7];
406                                 $info{'limits'}{'hsync_max'} = $bytes->[$offset+8];
407                                 $info{'limits'}{'clock_max'} = $bytes->[$offset+9];
408                         }
409
410                         elsif ($bytes->[$offset+3] == 0xfe)
411                         {
412                                 $info{'ascii'} .= extract_string($bytes, $offset);
413                         }
414
415                         elsif ($bytes->[$offset+3] == 0xff)
416                         {
417                                 $info{'serial'} .= extract_string($bytes, $offset);
418                         }
419
420                         next;
421                 }
422
423                 # Detailed Timing
424                 my $width = $bytes->[$offset+2] + (($bytes->[$offset+4] & 0xf0) << 4);
425                 my $height = $bytes->[$offset+5] + (($bytes->[$offset+7] & 0xf0) << 4);
426                 my $clock = extract_word($bytes, $offset) * 10000;
427                 my $hblank = $bytes->[$offset+3] + (($bytes->[$offset+4] & 0x0f) << 8);
428                 my $vblank = $bytes->[$offset+6] + (($bytes->[$offset+7] & 0x0f) << 8);
429                 my $area = ($width + $hblank) * ($height + $vblank);
430                 next unless $area; # Should not happen, but...
431                 my $refresh = ($clock + $area / 2) / $area; # Proper rounding
432                 add_timing($info{'timings'}, [$width, $height, $refresh]);
433         }
434
435         return \%info;
436 }
437
438 sub print_edid
439 {
440         my ($bus, $address) = @_;
441         my $bytes;
442
443         if (-r "/sys/bus/i2c/devices/$bus-00$address/eeprom")
444         {
445                 $bytes = get_edid_sysfs($bus, $address);
446         }
447         elsif (-r "/proc/sys/dev/sensors/eeprom-i2c-$bus-$address/00")
448         {
449                 $bytes = get_edid_procfs($bus, $address);
450         }
451
452         return 1 unless defined $bytes;
453         return 2 unless good_signature($bytes);
454
455         print_line('Checksum', '%s', verify_checksum($bytes));
456         my $edid_version = extract_byte($bytes, 0x12);
457         my $edid_revision = extract_byte($bytes, 0x13);
458         print_line('EDID Version', '%u.%u', $edid_version,
459                    $edid_revision);
460         if ($edid_version > 1 || $edid_revision > 2)
461         {
462                 $standard_scales[0][0] = 16;
463                 $standard_scales[0][1] = 10;
464         }
465         else
466         {
467                 $standard_scales[0][0] = 1;
468                 $standard_scales[0][1] = 1;
469         }
470
471         my $info = extract_detailed_timings($bytes);
472
473         print_line('Manufacturer ID', '%s', extract_manufacturer($bytes, 0x08));
474         print_line('Model Number', '0x%04X', extract_word($bytes, 0x0A));
475         print_line('Model Name', '%s', $info->{'monitor'})
476                 if defined $info->{'monitor'};
477
478         if ($info->{'serial'})
479         {
480                 print_line('Serial Number', '%s', $info->{'serial'})
481         }
482         elsif ((my $temp = extract_dword($bytes, 0x0C)))
483         {
484                 print_line('Serial Number', '%u', $temp)
485         }
486
487         print_line('Manufacture Time', '%u-W%02u',
488                    1990 + extract_byte($bytes, 0x11),
489                    extract_byte($bytes, 0x10));
490         print_line('Display Input', '%s', extract_display_input($bytes, 0x14));
491         print_line('Monitor Size (cm)', '%ux%u', extract_byte($bytes, 0x15),
492                    extract_byte($bytes, 0x16));
493         print_line('Gamma Factor', '%.2f',
494                    1 + extract_byte($bytes, 0x17) / 100.0);
495         print_line('DPMS Modes', '%s', extract_dpms($bytes, 0x18));
496         print_line('Color Mode', '%s', extract_color_mode($bytes, 0x18))
497                 if (($bytes->[0x18] & 0x18) != 0x18);
498         print_line('Additional Info', '%s', $info->{'ascii'})
499                 if $info->{'ascii'};
500
501         if (defined($info->{'limits'}))
502         {
503                 print_line('Vertical Sync (Hz)', '%u-%u',
504                            $info->{'limits'}{'vsync_min'},
505                            $info->{'limits'}{'vsync_max'});
506                 print_line('Horizontal Sync (kHz)', '%u-%u',
507                            $info->{'limits'}{'hsync_min'},
508                            $info->{'limits'}{'hsync_max'});
509                 print_line('Max Pixel Clock (MHz)', '%u',
510                            $info->{'limits'}{'clock_max'} * 10)
511                         unless $info->{'limits'}{'clock_max'} == 0xff;
512         }
513
514         print_timings($bytes, $info->{'timings'});
515         print("\n");
516         return 0;
517 }
518
519 # Get the address. Default to 0x50 if not given.
520 my $address;
521 if (defined($ARGV[1]))
522 {
523         $address = $ARGV[1];
524         # Convert to decimal, whatever the value.
525         $address = oct $address if $address =~ m/^0/;
526         # Convert to an hexadecimal string.
527         $address = sprintf '%02x', $address;
528 }
529 else
530 {
531         $address = '50';
532 }
533
534 if (defined($ARGV[0]))
535 {
536         my $error = print_edid($ARGV[0], $address);
537        
538         if ($error == 1)
539         {
540                 print STDERR
541                         "No EEPROM found at 0x$address on bus $ARGV[0].\n";
542                 exit 1;
543         }
544         elsif ($error == 2)
545         {
546                 print STDERR
547                         "EEPROM found at 0x$address on bus $ARGV[0], but is not an EDID EEPROM.\n";
548                 exit 1;
549         }
550 }
551 # If no bus is given, try them all.
552 else
553 {
554         my $total = 0;
555
556         for (my $i = 0; $i < 16; $i++)
557         {
558                 $total++ unless print_edid($i, $address);
559         }
560
561         unless ($total)
562         {
563                 print STDERR
564                         "No EDID EEPROM found.\n";
565                 exit 1;
566         }
567 }
Note: See TracBrowser for help on using the browser.