summaryrefslogtreecommitdiff
path: root/util/spd_tools/ddr4
diff options
context:
space:
mode:
authorRob Barnes <robbarnes@google.com>2020-08-26 12:47:27 -0600
committerFurquan Shaikh <furquan@google.com>2020-08-27 20:14:34 +0000
commit196e9c002122d3469ab486d2f40af1e66dcdf067 (patch)
tree0dff97ec9060bd6c056a7c49f96e64e7ce15812d /util/spd_tools/ddr4
parent1860cd460aaf3dabb58d996b81d51916949e59b3 (diff)
util/spd_tools: Remove intel subfolder
Move ddr4 and lp4x to spd_tools root folder. The tool now applies to non intel platforms. BUG=b:162939176 TEST=Run tool Signed-off-by: Rob Barnes <robbarnes@google.com> Change-Id: I0941ea036d760ee27eb34f259f4506a4b7584bee Reviewed-on: https://review.coreboot.org/c/coreboot/+/44844 Reviewed-by: Furquan Shaikh <furquan@google.com> Reviewed-by: Nick Vaccaro <nvaccaro@google.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'util/spd_tools/ddr4')
-rw-r--r--util/spd_tools/ddr4/README.md275
-rw-r--r--util/spd_tools/ddr4/gen_part_id.go215
-rw-r--r--util/spd_tools/ddr4/gen_spd.go1386
-rw-r--r--util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt37
4 files changed, 1913 insertions, 0 deletions
diff --git a/util/spd_tools/ddr4/README.md b/util/spd_tools/ddr4/README.md
new file mode 100644
index 0000000000..31e326d442
--- /dev/null
+++ b/util/spd_tools/ddr4/README.md
@@ -0,0 +1,275 @@
+# DDR4 SPD tools README
+
+Tools for generating SPD files for DDR4 memory used in memory down
+configurations on Intel Tiger Lake (TGL) based
+platforms. These tools generate SPDs following JESD79-4C
+and Jedec 4.1.2.L-5 R29 v103 specifications.
+
+There are two tools provided that assist TGL based mainboards
+to generate SPDs and Makefile to integrate these SPDs in coreboot
+build. These tools can also be used to allocate DRAM IDs (configure
+DRAM hardware straps) for any DDR4 memory part used by the board.
+
+* gen_spd.go: Generates de-duplicated SPD files using a global memory
+ part list provided by the mainboard in JSON format. Additionally,
+ generates a SPD manifest file(in CSV format) with information about
+ what memory part from the global list uses which of the generated
+ SPD files.
+
+* gen_part_id.go: Allocates DRAM strap IDs for different DDR4
+ memory parts used by the board. Takes as input list of memory parts
+ used by the board (with one memory part on each line) and the SPD
+ manifest file generated by gen_spd.go. Generates Makefile.inc for
+ integrating the generated SPD files in the coreboot build.
+
+## Tool 1 - gen_spd.go
+
+This program takes as input:
+* Pointer to directory where the generated SPD files and manifest will
+ be placed.
+* JSON file containing a global list of memory parts with their
+ attributes as per the datasheet. This is the list of all known
+ DDR4 memory parts irrespective of their usage on the board.
+* SoC platform name for which the SPDs are being generated. Currently
+ supported platform names are `TGL`.
+
+Input JSON file requires the following two fields for every memory part:
+* `name`: Name of the memory part
+* `attribs`: List of attributes of the memory part as per its
+ datasheet. These attributes match the part specifications and are
+ independent of any SoC expectations. Tool takes care of translating
+ the physical attributes of the memory part to match JEDEC and Intel
+ MRC expectations.
+
+`attribs` field further contains two types of sub-fields:
+* Mandatory: These attributes have to be provided for a memory part.
+* Optional: These attributes can be provided by memory part if it wants
+ to override the defaults.
+
+### Mandatory `attribs`
+
+* `speedMTps`: Maximum rate supported by the part in MT/s. Valid values:
+ `1600, 1866, 2133, 2400, 2666, 2933, 3200` MT/s.
+
+* `CL_nRCD_nRP`: Refers to CAS Latency specified for the part (find
+ "CL-nRCD-nRP" in the vendor spec for the DDR4 part).
+
+* `capacityPerDieGb`: Capacity per die in gigabits. Valid values:
+ `2, 4, 8, 16` Gb part.
+
+* `diesPerPackage`: Number of dies on the part. Valid values:
+ `1, 2` dies per package.
+
+* `deviceBusWidth`: Number of bits of the device's address bus. Valid values:
+ `8, 16` bit-wide bus. NOTE: Width of x4 is not supported by this tool.
+
+* `ranksPerPackage`: From Jedec doc 4_01_02_AnnexL-1R23:
+ “Package ranks per DIMM” refers to the collections of devices on the module
+ sharing common chip select signals (across the data width of the DIMM),
+ either from the edge connector for unbuffered modules or from the outputs of
+ a registering clock driver for RDIMMs and LRDIMMs.Number of bits of the
+ device's address bus. Valid values:
+ `1, 2` package ranks.
+
+### Optional `attribs`
+
+The following options are calculated by the tool based on the mandatory
+attributes described for the part, but there may be cases where a default value
+must be overridden, such as when a device appears to be 3200AA, but does not
+support all of the CAS latencies typically supported by a speed bin 3200AA part.
+Do deal with such a case, the variable can be overridden here and the tool will
+use this value instead of calculating one. All values must be defined in
+picosecond units, except for "CASLatencies", which would be represented as a
+string like "9 10 11 12 14".
+
+ * `TAAMinPs`: Defines the minimum CAS Latency.
+ Table 48 of Jedec doc 4_01_02_AnnexL-5R29 lists tAAmin for each speed grade.
+
+ * `TRASMinPs`: Refers to the minimum active to precharge delay time.
+ Table 55 of Jedec doc 4_01_02_AnnexL-5R29 lists tRPmin for each speed grade.
+
+ * `TCKMinPs`: Refers to the minimum clock cycle time.
+ Table 42 of Jedec doc 4_01_02_AnnexL-5R29 lists tCKmin for each speed grade.
+
+ * `TCKMaxPs`:Refers to the minimum clock cycle time.
+ Table 44 of Jedec doc 4_01_02_AnnexL-5R29 lists tCKmin for each speed grade.
+
+ * `TRFC1MinPs`: Refers to the minimum refresh recovery delay time.
+ Table 59 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC1min for each page size.
+
+ * `TRFC2MinPs`: Refers to the minimum refresh recovery delay time.
+ Table 61 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC2min for each page size.
+
+ * `TRFC4MinPs`: Refers to the minimum refresh recovery delay time.
+ Table 63 of Jedec doc 4_01_02_AnnexL-5R29 lists tRFC4min for each page size.
+
+ * `TFAWMinPs`:: Refers to the minimum four activate window delay time.
+ Table 66 of Jedec doc 4_01_02_AnnexL-5R29 lists tFAWmin for each speed grade
+ and page size combination.
+
+ * `TRRDSMinPs`: Refers to the minimum activate to activate delay time to
+ different bank groups.
+ Table 68 of Jedec doc 4_01_02_AnnexL-5R29 lists tRRD_Smin for each speed grade
+ and page size combination.
+
+ * `TRRDLMinPs`: Refers to the minimum activate to activate delay time to the
+ same bank group.
+ Table 70 of Jedec doc 4_01_02_AnnexL-5R29 lists tRRD_Lmin for each speed grade
+ and page size combination.
+
+ * `TCCDLMinPs`: Refers to the minimum CAS to CAS delay time to same bank group.
+ Table 72 of Jedec doc 4_01_02_AnnexL-5R29 lists tCCD_Lmin for each speed grade.
+
+ * `TWRMinPs`: Refers to the minimum write recovery time.
+ Table 75 of Jedec doc 4_01_02_AnnexL-5R29 lists tWRmin for each ddr4 type.
+
+ * `TWTRSMinPs`: Refers to minimum write to read time to different bank group.
+ Table 78 of Jedec doc 4_01_02_AnnexL-5R29 lists tWTR_Smin for each ddr4 type.
+
+ * `TWTRLMinPs`: Refers to minimum write to read time to same bank group.
+ Table 80 of Jedec doc 4_01_02_AnnexL-5R29 lists tWTR_Lmin for each ddr4 type.
+
+ * `CASLatencies`: Refers to the CAS latencies supported by the part.
+ The speed bin tables in the back of Jedec doc 4_01_02_AnnexL-5R29 define the
+ standard CAS latencies that a speed bin part is supposed to support.
+ In cases where a part does not support all of the CAS latencies listed in the
+ speed bin tables, this entry should be used to override the default settings.
+
+### Example JSON file
+```
+{
+ "parts": [
+ {
+ "name": "MEMORY_PART_A",
+ "attribs": {
+ "speedMTps": 3200,
+ "CL_nRCD_nRP": 22
+ "capacityPerDieGb": 8,
+ "diesPerPackage": 2,
+ "deviceBusWidth": 16,
+ "ranksPerPackage": 1,
+ }
+ },
+ {
+ "name": "MEMORY_PART_B",
+ "attribs": {
+ "speedMTps": 3200,
+ "CL_nRCD_nRP": 22
+ "capacityPerDieGb": 8,
+ "diesPerPackage": 1,
+ "deviceBusWidth": 16,
+ "ranksPerPackage": 2,
+ "casLatencies": "9 10 11 12 13 14 15 16 17 18 19 20",
+ "tCKMaxPs": "1250"
+ }
+ }
+ ]
+}
+```
+
+### Output
+
+This tool generates the following files using the global list of
+memory parts in JSON format as described above:
+ * De-duplicated SPDs required for the different memory parts. These
+ SPD files are named (ddr4-spd-1.hex, ddr4-spd-2.hex, and so on)
+ and placed in the directory provided as an input to the tool.
+ * CSV file representing which of the deduplicated SPD files is used
+ by which memory part. This file is named as
+ `spd_manifest.generated.txt` and placed in the directory provided
+ as an input to the tool along with the generated SPD
+ files. Example CSV file:
+ ```
+ MEMORY_PART_A, ddr4-spd-1.hex
+ MEMORY_PART_B, ddr4-spd-2.hex
+ MEMORY_PART_C, ddr4-spd-3.hex
+ MEMORY_PART_D, ddr4-spd-2.hex
+ MEMORY_PART_E, ddr4-spd-2.hex
+ ```
+
+## Tool 2 - gen_part_id.go
+
+This program takes as input:
+* Pointer to directory where the SPD files and the manifest file
+ `spd_manifest.generated.txt` (in CSV format) are placed by
+ gen_spd.go
+* File containing list of memory parts used by the board. Each line of
+ the file is supposed to contain one memory part `name` as present in
+ the global list of memory parts provided to gen_spd.go
+* Pointer to directory where the generated Makefile.inc should be
+ placed by the tool.
+
+### Output
+
+This program provides the following:
+
+* Prints out the list of DRAM hardware strap IDs that should be
+ allocated to each memory part listed in the input file.
+* Makefile.inc is generated in the provided directory to integrate
+ SPDs generated by gen_spd.go with the coreboot build for the board.
+* dram_id.generated.txt is generated in the same directory as
+ Makefile. This contains the part IDs assigned to the different
+ memory parts. (Useful to integrate in board schematics).
+
+Sample output (dram_id.generated.txt):
+```
+DRAM Part Name ID to assign
+MEMORY_PART_A 0 (0000)
+MEMORY_PART_B 1 (0001)
+MEMORY_PART_C 2 (0010)
+MEMORY_PART_D 1 (0001)
+```
+
+Sample Makefile.inc:
+```
+## SPDX-License-Identifier: GPL-2.0-or-later
+## This is an auto-generated file. Do not edit!!
+
+SPD_SOURCES =
+SPD_SOURCES += ddr4-spd-1.hex # ID = 0(0b0000) Parts = MEMORY_PART_A
+SPD_SOURCES += ddr4-spd-2.hex # ID = 1(0b0001) Parts = MEMORY_PART_B, MEMORY_PART_D
+SPD_SOURCES += ddr4-spd-3.hex # ID = 2(0b0010) Parts = MEMORY_PART_C
+```
+
+### Note of caution
+
+This program assigns DRAM IDs using the order of DRAM part names
+provided in the input file. Thus, when adding a new memory part to the
+list, it should always go to the end of the input text file. This
+guarantees that the memory parts that were already assigned IDs do not
+change.
+
+## How to build the tools?
+```
+# go build gen_spd.go
+# go build gen_part_id.go
+```
+
+## How to use the tools?
+```
+# ./gen_spd <spd_dir> <mem_parts_list_json> <platform>
+# ./gen_part_id <spd_dir> <makefile_dir> <mem_parts_used_file>
+```
+
+## Example Usage
+```
+# ./gen_spd ../../../../src/soc/intel/tigerlake/spd/ddr4 ./global_ddr4_mem_parts.json.txt 'TGL'
+
+```
+
+### Need to add a new memory part for a board?
+
+* If the memory part is not present in the global list of memory
+ parts, then add the memory part name and attributes as per the
+ datasheet to the file containing the global list.
+ * Use `gen_spd.go` with input as the file containing the global list
+ of memory parts to generate de-duplicated SPDs.
+ * If a new SPD file is generated, use `git add` to add it to the
+ tree and push a CL for review.
+* Update the file containing memory parts used by board (variant) to
+ add the new memory part name at the end of the file.
+ * Use gen_part_id.go providing it pointer to the location where SPD
+ files are stored and file containing the list of memory parts used
+ by the board(variant).
+ * Use `git add` to add `Makefile.inc` and `dram_id.generated.txt`
+ with updated changes and push a CL for review.
diff --git a/util/spd_tools/ddr4/gen_part_id.go b/util/spd_tools/ddr4/gen_part_id.go
new file mode 100644
index 0000000000..f67b4a9434
--- /dev/null
+++ b/util/spd_tools/ddr4/gen_part_id.go
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+package main
+
+import (
+ "encoding/csv"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+/*
+ * This program allocates DRAM strap IDs for different parts that are being used by the variant.
+ *
+ * It expects the following inputs:
+ * Pointer to SPD directory. This is the location where SPD files and SPD Manifest generated by
+ * gen_spd.go are placed.
+ * Pointer to Makefile directory. Makefile.inc generated by this program is placed in this
+ * location.
+ * Text file containing a list of memory parts names used by the board. Each line in the file
+ * is expected to have one memory part name.
+ */
+const (
+ SPDManifestFileName = "spd_manifest.generated.txt"
+ MakefileName = "Makefile.inc"
+ DRAMIdFileName = "dram_id.generated.txt"
+)
+
+func usage() {
+ fmt.Printf("\nUsage: %s <spd_dir> <makefile_dir> <mem_parts_used_file>\n\n", os.Args[0])
+ fmt.Printf(" where,\n")
+ fmt.Printf(" spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n")
+ fmt.Printf(" makefile_dir = Directory path where generated Makefile.inc should be placed\n")
+ fmt.Printf(" mem_parts_used_file = File containing list of memory parts used by the board\n\n\n")
+}
+
+func checkArgs() error {
+
+ for _, arg := range os.Args[1:] {
+ if _, err := os.Stat(arg); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+/*
+ * Read input file that contains list of memory part names used by the variant (one on a line)
+ * and split into separate strings for each part name.
+ */
+func readParts(memPartsUsedFileName string) ([]string, error) {
+ lines, err := ioutil.ReadFile(memPartsUsedFileName)
+ if err != nil {
+ return nil, err
+ }
+ str := string(lines)
+ parts := strings.Split(str, "\n")
+
+ return parts, nil
+}
+
+/*
+ * Read SPD manifest file(CSV) generated by gen_spd program and generate two maps:
+ * 1. Part to SPD Map : This maps global memory part name to generated SPD file name
+ * 2. SPD to Index Map: This generates a map of deduplicated SPD file names to index assigned to
+ * that SPD. This function sets index for all SPDs to -1. This index gets
+ * updated as part of genPartIdInfo() depending upon the SPDs actually used
+ * by the variant.
+ */
+func readSPDManifest(SPDDirName string) (map[string]string, map[string]int, error) {
+ f, err := os.Open(filepath.Join(SPDDirName, SPDManifestFileName))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer f.Close()
+ r := csv.NewReader(f)
+
+ partToSPDMap := make(map[string]string)
+ SPDToIndexMap := make(map[string]int)
+
+ for {
+ fields, err := r.Read()
+
+ if err == io.EOF {
+ break
+ }
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if len(fields) != 2 {
+ return nil, nil, fmt.Errorf("CSV file is incorrectly formatted")
+ }
+
+ partToSPDMap[fields[0]] = fields[1]
+ SPDToIndexMap[fields[1]] = -1
+ }
+
+ return partToSPDMap, SPDToIndexMap, nil
+}
+
+/* Print information about memory part used by variant and ID assigned to it. */
+func appendPartIdInfo(s *string, partName string, index int) {
+ *s += fmt.Sprintf("%-30s %d (%04b)\n", partName, index, int64(index))
+}
+
+type partIds struct {
+ SPDFileName string
+ memParts string
+}
+
+/*
+ * For each part used by variant, check if the SPD (as per the manifest) already has an ID
+ * assigned to it. If yes, then add the part name to the list of memory parts supported by the
+ * SPD entry. If not, then assign the next ID to the SPD file and add the part name to the
+ * list of memory parts supported by the SPD entry.
+ *
+ * Returns list of partIds that contains spdFileName and supported memory parts for each
+ * assigned ID.
+ */
+func genPartIdInfo(parts []string, partToSPDMap map[string]string, SPDToIndexMap map[string]int, makefileDirName string) ([]partIds, error) {
+ partIdList := []partIds{}
+ curId := 0
+ var s string
+
+ s += fmt.Sprintf("%-30s %s\n", "DRAM Part Name", "ID to assign")
+
+ for _, p := range parts {
+ if p == "" {
+ continue
+ }
+
+ SPDFileName,ok := partToSPDMap[p]
+ if !ok {
+ return nil, fmt.Errorf("Failed to find part ", p, " in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest")
+ }
+
+ index := SPDToIndexMap[SPDFileName]
+ if index != -1 {
+ partIdList[index].memParts += ", " + p
+ appendPartIdInfo(&s, p, index)
+ continue
+ }
+
+ SPDToIndexMap[SPDFileName] = curId
+
+ appendPartIdInfo(&s, p, curId)
+ entry := partIds{SPDFileName: SPDFileName, memParts: p}
+ partIdList = append(partIdList, entry)
+
+ curId++
+ }
+
+ fmt.Printf("%s", s)
+ err := ioutil.WriteFile(filepath.Join(makefileDirName, DRAMIdFileName), []byte(s), 0644)
+
+ return partIdList, err
+}
+
+var generatedCodeLicense string = "## SPDX-License-Identifier: GPL-2.0-or-later"
+var autoGeneratedInfo string = "## This is an auto-generated file. Do not edit!!"
+
+/*
+ * This function generates Makefile.inc under the variant directory path and adds assigned SPDs
+ * to SPD_SOURCES.
+ */
+func genMakefile(partIdList []partIds, makefileDirName string) error {
+ var s string
+
+ s += fmt.Sprintf("%s\n%s\n\n", generatedCodeLicense, autoGeneratedInfo)
+ s += fmt.Sprintf("MEMORY_TYPE = ddr4\n\n")
+ s += fmt.Sprintf("SPD_SOURCES =\n")
+
+ for i := 0; i < len(partIdList); i++ {
+ s += fmt.Sprintf("SPD_SOURCES += %s ", partIdList[i].SPDFileName)
+ s += fmt.Sprintf(" # ID = %d(0b%04b) ", i, int64(i))
+ s += fmt.Sprintf(" Parts = %04s\n", partIdList[i].memParts)
+ }
+
+ return ioutil.WriteFile(filepath.Join(makefileDirName, MakefileName), []byte(s), 0644)
+}
+
+func main() {
+ if len(os.Args) != 4 {
+ usage()
+ log.Fatal("Incorrect number of arguments")
+ }
+
+ SPDDir, MakefileDir, MemPartsUsedFile := os.Args[1], os.Args[2], os.Args[3]
+
+ partToSPDMap, SPDToIndexMap, err := readSPDManifest(SPDDir)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ parts, err := readParts(MemPartsUsedFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ partIdList, err := genPartIdInfo(parts, partToSPDMap, SPDToIndexMap, MakefileDir)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := genMakefile(partIdList, MakefileDir); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/util/spd_tools/ddr4/gen_spd.go b/util/spd_tools/ddr4/gen_spd.go
new file mode 100644
index 0000000000..5adadc962a
--- /dev/null
+++ b/util/spd_tools/ddr4/gen_spd.go
@@ -0,0 +1,1386 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+/*
+ * This program generates de-duplicated SPD files for DDR4 memory using the global memory
+ * part list provided in CSV format. In addition to that, it also generates SPD manifest in CSV
+ * format that contains entries of type (DRAM part name, SPD file name) which provides the SPD
+ * file name used by a given DRAM part.
+ *
+ * It takes as input:
+ * Pointer to directory where the generated SPD files will be placed.
+ * JSON file containing a list of memory parts with their attributes as per datasheet.
+ */
+const (
+ SPDManifestFileName = "spd_manifest.generated.txt"
+
+ PlatformTGL = 0
+)
+
+var platformMap = map[string]int {
+ "TGL": PlatformTGL,
+}
+
+var currPlatform int
+
+type memAttributes struct {
+ /* Primary attributes - must be provided by JSON file for each part */
+ SpeedMTps int
+ CL_nRCD_nRP int
+ CapacityPerDieGb int
+ DiesPerPackage int
+ DeviceBusWidth int
+ RanksPerPackage int
+
+ /*
+ * All the following parameters are optional and required only if the part requires
+ * special parameters as per the datasheet.
+ */
+ /* Timing parameters */
+ TAAMinPs int
+ TRCDMinPs int
+ TRPMinPs int
+ TRASMinPs int
+ TRCMinPs int
+ TCKMinPs int
+ TCKMaxPs int
+ TRFC1MinPs int
+ TRFC2MinPs int
+ TRFC4MinPs int
+ TFAWMinPs int
+ TRRDLMinPs int
+ TRRDSMinPs int
+ TCCDLMinPs int
+ TWRMinPs int
+ TWTRLMinPs int
+ TWTRSMinPs int
+
+ /* CAS */
+ CASLatencies string
+ CASFirstByte byte
+ CASSecondByte byte
+ CASThirdByte byte
+ CASFourthByte byte
+}
+
+/* This encodes the density in Gb to SPD low nibble value as per JESD 4.1.2.L-5 R29 */
+var densityGbToSPDEncoding = map[int]byte {
+ 2: 0x3,
+ 4: 0x4,
+ 8: 0x5,
+ 16: 0x6,
+}
+
+/*
+ * Tables 4 thru Table 7 from JESD79-4C.
+ * Maps density per die to row-column encoding for a device with x8/x16
+ * physical channel.
+ */
+var densityGbx8x16DieCapacityToRowColumnEncoding = map[int]byte {
+ 2: 0x11, /* 14 rows, 10 columns */
+ 4: 0x19, /* 15 rows, 10 columns */
+ 8: 0x21, /* 16 rows, 10 columns */
+ 16: 0x29, /* 17 rows, 10 columns */
+}
+
+/*
+ * Tables 169 & 170 in the JESD79-4C spec
+ * Maps die density to refresh timings. This is the same for x8 and x16
+ * devices.
+ */
+
+/* maps die density to rcf1 timing in pico seconds */
+var tRFC1Encoding = map[int]int {
+ 2: 160000,
+ 4: 260000,
+ 8: 350000,
+ 16: 550000,
+}
+
+/* maps die density to rcf2 timing in pico seconds */
+var tRFC2Encoding = map[int]int {
+ 2: 110000,
+ 4: 160000,
+ 8: 260000,
+ 16: 350000,
+}
+
+/* maps die density to rcf4 timing in pico seconds */
+var tRFC4Encoding = map[int]int {
+ 2: 90000,
+ 4: 110000,
+ 8: 160000,
+ 16: 260000,
+}
+
+func getTRCMinPs(memAttribs *memAttributes) int {
+ return memAttribs.TAAMinPs + memAttribs.TRASMinPs
+}
+
+func getDefaultTCKMinPs(memAttribs *memAttributes) int {
+ /* value 2000000 = 2 * 1000000, where 1000000 is to convert mS to pS */
+ return 2000000 / memAttribs.SpeedMTps
+}
+
+type speedBinAttributes struct {
+ TRASMinPs int
+ TCKMaxPs int
+}
+
+var speedBinToSPDEncoding = map[int]speedBinAttributes {
+ 1600: {
+ TRASMinPs: 35000,
+ TCKMaxPs: 1500,
+ },
+ 1866: {
+ TRASMinPs: 34000,
+ TCKMaxPs: 1250,
+ },
+ 2133: {
+ TRASMinPs: 33000,
+ TCKMaxPs: 1071,
+ },
+ 2400: {
+ TRASMinPs: 32000,
+ TCKMaxPs: 937,
+ },
+ 2666: {
+ TRASMinPs: 32000,
+ TCKMaxPs: 833,
+ },
+ 2933: {
+ TRASMinPs: 32000,
+ TCKMaxPs: 750,
+ },
+ 3200: {
+ TRASMinPs: 32000,
+ TCKMaxPs: 682,
+ },
+}
+
+func getBankGroups(memAttribs *memAttributes) byte {
+ var bg byte
+
+ switch memAttribs.DeviceBusWidth {
+ case 8:
+ bg = 4
+ case 16:
+ if memAttribs.DiesPerPackage == 1 {
+ bg = 2 /* x16 SDP has 2 bank groups */
+ } else {
+ bg = 4 /* x16 DDP has 4 bank groups */
+ }
+ }
+
+ return bg
+}
+
+func encodeBankGroups(bg byte) byte {
+ var val byte
+
+ switch bg {
+ case 2:
+ val = 1
+ case 4:
+ val = 2
+ }
+
+ return val << 6
+}
+
+func encodeDensityBanks(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = densityGbToSPDEncoding[memAttribs.CapacityPerDieGb]
+ b |= encodeBankGroups(getBankGroups(memAttribs))
+ /* No need to encode banksPerGroup.it's always 4 ([4:5] = 0) */
+
+ return b
+}
+
+func encodeSdramAddressing(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = densityGbx8x16DieCapacityToRowColumnEncoding[memAttribs.CapacityPerDieGb]
+
+ return b
+}
+
+func encodePackageDeviceType(dies int) byte {
+ var b byte
+
+ if dies > 1 {
+ /* If more than one die, then this is a non-monolithic device. */
+ b = 1
+ } else {
+ /* If only single die, then this is a monolithic device. */
+ b = 0
+ }
+
+ return b << 7
+}
+
+func encodeSignalLoadingFromDieCount(dies int) byte {
+ var loading byte
+
+ /*
+ * If die count = 1, signal loading = "not specified" = 0
+ * If die count > 1, signal loading = "multi" = 2
+ */
+ if dies == 1 {
+ loading = 0
+ } else {
+ loading = 1
+ }
+
+ return loading
+}
+
+func encodeDiesPerPackage(dies int) byte {
+ var b byte
+
+ b = encodePackageDeviceType(dies) /* Monolithic / Non-monolithic device */
+ b |= (byte(dies) - 1) << 4
+
+ return b
+}
+
+func encodePackageType(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = encodeDiesPerPackage(memAttribs.DiesPerPackage)
+ b |= encodeSignalLoadingFromDieCount(memAttribs.DiesPerPackage)
+
+ return b
+}
+
+func encodeDataWidth(bitWidthPerDevice int) byte {
+ var width byte
+
+ switch bitWidthPerDevice {
+ case 8:
+ width = 1
+ case 16:
+ width = 2
+ }
+
+ return width
+}
+
+func encodeRanks(ranks int) byte {
+ var b byte
+
+ b = byte(ranks - 1)
+
+ return b << 3
+}
+
+func encodeModuleOrganization(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = encodeDataWidth(memAttribs.DeviceBusWidth)
+ b |= encodeRanks(memAttribs.RanksPerPackage)
+
+ return b
+}
+
+func encodeTCKMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TCKMinPs)
+}
+
+func encodeTCKMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TCKMinPs)
+}
+
+func encodeTCKMax(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TCKMaxPs)
+}
+
+func encodeTCKMaxFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TCKMaxPs)
+}
+
+func divRoundUp(dividend int, divisor int) int {
+ return (dividend + divisor - 1) / divisor
+}
+
+func convNsToPs(timeNs int) int {
+ return timeNs * 1000
+}
+
+func convMtbToPs(mtb int) int {
+ return mtb * 125
+}
+
+func convPsToMtb(timePs int) int {
+ return divRoundUp(timePs, 125)
+}
+
+func convPsToMtbByte(timePs int) byte {
+ return byte(convPsToMtb(timePs) & 0xff)
+}
+
+func convPsToFtbByte(timePs int) byte {
+ mtb := convPsToMtb(timePs)
+ ftb := timePs - convMtbToPs(mtb)
+
+ return byte(ftb)
+}
+
+func encodeTAAMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TAAMinPs)
+}
+
+func encodeTAAMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TAAMinPs)
+}
+
+func encodeTRCDMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TRCDMinPs)
+}
+
+func encodeTRCDMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TRCDMinPs)
+}
+
+func encodeTRPMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TRPMinPs)
+}
+
+func encodeTRCMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TRCMinPs)
+}
+
+func encodeTRPMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TRPMinPs)
+}
+
+func encodeTRASRCMinMSNs(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = byte((convPsToMtb(memAttribs.TRASMinPs) >> 4) & 0xf0)
+ b |= byte((convPsToMtb(memAttribs.TRCMinPs) >> 8) & 0x0f)
+
+ return b
+}
+
+func encodeTRASMinLsb(memAttribs *memAttributes) byte {
+ return byte(convPsToMtb(memAttribs.TRASMinPs) & 0xff)
+}
+
+func encodeTRCMinLsb(memAttribs *memAttributes) byte {
+ return byte(convPsToMtb(memAttribs.TRCMinPs) & 0xff)
+}
+
+var pageSizefromBusWidthEncoding = map[int]int {
+ 8: 1,
+ 16: 2,
+}
+
+/*
+ * Per Table 69 & Table 70 of Jedec JESD79-4C
+ * tFAW timing is based on :
+ * Speed bin and page size
+ */
+func getTFAWMinPs(memAttribs *memAttributes) int {
+ var tFAWFixed int
+
+ if pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] == 1 {
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ tFAWFixed = 25000
+ case 1866:
+ tFAWFixed = 23000
+ default:
+ tFAWFixed = 21000
+ }
+ } else if pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] == 2 {
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ tFAWFixed = 35000
+ default:
+ tFAWFixed = 30000
+ }
+ }
+
+ return tFAWFixed
+}
+
+/* Update settings based on data sheet (json) supplied memory attributes */
+
+func updateTFAWMin(memAttribs *memAttributes) {
+ var tFAWFromTck int
+
+ if memAttribs.TFAWMinPs == 0 {
+ memAttribs.TFAWMinPs = getTFAWMinPs(memAttribs)
+ }
+
+ switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] {
+ case 1:
+ tFAWFromTck = 20 * memAttribs.TCKMinPs
+ case 2:
+ tFAWFromTck = 28 * memAttribs.TCKMinPs
+ }
+
+ if memAttribs.TFAWMinPs < tFAWFromTck {
+ memAttribs.TFAWMinPs = tFAWFromTck
+ }
+}
+
+func updateTRFC1Min(memAttribs *memAttributes) {
+ if memAttribs.TRFC1MinPs == 0 {
+ memAttribs.TRFC1MinPs = tRFC1Encoding[memAttribs.CapacityPerDieGb]
+ }
+}
+
+func updateTRFC2Min(memAttribs *memAttributes) {
+ if memAttribs.TRFC2MinPs == 0 {
+ memAttribs.TRFC2MinPs = tRFC2Encoding[memAttribs.CapacityPerDieGb]
+ }
+}
+
+func updateTRFC4Min(memAttribs *memAttributes) {
+ if memAttribs.TRFC4MinPs == 0 {
+ memAttribs.TRFC4MinPs = tRFC4Encoding[memAttribs.CapacityPerDieGb]
+ }
+}
+
+func getTRRDLMinPs(memAttribs *memAttributes) int {
+ var tRRDLFixed int
+
+ /*
+ * Per JESD79-4C Tables 169 & 170, tRRD_L is based on :
+ * Speed bin and page size
+ */
+ switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] {
+ case 1:
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ tRRDLFixed = 6000
+ default:
+ tRRDLFixed = 5300
+ }
+ case 2:
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ tRRDLFixed = 7500
+ default:
+ tRRDLFixed = 6400
+ }
+ }
+
+ return tRRDLFixed
+}
+
+func updateTRRDLMin(memAttribs *memAttributes) {
+ var tRRDLFromTck int
+
+ if memAttribs.TRRDLMinPs == 0 {
+ memAttribs.TRRDLMinPs= getTRRDLMinPs(memAttribs)
+ }
+
+ tRRDLFromTck = 4 * memAttribs.TCKMinPs
+
+ if memAttribs.TRRDLMinPs < tRRDLFromTck {
+ memAttribs.TRRDLMinPs = tRRDLFromTck
+ }
+}
+
+var speedToTRRDSMinPsOneKPageSize = map[int]int {
+ 1600: 5000,
+ 1866: 4200,
+ 2133: 3700,
+ 2400: 3300,
+ 2666: 3000,
+ 2933: 2700,
+ 3200: 2500,
+}
+
+var speedToTRRDSMinPsTwoKPageSize = map[int]int {
+ 1600: 6000,
+ 1866: 5300,
+ 2133: 5300,
+ 2400: 5300,
+ 2666: 5300,
+ 2933: 5300,
+ 3200: 5300,
+}
+
+func getTRRDSMinPs(memAttribs *memAttributes) int {
+ var tRRDFixed int
+
+ switch pageSizefromBusWidthEncoding[memAttribs.DeviceBusWidth] {
+ case 1:
+ tRRDFixed = speedToTRRDSMinPsOneKPageSize[memAttribs.SpeedMTps]
+ case 2:
+ tRRDFixed = speedToTRRDSMinPsTwoKPageSize[memAttribs.SpeedMTps]
+ }
+
+ return tRRDFixed
+}
+
+func updateTRRDSMin(memAttribs *memAttributes) {
+ var tRRDFromTck int
+
+ if memAttribs.TRRDSMinPs == 0 {
+ memAttribs.TRRDSMinPs = getTRRDSMinPs(memAttribs)
+ }
+
+ tRRDFromTck = 4 * memAttribs.TCKMinPs
+
+ if memAttribs.TRRDSMinPs < tRRDFromTck {
+ memAttribs.TRRDSMinPs = tRRDFromTck
+ }
+}
+
+/*
+ * Per JESD79-4C Tables 169 and 170,
+ * tCCD_L is based on :
+ * Speed Bin
+ */
+func getTCCDLMinPs(memAttribs *memAttributes) int {
+ var tCCDLFixed int
+
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ tCCDLFixed = 6250
+ case 1866:
+ tCCDLFixed = 5355
+ case 2133:
+ tCCDLFixed = 5355
+ default:
+ tCCDLFixed = 5000
+ }
+
+ return tCCDLFixed
+}
+
+func updateTCCDLMin(memAttribs *memAttributes) {
+ var tCCDLFromTck int
+
+ if memAttribs.TCCDLMinPs == 0 {
+ memAttribs.TCCDLMinPs = getTCCDLMinPs(memAttribs)
+ }
+
+ tCCDLFromTck = 5 * memAttribs.TCKMinPs
+
+ if memAttribs.TCCDLMinPs < tCCDLFromTck {
+ memAttribs.TCCDLMinPs = tCCDLFromTck
+ }
+}
+
+func encodeTRFC1MinLsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC1MinPs)
+
+ return byte(mtb & 0xff)
+}
+
+func encodeTRFC1MinMsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC1MinPs)
+
+ return byte((mtb >> 8) & 0xff)
+}
+
+func encodeTRFC2MinLsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC2MinPs)
+
+ return byte(mtb & 0xff)
+}
+
+func encodeTRFC2MinMsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC2MinPs)
+
+ return byte((mtb >> 8) & 0xff)
+}
+
+func encodeTRFC4MinLsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC4MinPs)
+
+ return byte(mtb & 0xff)
+}
+
+func encodeTRFC4MinMsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TRFC4MinPs)
+
+ return byte((mtb >> 8) & 0xff)
+}
+
+func encodeTFAWMinMSN(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TFAWMinPs)
+
+ return byte((mtb >> 8) & 0x0f)
+}
+
+func encodeTFAWMinLsb(memAttribs *memAttributes) byte {
+ var mtb int
+
+ mtb = convPsToMtb(memAttribs.TFAWMinPs)
+
+ return byte(mtb & 0xff)
+}
+
+func encodeCASFirstByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASFirstByte
+}
+
+func encodeCASSecondByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASSecondByte
+}
+
+func encodeCASThirdByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASThirdByte
+}
+
+func encodeCASFourthByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASFourthByte
+}
+
+func encodeTRRDSMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TRRDSMinPs)
+}
+
+func encodeTRRDSMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TRRDSMinPs)
+}
+
+func encodeTRRDLMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TRRDLMinPs)
+}
+
+func encodeTRRDLMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TRRDLMinPs)
+}
+
+func encodeTCCDLMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TCCDLMinPs)
+}
+
+func encodeTCCDLMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TCCDLMinPs)
+}
+
+func encodeTWRMinMSN(memAttribs *memAttributes) byte {
+ return byte((convPsToMtb(TimingValueTWRMinPs) >> 8) & 0x0f)
+}
+
+func encodeTWRMinLsb(memAttribs *memAttributes) byte {
+ return byte(convPsToMtb(TimingValueTWRMinPs) & 0xff)
+}
+
+func encodeTWTRMinMSNs(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = byte((convPsToMtb(memAttribs.TWTRLMinPs) >> 4) & 0xf0)
+ b |= byte((convPsToMtb(memAttribs.TWTRSMinPs) >> 8) & 0x0f)
+
+ return b
+}
+
+func encodeTWTRSMinLsb(memAttribs *memAttributes) byte {
+ return byte(convPsToMtb(memAttribs.TWTRSMinPs) & 0xff)
+}
+
+func encodeTWTRLMinLsb(memAttribs *memAttributes) byte {
+ return byte(convPsToMtb(memAttribs.TWTRLMinPs) & 0xff)
+}
+
+type SPDMemAttribFunc func (*memAttributes) byte
+type SPDConvConstFunc func () byte
+
+type SPDAttribTableEntry struct {
+ constVal byte
+ getVal SPDMemAttribFunc
+}
+
+const (
+ /* SPD Byte Index */
+ SPDIndexSize = 0
+ SPDIndexRevision = 1
+ SPDIndexMemoryType = 2
+ SPDIndexModuleType = 3
+ SPDIndexDensityBanks = 4
+ SPDIndexAddressing = 5
+ SPDIndexPackageType = 6
+ SPDIndexOptionalFeatures = 7
+ SPDIndexModuleOrganization = 12
+ SPDIndexBusWidth = 13
+ SPDIndexTimebases = 17
+ SPDIndexTCKMin = 18
+ SPDIndexTCKMax = 19
+ SPDIndexCASFirstByte = 20
+ SPDIndexCASSecondByte = 21
+ SPDIndexCASThirdByte = 22
+ SPDIndexCASFourthByte = 23
+ SPDIndexTAAMin = 24
+ SPDIndexTRCDMin = 25
+ SPDIndexTRPMin = 26
+ SPDIndexTRASRCMinMSNs = 27
+ SPDIndexTRASMinLsb = 28
+ SPDIndexTRCMinLsb = 29
+ SPDIndexTRFC1MinLsb = 30
+ SPDIndexTRFC1MinMsb = 31
+ SPDIndexTRFC2MinLsb = 32
+ SPDIndexTRFC2MinMsb = 33
+ SPDIndexTRFC4MinLsb = 34
+ SPDIndexTRFC4MinMsb = 35
+ SPDIndexTFAWMinMSN = 36
+ SPDIndexTFAWMinLsb = 37
+ SPDIndexTRRDSMin = 38
+ SPDIndexTRRDLMin = 39
+ SPDIndexTCCDLMin = 40
+ SPDIndexTWRMinMSN = 41
+ SPDIndexTWRMinLsb = 42
+ SPDIndexTWTRMinMSNs = 43
+ SPDIndexWTRSMinLsb = 44
+ SPDIndexWTRLMinLsb = 45
+ SPDIndexTCCDLMinFineOffset = 117
+ SPDIndexTRRDLMinFineOffset = 118
+ SPDIndexTRRDSMinFineOffset = 119
+ SPDIndexTRCMinFineOffset = 120
+ SPDIndexTRPMinFineOffset = 121
+ SPDIndexTRCDMinFineOffset = 122
+ SPDIndexTAAMinFineOffset = 123
+ SPDIndexTCKMaxFineOffset = 124
+ SPDIndexTCKMinFineOffset = 125
+ SPDIndexManufacturerPartNumberStartByte = 329
+ SPDIndexManufacturerPartNumberEndByte = 348
+
+ /* SPD Byte Value */
+
+ /*
+ * From JEDEC spec:
+ * 6:4 (Bytes total) = 2 (512 bytes)
+ * 3:0 (Bytes used) = 3 (384 bytes)
+ * Set to 0x23 for DDR4.
+ */
+ SPDValueSize = 0x23
+
+ /*
+ * From JEDEC spec: Revision 1.1
+ * Set to 0x11.
+ */
+ SPDValueRevision = 0x11
+
+ /* DDR4 memory type = 0x0C */
+ SPDValueMemoryType = 0x0C
+
+ /*
+ * From JEDEC spec:
+ * Module Type [0:3] :
+ * 0 = Undefined
+ * 1 = RDIMM (width = 133.35 mm nom)
+ * 2 = UDIMM (width = 133.35 mm nom)
+ * 3 = SO-DIMM (width = 68.60 mm nom)
+ * 4 = LRDIMM (width = 133.35 mm nom)
+ *
+ * DDR4 on TGL uses SO-DIMM type for for both memory down and DIMM config.
+ * Set to 0x03.
+ */
+ SPDValueModuleType = 0x03
+
+ /*
+ * From JEDEC spec:
+ * 5:4 (Maximum Activate Window) = 00 (8192 * tREFI)
+ * 3:0 (Maximum Activate Count) = 1000 (Unlimited MAC)
+ *
+ * Needs to come from datasheet, but most parts seem to support unlimited MAC.
+ * MR#24 OP3
+ */
+ SPDValueOptionalFeatures = 0x08
+
+ /*
+ * From JEDEC spec:
+ * 2:0 Primary Bus Width in Bits = 011 (x64 always)
+ * Set to 0x03.
+ */
+ SPDValueModuleBusWidth = 0x03
+
+ /*
+ * From JEDEC spec:
+ * 3:2 (MTB) = 00 (0.125ns)
+ * 1:0 (FTB) = 00 (1ps)
+ * Set to 0x00.
+ */
+ SPDValueTimebases = 0x00
+
+ /* CAS fourth byte: All bits are reserved */
+ SPDValueCASFourthByte = 0x00
+
+ /* As per JEDEC spec, unused digits of manufacturer part number are left as blank. */
+ SPDValueManufacturerPartNumberBlank = 0x20
+
+)
+
+const (
+ /*
+ * As per Table 75 of Jedec spec 4.1.20-L-5 R29 v103:
+ * tWRMin = 15nS for all DDR4 Speed Bins
+ * Set to 15000 pS
+ */
+ TimingValueTWRMinPs = 15000
+
+ /*
+ * As per Table 78 of Jedec spec 4.1.20-L-5 R29 v103:
+ * tWTR_SMin = 2.5nS for all DDR4 Speed Bins
+ * Set to 2500 pS
+ */
+ TimingValueTWTRSMinPs = 2500
+
+ /*
+ * As per Table 80 of Jedec spec 4.1.20-L-5 R29 v103:
+ * tWTR_LMin = 7.5 nS for all DDR4 Speed Bins
+ * Set to 7500 pS
+ */
+ TimingValueTWTRLMinPs = 7500
+)
+
+var SPDAttribTable = map[int]SPDAttribTableEntry {
+ SPDIndexSize: { constVal: SPDValueSize },
+ SPDIndexRevision: { constVal: SPDValueRevision },
+ SPDIndexMemoryType: { constVal: SPDValueMemoryType },
+ SPDIndexModuleType: { constVal: SPDValueModuleType },
+ SPDIndexDensityBanks: { getVal: encodeDensityBanks },
+ SPDIndexAddressing: { getVal: encodeSdramAddressing },
+ SPDIndexPackageType: { getVal: encodePackageType },
+ SPDIndexOptionalFeatures: { constVal: SPDValueOptionalFeatures },
+ SPDIndexModuleOrganization: { getVal: encodeModuleOrganization },
+ SPDIndexBusWidth: { constVal: SPDValueModuleBusWidth },
+ SPDIndexTimebases: { constVal: SPDValueTimebases },
+ SPDIndexTCKMin: { getVal: encodeTCKMin },
+ SPDIndexTCKMinFineOffset: { getVal: encodeTCKMinFineOffset },
+ SPDIndexTCKMax: { getVal: encodeTCKMax },
+ SPDIndexTCKMaxFineOffset: { getVal: encodeTCKMaxFineOffset },
+ SPDIndexCASFirstByte: { getVal: encodeCASFirstByte },
+ SPDIndexCASSecondByte: { getVal: encodeCASSecondByte },
+ SPDIndexCASThirdByte: { getVal: encodeCASThirdByte },
+ SPDIndexCASFourthByte: { getVal: encodeCASFourthByte },
+ SPDIndexTAAMin: { getVal: encodeTAAMin },
+ SPDIndexTAAMinFineOffset: { getVal: encodeTAAMinFineOffset },
+ SPDIndexTRCDMin: { getVal: encodeTRCDMin },
+ SPDIndexTRCDMinFineOffset: { getVal: encodeTRCDMinFineOffset },
+ SPDIndexTRPMin: { getVal: encodeTRPMin },
+ SPDIndexTRPMinFineOffset: { getVal: encodeTRPMinFineOffset },
+ SPDIndexTRASRCMinMSNs: { getVal: encodeTRASRCMinMSNs },
+ SPDIndexTRASMinLsb: { getVal: encodeTRASMinLsb },
+ SPDIndexTRCMinLsb: { getVal: encodeTRCMinLsb },
+ SPDIndexTRCMinFineOffset: { getVal: encodeTRCMinFineOffset },
+ SPDIndexTRFC1MinLsb: { getVal: encodeTRFC1MinLsb },
+ SPDIndexTRFC1MinMsb: { getVal: encodeTRFC1MinMsb },
+ SPDIndexTRFC2MinLsb: { getVal: encodeTRFC2MinLsb },
+ SPDIndexTRFC2MinMsb: { getVal: encodeTRFC2MinMsb },
+ SPDIndexTRFC4MinLsb: { getVal: encodeTRFC4MinLsb },
+ SPDIndexTRFC4MinMsb: { getVal: encodeTRFC4MinMsb },
+ SPDIndexTFAWMinMSN: { getVal: encodeTFAWMinMSN },
+ SPDIndexTFAWMinLsb: { getVal: encodeTFAWMinLsb },
+ SPDIndexTRRDSMin: { getVal: encodeTRRDSMin },
+ SPDIndexTRRDSMinFineOffset: { getVal: encodeTRRDSMinFineOffset },
+ SPDIndexTRRDLMin: { getVal: encodeTRRDLMin },
+ SPDIndexTRRDLMinFineOffset: { getVal: encodeTRRDLMinFineOffset },
+ SPDIndexTCCDLMin: { getVal: encodeTCCDLMin },
+ SPDIndexTCCDLMinFineOffset: { getVal: encodeTCCDLMinFineOffset },
+ SPDIndexTWRMinMSN: { getVal: encodeTWRMinMSN },
+ SPDIndexTWRMinLsb: { getVal: encodeTWRMinLsb },
+ SPDIndexTWTRMinMSNs: { getVal: encodeTWTRMinMSNs },
+ SPDIndexWTRSMinLsb: { getVal: encodeTWTRSMinLsb },
+ SPDIndexWTRLMinLsb: { getVal: encodeTWTRLMinLsb },
+}
+
+type memParts struct {
+ MemParts []memPart `json:"parts"`
+}
+
+type memPart struct {
+ Name string
+ Attribs memAttributes
+ SPDFileName string
+}
+
+func writeSPDManifest(memParts *memParts, SPDDirName string) error {
+ var s string
+
+ fmt.Printf("Generating SPD Manifest with following entries:\n")
+
+ for i := 0; i < len(memParts.MemParts); i++ {
+ fmt.Printf("%-40s %s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName)
+ s += fmt.Sprintf("%s,%s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName)
+ }
+
+ return ioutil.WriteFile(filepath.Join(SPDDirName, SPDManifestFileName), []byte(s), 0644)
+}
+
+func isManufacturerPartNumberByte(index int) bool {
+ if index >= SPDIndexManufacturerPartNumberStartByte && index <= SPDIndexManufacturerPartNumberEndByte {
+ return true
+ }
+ return false
+}
+
+
+func getSPDByte(index int, memAttribs *memAttributes) byte {
+ e, ok := SPDAttribTable[index]
+ if ok == false {
+ if isManufacturerPartNumberByte(index) {
+ return SPDValueManufacturerPartNumberBlank
+ }
+ return 0x00
+ }
+
+ if e.getVal != nil {
+ return e.getVal(memAttribs)
+ }
+
+ return e.constVal
+}
+
+func createSPD(memAttribs *memAttributes) string {
+ var s string
+
+ for i := 0; i < 512; i++ {
+ b := getSPDByte(i, memAttribs)
+
+ if (i + 1) % 16 == 0 {
+ s += fmt.Sprintf("%02X\n", b)
+ } else {
+ s += fmt.Sprintf("%02X ", b)
+ }
+ }
+
+ return s
+}
+
+func dedupeMemoryPart(dedupedParts []*memPart, memPart *memPart) bool {
+ for i := 0; i < len(dedupedParts); i++ {
+ if reflect.DeepEqual(dedupedParts[i].Attribs, memPart.Attribs) {
+ memPart.SPDFileName = dedupedParts[i].SPDFileName
+ return true
+ }
+ }
+
+ return false
+}
+
+func generateSPD(memPart *memPart, SPDId int, SPDDirName string) {
+ s := createSPD(&memPart.Attribs)
+ memPart.SPDFileName = fmt.Sprintf("ddr4-spd-%d.hex", SPDId)
+ ioutil.WriteFile(filepath.Join(SPDDirName, memPart.SPDFileName), []byte(s), 0644)
+}
+
+func readMemoryParts(memParts *memParts, memPartsFileName string) error {
+ databytes, err := ioutil.ReadFile(memPartsFileName)
+ if err != nil {
+ return err
+ }
+
+ return json.Unmarshal(databytes, memParts)
+}
+
+func validateSpeedMTps(speedBin int) error {
+ if _, ok := speedBinToSPDEncoding[speedBin]; ok == false {
+ return fmt.Errorf("Incorrect speed bin: DDR4-", speedBin)
+ }
+ return nil
+}
+
+func validateCapacityPerDie(capacityPerDieGb int) error {
+ if _, ok := densityGbToSPDEncoding[capacityPerDieGb]; ok == false {
+ return fmt.Errorf("Incorrect capacity per die: ", capacityPerDieGb)
+ }
+ return nil
+}
+
+func validateDiesPerPackage(dieCount int) error {
+ if dieCount >= 1 && dieCount <= 2 {
+ return nil
+ }
+ return fmt.Errorf("Incorrect dies per package count: ", dieCount)
+}
+
+func validateDeviceBusWidth(width int) error {
+ if width != 8 && width != 16 {
+ return fmt.Errorf("Incorrect device bus width: ", width)
+ }
+ return nil
+}
+
+func validateRanksPerPackage(ranks int) error {
+ if ranks >= 1 && ranks <= 2 {
+ return nil
+ }
+ return fmt.Errorf("Incorrect package ranks: ", ranks)
+}
+
+
+func validateCASLatency(CL int) error {
+ if CL >= 10 && CL <= 24 && CL != 23 {
+ return nil
+ }
+ return fmt.Errorf("Incorrect CAS latency: ", CL)
+}
+
+/*
+1) validate memory parts
+2) remove any fields that Intel does not care about
+*/
+
+/* verify the supplied CAS Latencies supported does not match default */
+func verifySupportedCASLatencies(part *memPart) error {
+ if part.Attribs.CASLatencies == getDefaultCASLatencies(&part.Attribs) {
+ return fmt.Errorf("CASLatencies for %s already matches default,\nPlease remove CASLatencies override line from the %s part attributes in the global part list and regenerate SPD Manifest", part.Name, part.Name)
+ }
+
+ return nil
+}
+
+func validateMemoryParts(memParts *memParts) error {
+ for i := 0; i < len(memParts.MemParts); i++ {
+ if err := validateSpeedMTps(memParts.MemParts[i].Attribs.SpeedMTps); err != nil {
+ return err
+ }
+ if err := validateCapacityPerDie(memParts.MemParts[i].Attribs.CapacityPerDieGb); err != nil {
+ return err
+ }
+ if err := validateDiesPerPackage(memParts.MemParts[i].Attribs.DiesPerPackage); err != nil {
+ return err
+ }
+ if err := validateDeviceBusWidth(memParts.MemParts[i].Attribs.DeviceBusWidth); err != nil {
+ return err
+ }
+ if err := validateRanksPerPackage(memParts.MemParts[i].Attribs.RanksPerPackage); err != nil {
+ return err
+ }
+ if err := validateCASLatency(memParts.MemParts[i].Attribs.CL_nRCD_nRP); err != nil {
+ return err
+ }
+ /* If CAS Latency was supplied, make sure it doesn't match default value */
+ if len(memParts.MemParts[i].Attribs.CASLatencies) != 0 {
+ if err := verifySupportedCASLatencies(&memParts.MemParts[i]); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+const (
+ /* First Byte */
+ CAS9 = 1 << 2
+ CAS10 = 1 << 3
+ CAS11 = 1 << 4
+ CAS12 = 1 << 5
+ CAS13 = 1 << 6
+ CAS14 = 1 << 7
+ /* Second Byte */
+ CAS15 = 1 << 0
+ CAS16 = 1 << 1
+ CAS17 = 1 << 2
+ CAS18 = 1 << 3
+ CAS19 = 1 << 4
+ CAS20 = 1 << 5
+ CAS21 = 1 << 6
+ CAS22 = 1 << 7
+ /* Third Byte */
+ CAS24 = 1 << 1
+)
+
+func encodeLatencies(latency int, memAttribs *memAttributes) error {
+ switch latency {
+ case 9:
+ memAttribs.CASFirstByte |= CAS9
+ case 10:
+ memAttribs.CASFirstByte |= CAS10
+ case 11:
+ memAttribs.CASFirstByte |= CAS11
+ case 12:
+ memAttribs.CASFirstByte |= CAS12
+ case 13:
+ memAttribs.CASFirstByte |= CAS13
+ case 14:
+ memAttribs.CASFirstByte |= CAS14
+ case 15:
+ memAttribs.CASSecondByte |= CAS15
+ case 16:
+ memAttribs.CASSecondByte |= CAS16
+ case 17:
+ memAttribs.CASSecondByte |= CAS17
+ case 18:
+ memAttribs.CASSecondByte |= CAS18
+ case 19:
+ memAttribs.CASSecondByte |= CAS19
+ case 20:
+ memAttribs.CASSecondByte |= CAS20
+ case 21:
+ memAttribs.CASSecondByte |= CAS21
+ case 22:
+ memAttribs.CASSecondByte |= CAS22
+ case 24:
+ memAttribs.CASThirdByte |= CAS24
+ default:
+ fmt.Errorf("Incorrect CAS Latency: ", latency)
+ }
+
+ return nil
+}
+
+/* Default CAS Latencies from Speed Bin tables in JEDS79-4C */
+func getDefaultCASLatencies(memAttribs *memAttributes) string {
+ var str string
+
+ switch memAttribs.SpeedMTps {
+ case 1600:
+ switch memAttribs.CL_nRCD_nRP {
+ case 10:
+ str = "9 10 11 12"
+ case 11:
+ str = "9 11 12"
+ case 12:
+ str = "10 12"
+ }
+ case 1866:
+ switch memAttribs.CL_nRCD_nRP {
+ case 12:
+ str = "9 10 12 13 14"
+ case 13:
+ str = "9 11 12 13 14"
+ case 14:
+ str = "10 12 14"
+ }
+ case 2133:
+ switch memAttribs.CL_nRCD_nRP {
+ case 14:
+ str = "9 10 12 14 15 16"
+ case 15:
+ str = "9 11 12 13 14 15 16"
+ case 16:
+ str = "10 12 14 16"
+ }
+ case 2400:
+ switch memAttribs.CL_nRCD_nRP {
+ case 15:
+ str = "9 10 12 14 15 16 17 18"
+ case 16:
+ str = "9 11 12 13 14 15 16 17 18"
+ case 17:
+ str = "10 11 12 13 14 15 16 17 18"
+ case 18:
+ str = "10 12 14 16 18"
+ }
+ case 2666:
+ switch memAttribs.CL_nRCD_nRP {
+ case 17:
+ str = "9 10 11 12 13 14 15 16 17 18 19 20"
+ case 18:
+ str = "9 10 11 12 13 14 15 16 17 18 19 20"
+ case 19:
+ str = "10 11 12 13 14 15 16 17 18 19 20"
+ case 20:
+ str = "10 12 14 16 18 20"
+ }
+ case 2933:
+ switch memAttribs.CL_nRCD_nRP {
+ case 19:
+ str = "9 10 11 12 13 14 15 16 17 18 19 20 21 22"
+ case 20:
+ str = "10 11 12 13 14 15 16 17 18 19 20 21 22"
+ case 21:
+ str = "10 11 12 13 14 15 16 17 18 19 20 21 22"
+ case 22:
+ str = "10 12 14 16 18 20 22"
+ }
+ case 3200:
+ switch memAttribs.CL_nRCD_nRP {
+ case 20:
+ str = "9 10 11 12 13 14 15 16 17 18 19 20 21 22 24"
+ case 22:
+ str = "10 11 12 13 14 15 16 17 18 19 20 21 22 24"
+ case 24:
+ str = "10 12 14 16 18 20 22 24"
+ }
+ }
+
+ return str
+}
+
+func updateCAS(memAttribs *memAttributes) error {
+ if len(memAttribs.CASLatencies) == 0 {
+ memAttribs.CASLatencies = getDefaultCASLatencies(memAttribs)
+ }
+
+ latencies := strings.Fields(memAttribs.CASLatencies)
+ for i := 0; i < len(latencies); i++ {
+ latency,err := strconv.Atoi(latencies[i])
+ if err != nil {
+ return fmt.Errorf("Unable to convert latency ", latencies[i])
+ }
+ if err := encodeLatencies(latency, memAttribs); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func getTAAMinPs(memAttribs *memAttributes) int {
+ return (memAttribs.CL_nRCD_nRP * 2000000) / memAttribs.SpeedMTps
+}
+
+func updateTAAMin(memAttribs *memAttributes) {
+ if memAttribs.TAAMinPs == 0 {
+ memAttribs.TAAMinPs = getTAAMinPs(memAttribs)
+ }
+}
+
+func updateTRCDMin(memAttribs *memAttributes) {
+ /* tRCDmin is same as tAAmin for all cases */
+ if memAttribs.TRCDMinPs == 0 {
+ memAttribs.TRCDMinPs = getTAAMinPs(memAttribs)
+ }
+}
+
+func updateTRPMin(memAttribs *memAttributes) {
+ /* tRPmin is same as tAAmin for all cases */
+ if memAttribs.TRPMinPs == 0 {
+ memAttribs.TRPMinPs = getTAAMinPs(memAttribs)
+ }
+}
+
+func updateTRASMin(memAttribs *memAttributes) {
+ if memAttribs.TRASMinPs == 0 {
+ memAttribs.TRASMinPs = speedBinToSPDEncoding[memAttribs.SpeedMTps].TRASMinPs
+ }
+}
+
+
+func updateTRCMin(memAttribs *memAttributes) {
+ if memAttribs.TRCMinPs == 0 {
+ memAttribs.TRCMinPs = getTRCMinPs(memAttribs)
+ }
+}
+
+func updateTCK(memAttribs *memAttributes) {
+ if memAttribs.TCKMinPs == 0 {
+ memAttribs.TCKMinPs = getDefaultTCKMinPs(memAttribs)
+ }
+ if memAttribs.TCKMaxPs == 0 {
+ memAttribs.TCKMaxPs = speedBinToSPDEncoding[memAttribs.SpeedMTps].TCKMaxPs
+ }
+}
+
+func updateTWRMin(memAttribs *memAttributes) {
+ if memAttribs.TWRMinPs == 0 {
+ memAttribs.TWRMinPs = TimingValueTWRMinPs
+ }
+}
+
+func updateTWTRMin(memAttribs *memAttributes) {
+ if memAttribs.TWTRLMinPs == 0 {
+ memAttribs.TWTRLMinPs = TimingValueTWTRLMinPs
+ }
+ if memAttribs.TWTRSMinPs == 0 {
+ memAttribs.TWTRSMinPs = TimingValueTWTRSMinPs
+ }
+}
+
+func updateMemoryAttributes(memAttribs *memAttributes) {
+ updateTCK(memAttribs)
+ updateTAAMin(memAttribs)
+ updateTRCDMin(memAttribs)
+ updateTRPMin(memAttribs)
+ updateTRASMin(memAttribs)
+ updateTRCMin(memAttribs)
+ updateTWRMin(memAttribs)
+ updateTWTRMin(memAttribs)
+ updateCAS(memAttribs)
+ updateTRFC1Min(memAttribs)
+ updateTRFC2Min(memAttribs)
+ updateTRFC4Min(memAttribs)
+ updateTCCDLMin(memAttribs)
+ updateTRRDSMin(memAttribs)
+ updateTRRDLMin(memAttribs)
+ updateTFAWMin(memAttribs)
+}
+
+func isPlatformSupported(platform string) error {
+ var ok bool
+
+ currPlatform, ok = platformMap[platform]
+ if ok == false {
+ return fmt.Errorf("Unsupported platform: ", platform)
+ }
+
+ return nil
+}
+
+func usage() {
+ fmt.Printf("\nUsage: %s <spd_dir> <mem_parts_list_json> <platform>\n\n", os.Args[0])
+ fmt.Printf(" where,\n")
+ fmt.Printf(" spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n")
+ fmt.Printf(" mem_parts_list_json = JSON File containing list of memory parts and attributes\n")
+ fmt.Printf(" platform = SoC Platform for which the SPDs are being generated\n\n\n")
+}
+
+func main() {
+ if len(os.Args) != 4 {
+ usage()
+ log.Fatal("Incorrect number of arguments")
+ }
+
+ var memParts memParts
+ var dedupedParts []*memPart
+
+ SPDDir, GlobalMemPartsFile, Platform := os.Args[1], os.Args[2], strings.ToUpper(os.Args[3])
+
+ if err := isPlatformSupported(Platform); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := readMemoryParts(&memParts, GlobalMemPartsFile); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := validateMemoryParts(&memParts); err != nil {
+ log.Fatal(err)
+ }
+
+ SPDId := 1
+
+ for i := 0; i < len(memParts.MemParts); i++ {
+ updateMemoryAttributes(&memParts.MemParts[i].Attribs)
+ if dedupeMemoryPart(dedupedParts, &memParts.MemParts[i]) == false {
+ generateSPD(&memParts.MemParts[i], SPDId, SPDDir)
+ SPDId++
+ dedupedParts = append(dedupedParts, &memParts.MemParts[i])
+ }
+ }
+
+ if err := writeSPDManifest(&memParts, SPDDir); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt b/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt
new file mode 100644
index 0000000000..4e9c7c8e95
--- /dev/null
+++ b/util/spd_tools/ddr4/global_ddr4_mem_parts.json.txt
@@ -0,0 +1,37 @@
+{
+ "parts": [
+ {
+ "name": "H5AN8G6NDJR-XNC",
+ "attribs": {
+ "speedMTps": 3200,
+ "CL_nRCD_nRP": 22,
+ "capacityPerDieGb": 8,
+ "diesPerPackage": 1,
+ "deviceBusWidth": 16,
+ "ranksPerPackage": 1
+ }
+ },
+ {
+ "name": "MT40A512M16TB-062E:J",
+ "attribs": {
+ "speedMTps": 3200,
+ "CL_nRCD_nRP": 22,
+ "capacityPerDieGb": 8,
+ "diesPerPackage": 1,
+ "deviceBusWidth": 16,
+ "ranksPerPackage": 1
+ }
+ },
+ {
+ "name": "H5ANAG6NCMR-XNC",
+ "attribs": {
+ "speedMTps": 3200,
+ "CL_nRCD_nRP": 22,
+ "capacityPerDieGb": 8,
+ "diesPerPackage": 2,
+ "deviceBusWidth": 16,
+ "ranksPerPackage": 1
+ }
+ }
+ ]
+}