File i2c-tools-hackweek-9-improve-DDR3-support.diff of Package python-smbus

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
@@ -1186,36 +1186,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);
@@ -1249,17 +1350,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)) {
@@ -1269,14 +1369,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)",
@@ -1312,43 +1436,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);
@@ -1359,17 +1471,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)
@@ -1470,26 +1592,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)