i2c-tools/i2c-tools-hackweek-9-improve-DDR3-support.diff

374 lines
13 KiB
Diff
Raw Normal View History

Subject: decode-dimms: Improve DDR3 support
Upstream: yes, r6132 to r6149
This is my hackweek 9 project:
https://github.com/SUSE/hackweek/wiki/DDR3-SPD-Information-Decoding
---
eeprom/decode-dimms | 271 +++++++++++++++++++++++++++++++++++++---------------
1 file changed, 195 insertions(+), 76 deletions(-)
--- a/eeprom/decode-dimms
+++ b/eeprom/decode-dimms
@@ -5,7 +5,7 @@
# Copyright 1998, 1999 Philip Edelbrock <phil@netroedge.com>
# modified by Christian Zuckschwerdt <zany@triq.net>
# modified by Burkart Lingner <burkart@bollchen.de>
-# Copyright (C) 2005-2011 Jean Delvare <khali@linux-fr.org>
+# Copyright (C) 2005-2013 Jean Delvare <khali@linux-fr.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -1176,36 +1176,137 @@ sub decode_ddr2_sdram($)
printl("PLL Relock Time", $bytes->[46] . " us") if ($bytes->[46]);
}
+# Return combined time in ns
+sub ddr3_mtb_ftb($$$$)
+{
+ my ($byte1, $byte2, $mtb, $ftb) = @_;
+
+ # byte1 is unsigned in ns, but byte2 is signed in ps
+ $byte2 -= 0x100 if $byte2 & 0x80;
+
+ return $byte1 * $mtb + $byte2 * $ftb / 1000;
+}
+
+sub ddr3_reference_card($$)
+{
+ my ($rrc, $ext) = @_;
+ my $alphabet = "ABCDEFGHJKLMNPRTUVWY";
+ my $ref = $rrc & 0x1f;
+ my $revision = $ext >> 5;
+ my $ref_card;
+
+ return "ZZ" if $ref == 0x1f;
+ $ref += 0x1f if $rrc & 0x80;
+ $revision = (($rrc >> 5) & 0x03) if $revision == 0;
+
+ if ($ref < length($alphabet)) {
+ # One letter reference card
+ $ref_card = substr($alphabet, $ref, 1);
+ } else {
+ # Two letter reference card
+ my $ref1 = int($ref / (length($alphabet)));
+ $ref -= length($alphabet) * $ref1;
+ $ref_card = substr($alphabet, $ref1, 1) .
+ substr($alphabet, $ref, 1);
+ }
+
+ return "$ref_card revision $revision";
+}
+
+sub ddr3_revision_number($)
+{
+ my $h = $_[0] >> 4;
+ my $l = $_[0] & 0x0f;
+
+ # Decode as suggested by JEDEC Standard 21-C
+ return sprintf("%d", $l) if $h == 0;
+ return sprintf("%d.%d", $h, $l) if $h < 0xa;
+ return sprintf("%c%d", ord('A') + $h - 0xa, $l);
+}
+
+sub ddr3_device_type($)
+{
+ my $byte = shift;
+ my $type = $byte & 0x80 ? "Non-Standard" : "Standard Monolithic";
+ my $die_count = ($byte >> 4) & 0x07;
+ my $loading = ($byte >> 2) & 0x03;
+
+ if ($die_count == 1) {
+ $type .= "\nSingle die";
+ } elsif ($die_count == 2) {
+ $type .= "\n2 die";
+ } elsif ($die_count == 3) {
+ $type .= "\n4 die";
+ } elsif ($die_count == 4) {
+ $type .= "\n8 die";
+ }
+
+ if ($loading == 1) {
+ $type .= "\nMulti load stack";
+ } elsif ($loading == 2) {
+ $type .= "\nSingle load stack";
+ }
+
+ return $type;
+}
+
+use constant DDR3_UNBUFFERED => 1;
+use constant DDR3_REGISTERED => 2;
+use constant DDR3_CLOCKED => 3;
+use constant DDR3_LOAD_REDUCED => 4;
+
# Parameter: EEPROM bytes 0-127 (using 3-76)
sub decode_ddr3_sdram($)
{
my $bytes = shift;
my $temp;
my $ctime;
+ my ($ftb, $mtb);
+ my $ii;
- my @module_types = ("Undefined", "RDIMM", "UDIMM", "SO-DIMM",
- "Micro-DIMM", "Mini-RDIMM", "Mini-UDIMM",
- "Mini-CDIMM", "72b-SO-UDIMM", "72b-SO-RDIMM",
- "72b-SO-CDIMM", "LRDIMM", "16b-SO-DIMM",
- "32b-SO-DIMM");
+ my @module_types = (
+ { type => "Undefined", width => "Unknown" },
+ { type => "RDIMM", width => "133.35 mm", family => DDR3_REGISTERED },
+ { type => "UDIMM", width => "133.35 mm", family => DDR3_UNBUFFERED },
+ { type => "SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
+ { type => "Micro-DIMM", width => "TBD", family => DDR3_UNBUFFERED },
+ { type => "Mini-RDIMM", width => "82.0 mm", family => DDR3_REGISTERED },
+ { type => "Mini-UDIMM", width => "82.0 mm", family => DDR3_UNBUFFERED },
+ { type => "Mini-CDIMM", width => "67.6 mm", family => DDR3_CLOCKED },
+ { type => "72b-SO-UDIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
+ { type => "72b-SO-RDIMM", width => "67.6 mm", family => DDR3_REGISTERED },
+ { type => "72b-SO-CDIMM", width => "67.6 mm", family => DDR3_CLOCKED },
+ { type => "LRDIMM", width => "133.35 mm", family => DDR3_LOAD_REDUCED },
+ { type => "16b-SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
+ { type => "32b-SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
+ );
printl("Module Type", ($bytes->[3] <= $#module_types) ?
- $module_types[$bytes->[3]] :
+ $module_types[$bytes->[3]]->{type} :
sprintf("Reserved (0x%.2X)", $bytes->[3]));
+# time bases
+ if (($bytes->[9] & 0x0f) == 0 || $bytes->[11] == 0) {
+ print STDERR "Invalid time base divisor, can't decode\n";
+ return;
+ }
+ $ftb = ($bytes->[9] >> 4) / ($bytes->[9] & 0x0f);
+ $mtb = $bytes->[10] / $bytes->[11];
+
# speed
prints("Memory Characteristics");
- my $dividend = ($bytes->[9] >> 4) & 15;
- my $divisor = $bytes->[9] & 15;
- printl("Fine time base", sprintf("%.3f", $dividend / $divisor) . " ps");
-
- $dividend = $bytes->[10];
- $divisor = $bytes->[11];
- my $mtb = $dividend / $divisor;
- printl("Medium time base", tns3($mtb));
+ $ctime = ddr3_mtb_ftb($bytes->[12], $bytes->[34], $mtb, $ftb);
+ # Starting with DDR3-1866, vendors may start approximating the
+ # minimum cycle time. Try to guess what they really meant so
+ # that the reported speed matches the standard.
+ for ($ii = 7; $ii < 15; $ii++) {
+ if ($ctime > 7.5/$ii - $ftb/1000 && $ctime < 7.5/$ii + $ftb/1000) {
+ $ctime = 7.5/$ii;
+ last;
+ }
+ }
- $ctime = $bytes->[12] * $mtb;
my $ddrclk = 2 * (1000 / $ctime);
my $tbits = 1 << (($bytes->[8] & 7) + 3);
my $pcclk = int ($ddrclk * $tbits / 8);
@@ -1237,17 +1338,16 @@ sub decode_ddr3_sdram($)
my $trp;
my $tras;
- $taa = ceil($bytes->[16] / $bytes->[12]);
- $trcd = ceil($bytes->[18] / $bytes->[12]);
- $trp = ceil($bytes->[20] / $bytes->[12]);
- $tras = ceil(((($bytes->[21] & 0x0f) << 8) + $bytes->[22]) / $bytes->[12]);
+ $taa = ddr3_mtb_ftb($bytes->[16], $bytes->[35], $mtb, $ftb);
+ $trcd = ddr3_mtb_ftb($bytes->[18], $bytes->[36], $mtb, $ftb);
+ $trp = ddr3_mtb_ftb($bytes->[20], $bytes->[37], $mtb, $ftb);
+ $tras = ((($bytes->[21] & 0x0f) << 8) + $bytes->[22]) * $mtb;
- printl("tCL-tRCD-tRP-tRAS", join("-", $taa, $trcd, $trp, $tras));
+ printl("tCL-tRCD-tRP-tRAS", ddr_core_timings(ceil($taa / $ctime), $ctime, $trcd, $trp, $tras));
# latencies
my $highestCAS = 0;
my %cas;
- my $ii;
my $cas_sup = ($bytes->[15] << 8) + $bytes->[14];
for ($ii = 0; $ii < 15; $ii++) {
if ($cas_sup & (1 << $ii)) {
@@ -1257,14 +1357,38 @@ sub decode_ddr3_sdram($)
}
printl("Supported CAS Latencies (tCL)", cas_latencies(keys %cas));
+# standard DDR3 speeds
+ prints("Timings at Standard Speeds");
+ foreach my $ctime_at_speed (7.5/8, 7.5/7, 1.25, 1.5, 1.875, 2.5) {
+ my $best_cas = 0;
+
+ # Find min CAS latency at this speed
+ for ($ii = 14; $ii >= 0; $ii--) {
+ next unless ($cas_sup & (1 << $ii));
+ if (ceil($taa / $ctime_at_speed) <= $ii + 4) {
+ $best_cas = $ii + 4;
+ }
+ }
+
+ printl_cond($best_cas && $ctime_at_speed >= $ctime,
+ "tCL-tRCD-tRP-tRAS" . as_ddr(3, $ctime_at_speed),
+ ddr_core_timings($best_cas, $ctime_at_speed,
+ $trcd, $trp, $tras));
+ }
+
# more timing information
prints("Timing Parameters");
+ printl("Minimum Cycle Time (tCK)", tns3($ctime));
+ printl("Minimum CAS Latency Time (tAA)", tns3($taa));
printl("Minimum Write Recovery time (tWR)", tns3($bytes->[17] * $mtb));
+ printl("Minimum RAS# to CAS# Delay (tRCD)", tns3($trcd));
printl("Minimum Row Active to Row Active Delay (tRRD)",
tns3($bytes->[19] * $mtb));
+ printl("Minimum Row Precharge Delay (tRP)", tns3($trp));
+ printl("Minimum Active to Precharge Delay (tRAS)", tns3($tras));
printl("Minimum Active to Auto-Refresh Delay (tRC)",
- tns3((((($bytes->[21] >> 4) & 15) << 8) + $bytes->[23]) * $mtb));
+ tns3(ddr3_mtb_ftb((($bytes->[21] & 0xf0) << 4) + $bytes->[23], $bytes->[38], $mtb, $ftb)));
printl("Minimum Recovery Delay (tRFC)",
tns3((($bytes->[25] << 8) + $bytes->[24]) * $mtb));
printl("Minimum Write to Read CMD Delay (tWTR)",
@@ -1300,43 +1424,31 @@ sub decode_ddr3_sdram($)
($bytes->[31] & 8) ? "Yes" : "No");
printl("Partial Array Self-Refresh?",
($bytes->[31] & 128) ? "Yes" : "No");
- printl("Thermal Sensor Accuracy",
- ($bytes->[32] & 128) ? sprintf($bytes->[32] & 127) :
- "Not implemented");
- printl("SDRAM Device Type",
- ($bytes->[33] & 128) ? sprintf($bytes->[33] & 127) :
- "Standard Monolithic");
- if ($bytes->[3] >= 1 && $bytes->[3] <= 6) {
+ printl("Module Thermal Sensor",
+ ($bytes->[32] & 128) ? "Yes" : "No");
+ printl("SDRAM Device Type", ddr3_device_type($bytes->[33]));
+
+ # Following bytes are type-specific, so don't continue if type
+ # isn't known.
+ return if $bytes->[3] == 0 || $bytes->[3] > $#module_types;
+ if ($module_types[$bytes->[3]]->{family} == DDR3_UNBUFFERED ||
+ $module_types[$bytes->[3]]->{family} == DDR3_REGISTERED ||
+ $module_types[$bytes->[3]]->{family} == DDR3_CLOCKED ||
+ $module_types[$bytes->[3]]->{family} == DDR3_LOAD_REDUCED) {
prints("Physical Characteristics");
- printl("Module Height (mm)", ($bytes->[60] & 31) + 15);
- printl("Module Thickness (mm)", sprintf("%d front, %d back",
+ printl("Module Height", (($bytes->[60] & 31) + 15) . " mm");
+ printl("Module Thickness", sprintf("%d mm front, %d mm back",
($bytes->[61] & 15) + 1,
(($bytes->[61] >> 4) & 15) +1));
- printl("Module Width (mm)", ($bytes->[3] <= 2) ? 133.5 :
- ($bytes->[3] == 3) ? 67.6 : "TBD");
+ printl("Module Width", $module_types[$bytes->[3]]->{width});
+ printl("Module Reference Card", ddr3_reference_card($bytes->[62], $bytes->[60]));
- my $alphabet = "ABCDEFGHJKLMNPRTUVWY";
- my $ref = $bytes->[62] & 31;
- my $ref_card;
- if ($ref == 31) {
- $ref_card = "ZZ";
- } else {
- if ($bytes->[62] & 128) {
- $ref += 31;
- }
- if ($ref < length $alphabet) {
- $ref_card = substr $alphabet, $ref, 1;
- } else {
- my $ref1 = int($ref / (length $alphabet));
- $ref -= (length $alphabet) * $ref1;
- $ref_card = (substr $alphabet, $ref1, 1) .
- (substr $alphabet, $ref, 1);
- }
- }
- printl("Module Reference Card", $ref_card);
+ printl_cond($module_types[$bytes->[3]]->{family} == DDR3_UNBUFFERED,
+ "Rank 1 Mapping", $bytes->[63] & 0x01 ? "Mirrored" : "Standard");
}
- if ($bytes->[3] == 1 || $bytes->[3] == 5) {
+
+ if ($module_types[$bytes->[3]]->{family} == DDR3_REGISTERED) {
prints("Registered DIMM");
my @rows = ("Undefined", 1, 2, 4);
@@ -1347,17 +1459,27 @@ sub decode_ddr3_sdram($)
printl("Register device type",
(($bytes->[68] & 7) == 0) ? "SSTE32882" :
"Undefined");
- printl("Register revision", sprintf("0x%.2X", $bytes->[67]));
- printl("Heat spreader characteristics",
- ($bytes->[64] < 128) ? "Not incorporated" :
- sprintf("%.2X", ($bytes->[64] & 127)));
- my $regs;
- for (my $i = 0; $i < 8; $i++) {
- $regs = sprintf("SSTE32882 RC%d/RC%d",
- $i * 2, $i * 2 + 1);
- printl($regs, sprintf("%.2X", $bytes->[$i + 69]));
- }
+ printl_cond($bytes->[67] != 0xff,
+ "Register revision", ddr3_revision_number($bytes->[67]));
+ printl("Heat spreader", $bytes->[64] & 0x80 ? "Yes" : "No");
}
+
+ if ($module_types[$bytes->[3]]->{family} == DDR3_LOAD_REDUCED) {
+ prints("Load Reduced DIMM");
+
+ my @rows = ("Undefined", 1, 2, "Reserved");
+ printl("# DRAM Rows", $rows[($bytes->[63] >> 2) & 3]);
+ my @mirroring = ("None", "Odd ranks", "Reserved", "Reserved");
+ printl("Mirroring", $mirroring[$bytes->[63] & 3]);
+ printl("Rank Numbering", $bytes->[63] & 0x20 ? "Even only" : "Contiguous");
+ printl("Buffer Orientation", $bytes->[63] & 0x10 ? "Horizontal" : "Vertical");
+ printl("Register manufacturer",
+ manufacturer_ddr3($bytes->[65], $bytes->[66]));
+ printl_cond($bytes->[64] != 0xff,
+ "Buffer Revision", ddr3_revision_number($bytes->[64]));
+ printl("Heat spreader", $bytes->[63] & 0x80 ? "Yes" : "No");
+ }
+
}
# Parameter: EEPROM bytes 0-127 (using 4-5)
@@ -1458,26 +1580,23 @@ sub decode_ddr3_mfg_data($)
printl("Module Manufacturer",
manufacturer_ddr3($bytes->[117], $bytes->[118]));
- if (spd_written(@{$bytes}[148..149])) {
- printl("DRAM Manufacturer",
- manufacturer_ddr3($bytes->[148], $bytes->[149]));
- }
+ printl_cond(spd_written(@{$bytes}[148..149]),
+ "DRAM Manufacturer",
+ manufacturer_ddr3($bytes->[148], $bytes->[149]));
printl_mfg_location_code($bytes->[119]);
- if (spd_written(@{$bytes}[120..121])) {
- printl("Manufacturing Date",
- manufacture_date($bytes->[120], $bytes->[121]));
- }
+ printl_cond(spd_written(@{$bytes}[120..121]),
+ "Manufacturing Date",
+ manufacture_date($bytes->[120], $bytes->[121]));
printl_mfg_assembly_serial(@{$bytes}[122..125]);
printl("Part Number", part_number(@{$bytes}[128..145]));
- if (spd_written(@{$bytes}[146..147])) {
- printl("Revision Code",
- sprintf("0x%02X%02X", $bytes->[146], $bytes->[147]));
- }
+ printl_cond(spd_written(@{$bytes}[146..147]),
+ "Revision Code",
+ sprintf("0x%02X%02X", $bytes->[146], $bytes->[147]));
}
# Parameter: EEPROM bytes 0-127 (using 64-98)