summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--util/spd_tools/intel/lp4x/README.md265
-rw-r--r--util/spd_tools/intel/lp4x/gen_part_id.go214
-rw-r--r--util/spd_tools/intel/lp4x/gen_spd.go971
3 files changed, 1450 insertions, 0 deletions
diff --git a/util/spd_tools/intel/lp4x/README.md b/util/spd_tools/intel/lp4x/README.md
new file mode 100644
index 0000000000..e614f259cf
--- /dev/null
+++ b/util/spd_tools/intel/lp4x/README.md
@@ -0,0 +1,265 @@
+# LPDDR4x SPD tools README
+
+Tools for generating SPD files for LPDDR4x memory used in memory down
+configurations on Intel Tiger Lake (TGL) and Jasper Lake (JSL) based
+platforms. These tools generate SPDs following JESD209-4C
+specification and Intel recommendations (doc #616599, #610202) for
+LPDDR4x SPD.
+
+There are two tools provided that assist TGL and JSL 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 LPDDR4x 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 LPDDR4x
+ 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
+ LPDDR4x 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` and `JSL`.
+
+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`
+
+* `densityPerChannelGb`: Density in Gb of the physical channel.
+
+* `banks`: Number of banks per physical channel. This is typically 8
+ for LPDDR4x memory parts.
+
+* `channelsPerDie`: Number of physical channels per die. Valid values:
+ `1, 2, 4`. For a part with x16 bit width, number of channels per die
+ is 1 or 2. For a part with x8 bit width, number of channels can be
+ 2 or 4 (4 is basically when two dual-channel byte mode devices are
+ combined as shown in Figure 3 in JESD209-4C).
+
+* `diesPerPackage`: Number of physical dies in each SDRAM
+ package. As per JESD209-4C, "Standard LPDDR4 package ballmaps
+ allocate one ZQ ball per die." Thus, number of diesPerPackage is the
+ number of ZQ balls on the package.
+
+* `bitWidthPerChannel`: Width of each physical channel. Valid values:
+ `8, 16` bits.
+
+* `ranksPerChannel`: Number of ranks per physical channel. Valid
+ values: `1, 2`. If the channels across multiple dies share the same
+ DQ/DQS pins but use a separate CS, then ranks is 2 else it is 1.
+
+* `speedMbps`: Maximum data rate supported by the part in Mbps. Valid
+ values: `3200, 3733, 4267` Mbps.
+
+### Optional `attribs`
+
+* `trfcabNs`: Minimum Refresh Recovery Delay Time (tRFCab) for all
+ banks in nanoseconds. As per JESD209-4C, this is dependent on the
+ density per channel. Default values used:
+ * 6Gb : 280ns
+ * 8Gb : 280ns
+ * 12Gb: 380ns
+ * 16Gb: 380ns
+
+* `trfcpbNs`: Minimum Refresh Recovery Delay Time (tRFCab) per
+ bank in nanoseconds. As per JESD209-4C, this is dependent on the
+ density per channel. Default values used:
+ * 6Gb : 140ns
+ * 8Gb : 140ns
+ * 12Gb: 190ns
+ * 16Gb: 190ns
+
+* `trpabMinNs`: Minimum Row Precharge Delay Time (tRPab) for all banks
+ in nanoseconds. As per JESD209-4C, this is max(21ns, 4nck) which
+ defaults to `21ns`.
+
+* `trppbMinNs`: Minimum Row Precharge Delay Time (tRPpb) per bank in
+ nanoseconds. As per JESD209-4C, this is max(18ns, 4nck) which
+ defaults to `18ns`.
+
+* `tckMinPs`: SDRAM minimum cycle time (tckMin) value in
+ picoseconds. This is typically calculated based on the `speedMbps`
+ attribute. `(1 / speedMbps) * 2`. Default values used(taken from
+ JESD209-4C):
+ * 4267 Mbps: 468ps
+ * 3733 Mbps: 535ps
+ * 3200 Mbps: 625ps
+
+* `tckMaxPs`: SDRAM maximum cycle time (tckMax) value in
+ picoseconds. Default value used: `31875ps`. As per JESD209-4C,
+ TCKmax should be 100ns (100000ps) for all speed grades. But the SPD
+ byte to encode this field is only 1 byte. Hence, the maximum value
+ that can be encoded is 31875ps.
+
+* `taaMinPs`: Minimum CAS Latency Time(taaMin) in picoseconds. This
+ value defaults to nck * tckMin, where nck is minimum CAS latency.
+
+* `trcdMinNs`: Minimum RAS# to CAS# Delay Time (tRCDmin) in
+ nanoseconds. As per JESD209-4C, this is max(18ns, 4nck) which
+ defaults to `18ns`.
+
+* `casLatencies`: List of CAS latencies supported by the
+ part. This is dependent on the attrib `speedMbps`. Default values
+ used:
+ * 4267: `"6 10 14 20 24 28 32 36"`.
+ * 3733: `"6 10 14 20 24 28 32"`.
+ * 3200: `"6 10 14 20 24 28"`.
+
+### Example JSON file
+```
+{
+ "parts": [
+ {
+ "name": "MEMORY_PART_A",
+ "attribs": {
+ "densityPerChannelGb": 8,
+ "banks": 8,
+ "channelsPerDie": 2,
+ "diesPerPackage": 2,
+ "bitWidthPerChannel": 16,
+ "ranksPerChannel": 1,
+ "speedMbps": 4267
+ }
+ },
+ {
+ "name": "MEMORY_PART_B",
+ "attribs": {
+ "densityPerChannelGb": 8,
+ "banks": 8,
+ "channelsPerDie": 1,
+ "diesPerPackage": 2,
+ "bitWidthPerChannel": 16,
+ "ranksPerChannel": 1,
+ "speedMbps": 3733,
+ "casLatencies": "14 20 24 28 32",
+ "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 (spd_1.hex, spd_2.hex, spd_3.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, spd_1.hex
+ MEMORY_PART_B, spd_2.hex
+ MEMORY_PART_C, spd_3.hex
+ MEMORY_PART_D, spd_2.hex
+ MEMORY_PART_E, 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 += spd_1.hex # ID = 0(0b0000) Parts = MEMORY_PART_A
+SPD_SOURCES += spd_2.hex # ID = 1(0b0001) Parts = MEMORY_PART_B, MEMORY_PART_D
+SPD_SOURCES += 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>
+```
+
+### 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/intel/lp4x/gen_part_id.go b/util/spd_tools/intel/lp4x/gen_part_id.go
new file mode 100644
index 0000000000..6c2ca11d1e
--- /dev/null
+++ b/util/spd_tools/intel/lp4x/gen_part_id.go
@@ -0,0 +1,214 @@
+/* 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("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/intel/lp4x/gen_spd.go b/util/spd_tools/intel/lp4x/gen_spd.go
new file mode 100644
index 0000000000..acdc2657f1
--- /dev/null
+++ b/util/spd_tools/intel/lp4x/gen_spd.go
@@ -0,0 +1,971 @@
+/* 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 LPDDR4x 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
+ PlatformJSL = 1
+)
+
+var platformMap = map[string]int {
+ "TGL": PlatformTGL,
+ "JSL": PlatformJSL,
+}
+
+var currPlatform int
+
+type memAttributes struct {
+ /* Primary attributes - must be provided by JSON file for each part */
+ DensityPerChannelGb int
+ Banks int
+ ChannelsPerDie int
+ DiesPerPackage int
+ BitWidthPerChannel int
+ RanksPerChannel int
+ SpeedMbps int
+
+ /*
+ * All the following parameters are optional and required only if the part requires
+ * special parameters as per the datasheet.
+ */
+ /* Timing parameters */
+ TRFCABNs int
+ TRFCPBNs int
+ TRPABMinNs int
+ TRPPBMinNs int
+ TCKMinPs int
+ TCKMaxPs int
+ TAAMinPs int
+ TRCDMinNs int
+
+ /* CAS */
+ CASLatencies string
+ CASFirstByte byte
+ CASSecondByte byte
+ CASThirdByte byte
+}
+
+/* This encodes the density in Gb to SPD values as per JESD 21-C */
+var densityGbToSPDEncoding = map[int]byte {
+ 4: 0x4,
+ 6: 0xb,
+ 8: 0x5,
+ 12: 0x8,
+ 16: 0x6,
+ 24: 0x9,
+ 32: 0x7,
+}
+
+/*
+ * Table 3 from JESD209-4C.
+ * Maps density per physical channel to row-column encoding as per JESD 21-C for a device with
+ * x16 physical channel.
+ */
+var densityGbx16ChannelToRowColumnEncoding = map[int]byte {
+ 4: 0x19, /* 15 rows, 10 columns */
+ 6: 0x21, /* 16 rows, 10 columns */
+ 8: 0x21, /* 16 rows, 10 columns */
+ 12: 0x29, /* 17 rows, 10 columns */
+ 16: 0x29, /* 17 rows, 10 columns */
+}
+
+/*
+ * Table 5 from JESD209-4C.
+ * Maps density per physical channel to row-column encoding as per JESD 21-C for a device with
+ * x8 physical channel.
+ */
+var densityGbx8ChannelToRowColumnEncoding = map[int]byte {
+ 3: 0x21, /* 16 rows, 10 columns */
+ 4: 0x21, /* 16 rows, 10 columns */
+ 6: 0x29, /* 17 rows, 10 columns */
+ 8: 0x29, /* 17 rows, 10 columns */
+ 12: 0x31, /* 18 rows, 10 columns */
+ 16: 0x31, /* 18 rows, 10 columns */
+}
+
+type refreshTimings struct {
+ TRFCABNs int
+ TRFCPBNs int
+}
+
+/*
+ * Table 112 from JESD209-4C
+ * Maps density per physical channel to refresh timings. This is the same for x8 and x16
+ * devices.
+ */
+var densityGbPhysicalChannelToRefreshEncoding = map[int]refreshTimings {
+ 3: {
+ TRFCABNs: 180,
+ TRFCPBNs: 90,
+ },
+ 4: {
+ TRFCABNs: 180,
+ TRFCPBNs: 90,
+ },
+ 6: {
+ TRFCABNs: 280,
+ TRFCPBNs: 140,
+ },
+ 8: {
+ TRFCABNs: 280,
+ TRFCPBNs: 140,
+ },
+ 12: {
+ TRFCABNs: 380,
+ TRFCPBNs: 190,
+ },
+ 16: {
+ TRFCABNs: 380,
+ TRFCPBNs: 190,
+ },
+}
+
+type speedParams struct {
+ TCKMinPs int
+ TCKMaxPs int
+ CASLatenciesx16Channel string
+ CASLatenciesx8Channel string
+}
+
+const (
+ /* First Byte */
+ CAS6 = 1 << 1
+ CAS10 = 1 << 4
+ CAS14 = 1 << 7
+ /* Second Byte */
+ CAS16 = 1 << 0
+ CAS20 = 1 << 2
+ CAS22 = 1 << 3
+ CAS24 = 1 << 4
+ CAS26 = 1 << 5
+ CAS28 = 1 << 6
+ /* Third Byte */
+ CAS32 = 1 << 0
+ CAS36 = 1 << 2
+ CAS40 = 1 << 4
+)
+
+const (
+ /*
+ * JEDEC spec says that TCKmax should be 100ns for all speed grades.
+ * 100ns in MTB units comes out to be 0x320. But since this is a byte field, set it to
+ * 0xFF i.e. 31.875ns.
+ */
+ TCKMaxPsDefault = 31875
+)
+
+var speedMbpsToSPDEncoding = map[int]speedParams {
+ 4267: {
+ TCKMinPs: 468, /* 1/4267 * 2 */
+ TCKMaxPs: TCKMaxPsDefault,
+ CASLatenciesx16Channel: "6 10 14 20 24 28 32 36",
+ CASLatenciesx8Channel: "6 10 16 22 26 32 36 40",
+ },
+ 3733: {
+ TCKMinPs: 535, /* 1/3733 * 2 */
+ TCKMaxPs: TCKMaxPsDefault,
+ CASLatenciesx16Channel: "6 10 14 20 24 28 32",
+ CASLatenciesx8Channel: "6 10 16 22 26 32 36",
+ },
+ 3200: {
+ TCKMinPs: 625, /* 1/3200 * 2 */
+ TCKMaxPs: TCKMaxPsDefault,
+ CASLatenciesx16Channel: "6 10 14 20 24 28",
+ CASLatenciesx8Channel: "6 10 16 22 26 32",
+ },
+}
+
+var bankEncoding = map[int]byte {
+ 4: 0 << 4,
+ 8: 1 << 4,
+}
+
+const (
+ TGLLogicalChannelWidth = 16
+)
+
+/* Returns density to encode as per Intel MRC expectations. */
+func getMRCDensity(memAttribs *memAttributes) int {
+ if currPlatform == PlatformTGL {
+ /*
+ * Intel MRC on TGL expects density per logical channel to be encoded in
+ * SPDIndexDensityBanks. Logical channel on TGL is an x16 channel.
+ */
+ return memAttribs.DensityPerChannelGb * TGLLogicalChannelWidth / memAttribs.BitWidthPerChannel
+ } else if currPlatform == PlatformJSL {
+ /*
+ * Intel MRC on JSL expects density per die to be encoded in
+ * SPDIndexDensityBanks.
+ */
+ return memAttribs.DensityPerChannelGb * memAttribs.ChannelsPerDie
+ }
+
+ return 0
+}
+
+func encodeDensityBanks(memAttribs *memAttributes) byte {
+ var b byte
+
+ b = densityGbToSPDEncoding[getMRCDensity(memAttribs)]
+ b |= bankEncoding[memAttribs.Banks]
+
+ return b
+}
+
+func encodeSdramAddressing(memAttribs *memAttributes) byte {
+ densityPerChannelGb := memAttribs.DensityPerChannelGb
+ if memAttribs.BitWidthPerChannel == 8 {
+ return densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb]
+ } else {
+ return densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb]
+ }
+ return 0
+}
+
+func encodeChannelsPerDie(channels int) byte {
+ var temp byte
+
+ temp = byte(channels >> 1)
+
+ return temp << 2
+}
+
+func encodePackage(dies int) byte {
+ var temp byte
+
+ if dies > 1 {
+ /* If more than one die, then this is a non-monolithic device. */
+ temp = 1
+ } else {
+ /* If only single die, then this is a monolithic device. */
+ temp = 0
+ }
+
+ return temp << 7
+}
+
+func encodeDiesPerPackage(memAttribs *memAttributes) byte {
+ var dies int = 0
+ if currPlatform == PlatformTGL {
+ /* Intel MRC expects logical dies to be encoded for TGL. */
+ dies = memAttribs.ChannelsPerDie * memAttribs.RanksPerChannel * memAttribs.BitWidthPerChannel / 16
+ } else if currPlatform == PlatformJSL {
+ /* Intel MRC expects physical dies to be encoded for JSL. */
+ dies = memAttribs.DiesPerPackage
+ }
+
+ b := encodePackage(dies) /* Monolithic / Non-monolithic device */
+ b |= (byte(dies) - 1) << 4
+
+ return b
+}
+
+func encodePackageType(memAttribs *memAttributes) byte {
+ var b byte
+
+ b |= encodeChannelsPerDie(memAttribs.ChannelsPerDie)
+ b |= encodeDiesPerPackage(memAttribs)
+
+ return b
+}
+
+func encodeDataWidth(bitWidthPerChannel int) byte {
+ return byte(bitWidthPerChannel / 8)
+}
+
+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.BitWidthPerChannel)
+ b |= encodeRanks(memAttribs.RanksPerChannel)
+
+ return b
+}
+
+const (
+ /*
+ * As per advisory 616599:
+ * 7:5 (Number of system channels) = 000 (1 channel always)
+ * 2:0 (Bus width) = 001 (x16 always)
+ * Set to 0x01.
+ */
+ SPDValueBusWidthTGL = 0x01
+ /*
+ * As per advisory 610202:
+ * 7:5 (Number of system channels) = 001 (2 channel always)
+ * 2:0 (Bus width) = 010 (x32 always)
+ * Set to 0x01.
+ */
+ SPDValueBusWidthJSL = 0x22
+)
+
+func encodeBusWidth(memAttribs *memAttributes) byte {
+ if currPlatform == PlatformTGL {
+ return SPDValueBusWidthTGL
+ } else if currPlatform == PlatformJSL {
+ return SPDValueBusWidthJSL
+ }
+ return 0
+}
+
+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 encodeCASFirstByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASFirstByte
+}
+
+func encodeCASSecondByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASSecondByte
+}
+
+func encodeCASThirdByte(memAttribs *memAttributes) byte {
+ return memAttribs.CASThirdByte
+}
+
+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 convNsToMtb(timeNs int) int {
+ return convPsToMtb(convNsToPs(timeNs))
+}
+
+func convNsToMtbByte(timeNs int) byte {
+ return convPsToMtbByte(convNsToPs(timeNs))
+}
+
+func convNsToFtbByte(timeNs int) byte {
+ return convPsToFtbByte(convNsToPs(timeNs))
+}
+
+func encodeTAAMin(memAttribs *memAttributes) byte {
+ return convPsToMtbByte(memAttribs.TAAMinPs)
+}
+
+func encodeTAAMinFineOffset(memAttribs *memAttributes) byte {
+ return convPsToFtbByte(memAttribs.TAAMinPs)
+}
+
+func encodeTRCDMin(memAttribs *memAttributes) byte {
+ return convNsToMtbByte(memAttribs.TRCDMinNs)
+}
+
+func encodeTRCDMinFineOffset(memAttribs *memAttributes) byte {
+ return convNsToFtbByte(memAttribs.TRCDMinNs)
+}
+
+func encodeTRPABMin(memAttribs *memAttributes) byte {
+ return convNsToMtbByte(memAttribs.TRPABMinNs)
+}
+
+func encodeTRPABMinFineOffset(memAttribs *memAttributes) byte {
+ return convNsToFtbByte(memAttribs.TRPABMinNs)
+}
+
+func encodeTRPPBMin(memAttribs *memAttributes) byte {
+ return convNsToMtbByte(memAttribs.TRPPBMinNs)
+}
+
+func encodeTRPPBMinFineOffset(memAttribs *memAttributes) byte {
+ return convNsToFtbByte(memAttribs.TRPPBMinNs)
+}
+
+func encodeTRFCABMinMsb(memAttribs *memAttributes) byte {
+ return byte((convNsToMtb(memAttribs.TRFCABNs) >> 8) & 0xff)
+}
+
+func encodeTRFCABMinLsb(memAttribs *memAttributes) byte {
+ return byte(convNsToMtb(memAttribs.TRFCABNs) & 0xff)
+}
+
+func encodeTRFCPBMinMsb(memAttribs *memAttributes) byte {
+ return byte((convNsToMtb(memAttribs.TRFCPBNs) >> 8) & 0xff)
+}
+
+func encodeTRFCPBMinLsb(memAttribs *memAttributes) byte {
+ return byte(convNsToMtb(memAttribs.TRFCPBNs) & 0xff)
+}
+
+type SPDAttribFunc func (*memAttributes) byte
+
+type SPDAttribTableEntry struct {
+ constVal byte
+ getVal SPDAttribFunc
+}
+
+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
+ SPDIndexReadWriteLatency = 25
+ SPDIndexTRCDMin = 26
+ SPDIndexTRPABMin = 27
+ SPDIndexTRPPBMin = 28
+ SPDIndexTRFCABMinLSB = 29
+ SPDIndexTRFCABMinMSB = 30
+ SPDIndexTRFCPBMinLSB = 31
+ SPDIndexTRFCPBMinMSB = 32
+ SPDIndexTRPPBMinFineOffset = 120
+ SPDIndexTRPABMinFineOffset = 121
+ SPDIndexTRCDMinFineOffset = 122
+ SPDIndexTAAMinFineOffset = 123
+ SPDIndexTCKMaxFineOffset = 124
+ SPDIndexTCKMinFineOffset = 125
+
+ /* SPD Byte Value */
+
+ /*
+ * From JEDEC spec:
+ * 6:4 (Bytes total) = 2 (512 bytes)
+ * 3:0 (Bytes used) = 3 (384 bytes)
+ * Set to 0x23 for LPDDR4x.
+ */
+ SPDValueSize = 0x23
+
+ /*
+ * From JEDEC spec: Revision 1.1
+ * Set to 0x11.
+ */
+ SPDValueRevision = 0x11
+
+ /* LPDDR4x memory type = 0x11 */
+ SPDValueMemoryType = 0x11
+
+ /*
+ * From JEDEC spec:
+ * 7:7 (Hybrid) = 0 (Not hybrid)
+ * 6:4 (Hybrid media) = 000 (Not hybrid)
+ * 3:0 (Base Module Type) = 1110 (Non-DIMM solution)
+ *
+ * This is dependent on hardware design. LPDDR4x only has memory down solution.
+ * Hence this is not hybrid non-DIMM solution.
+ * Set to 0x0E.
+ */
+ SPDValueModuleType = 0x0e
+
+ /*
+ * 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:
+ * 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
+
+ /* Write Latency Set A and Read Latency DBI-RD disabled. */
+ SPDValueReadWriteLatency = 0x00
+)
+
+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: { getVal: encodeBusWidth },
+ SPDIndexTimebases: { constVal: SPDValueTimebases },
+ SPDIndexTCKMin: { getVal: encodeTCKMin },
+ SPDIndexTCKMax: { getVal: encodeTCKMax },
+ SPDIndexTCKMaxFineOffset: { getVal: encodeTCKMaxFineOffset },
+ SPDIndexTCKMinFineOffset: { getVal: encodeTCKMinFineOffset },
+ SPDIndexCASFirstByte: { getVal: encodeCASFirstByte },
+ SPDIndexCASSecondByte: { getVal: encodeCASSecondByte },
+ SPDIndexCASThirdByte: { getVal: encodeCASThirdByte },
+ SPDIndexCASFourthByte: { constVal: SPDValueCASFourthByte },
+ SPDIndexTAAMin: { getVal: encodeTAAMin },
+ SPDIndexTAAMinFineOffset: { getVal: encodeTAAMinFineOffset },
+ SPDIndexReadWriteLatency: { constVal: SPDValueReadWriteLatency },
+ SPDIndexTRCDMin: { getVal: encodeTRCDMin },
+ SPDIndexTRCDMinFineOffset: { getVal: encodeTRCDMinFineOffset },
+ SPDIndexTRPABMin: { getVal: encodeTRPABMin },
+ SPDIndexTRPABMinFineOffset: { getVal: encodeTRPABMinFineOffset },
+ SPDIndexTRPPBMin: { getVal: encodeTRPPBMin },
+ SPDIndexTRPPBMinFineOffset: { getVal: encodeTRPPBMinFineOffset },
+ SPDIndexTRFCABMinLSB: { getVal: encodeTRFCABMinLsb },
+ SPDIndexTRFCABMinMSB: { getVal: encodeTRFCABMinMsb },
+ SPDIndexTRFCPBMinLSB: { getVal: encodeTRFCPBMinLsb },
+ SPDIndexTRFCPBMinMSB: { getVal: encodeTRFCPBMinMsb },
+}
+
+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 getSPDByte(index int, memAttribs *memAttributes) byte {
+ e, ok := SPDAttribTable[index]
+ if ok == false {
+ 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("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 validateDensityx8Channel(densityPerChannelGb int) error {
+ if _, ok := densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false {
+ return fmt.Errorf("Incorrect x8 density: ", densityPerChannelGb, "Gb")
+ }
+ return nil
+}
+
+func validateDensityx16Channel(densityPerChannelGb int) error {
+ if _, ok := densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false {
+ return fmt.Errorf("Incorrect x16 density: ", densityPerChannelGb, "Gb")
+ }
+ return nil
+}
+
+func validateDensity(memAttribs *memAttributes) error {
+ if memAttribs.BitWidthPerChannel == 8 {
+ return validateDensityx8Channel(memAttribs.DensityPerChannelGb)
+ } else if memAttribs.BitWidthPerChannel == 16 {
+ return validateDensityx16Channel(memAttribs.DensityPerChannelGb)
+ }
+
+ return fmt.Errorf("No density table for this bit width: ", memAttribs.BitWidthPerChannel)
+}
+
+func validateBanks(banks int) error {
+ if banks != 4 && banks != 8 {
+ return fmt.Errorf("Incorrect banks: ", banks)
+ }
+ return nil
+}
+
+func validateChannels(channels int) error {
+ if channels != 1 && channels != 2 && channels != 4 {
+ return fmt.Errorf("Incorrect channels per die: ", channels)
+ }
+ return nil
+}
+
+func validateDataWidth(width int) error {
+ if width != 8 && width != 16 {
+ return fmt.Errorf("Incorrect bit width: ", width)
+ }
+ return nil
+}
+
+func validateRanks(ranks int) error {
+ if ranks != 1 && ranks != 2 {
+ return fmt.Errorf("Incorrect ranks: ", ranks)
+ }
+ return nil
+}
+
+func validateSpeed(speed int) error {
+ if _, ok := speedMbpsToSPDEncoding[speed]; ok == false {
+ return fmt.Errorf("Incorrect speed: ", speed, " Mbps")
+ }
+ return nil
+}
+
+func validateMemoryParts(memParts *memParts) error {
+ for i := 0; i < len(memParts.MemParts); i++ {
+ if err := validateBanks(memParts.MemParts[i].Attribs.Banks); err != nil {
+ return err
+ }
+ if err := validateChannels(memParts.MemParts[i].Attribs.ChannelsPerDie); err != nil {
+ return err
+ }
+ if err := validateDataWidth(memParts.MemParts[i].Attribs.BitWidthPerChannel); err != nil {
+ return err
+ }
+ if err := validateDensity(&memParts.MemParts[i].Attribs); err != nil {
+ return err
+ }
+ if err := validateRanks(memParts.MemParts[i].Attribs.RanksPerChannel); err != nil {
+ return err
+ }
+ if err := validateSpeed(memParts.MemParts[i].Attribs.SpeedMbps); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func encodeLatencies(latency int, memAttribs *memAttributes) error {
+ switch latency {
+ case 6:
+ memAttribs.CASFirstByte |= CAS6
+ case 10:
+ memAttribs.CASFirstByte |= CAS10
+ case 14:
+ memAttribs.CASFirstByte |= CAS14
+ case 16:
+ memAttribs.CASSecondByte |= CAS16
+ case 20:
+ memAttribs.CASSecondByte |= CAS20
+ case 22:
+ memAttribs.CASSecondByte |= CAS22
+ case 24:
+ memAttribs.CASSecondByte |= CAS24
+ case 26:
+ memAttribs.CASSecondByte |= CAS26
+ case 28:
+ memAttribs.CASSecondByte |= CAS28
+ case 32:
+ memAttribs.CASThirdByte |= CAS32
+ case 36:
+ memAttribs.CASThirdByte |= CAS36
+ case 40:
+ memAttribs.CASThirdByte |= CAS40
+ default:
+ fmt.Errorf("Incorrect CAS Latency: ", latency)
+ }
+
+ return nil
+}
+
+func updateTCK(memAttribs *memAttributes) {
+ if memAttribs.TCKMinPs == 0 {
+ memAttribs.TCKMinPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMinPs
+ }
+ if memAttribs.TCKMaxPs == 0 {
+ memAttribs.TCKMaxPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMaxPs
+ }
+}
+
+func getCASLatencies(memAttribs *memAttributes) string {
+ if memAttribs.BitWidthPerChannel == 16 {
+ return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx16Channel
+ } else if memAttribs.BitWidthPerChannel == 8 {
+ return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx8Channel
+ }
+
+ return ""
+}
+
+func updateCAS(memAttribs *memAttributes) error {
+ if len(memAttribs.CASLatencies) == 0 {
+ memAttribs.CASLatencies = getCASLatencies(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 getMinCAS(memAttribs *memAttributes) (int, error) {
+ if (memAttribs.CASThirdByte & CAS40) != 0 {
+ return 40, nil
+ }
+ if (memAttribs.CASThirdByte & CAS36) != 0 {
+ return 36, nil
+ }
+ if (memAttribs.CASThirdByte & CAS32) != 0 {
+ return 32, nil
+ }
+ if (memAttribs.CASSecondByte & CAS28) != 0 {
+ return 28, nil
+ }
+
+ return 0, fmt.Errorf("Unexpected min CAS")
+}
+
+func updateTAAMin(memAttribs *memAttributes) error {
+ if memAttribs.TAAMinPs == 0 {
+ minCAS, err := getMinCAS(memAttribs)
+ if err != nil {
+ return err
+ }
+ memAttribs.TAAMinPs = memAttribs.TCKMinPs * minCAS
+ }
+
+ return nil
+}
+
+func updateTRFCAB(memAttribs *memAttributes) {
+ if memAttribs.TRFCABNs == 0 {
+ memAttribs.TRFCABNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCABNs
+ }
+}
+
+func updateTRFCPB(memAttribs *memAttributes) {
+ if memAttribs.TRFCPBNs == 0 {
+ memAttribs.TRFCPBNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCPBNs
+ }
+}
+
+func updateTRCD(memAttribs *memAttributes) {
+ if memAttribs.TRCDMinNs == 0 {
+ /* JEDEC spec says max of 18ns */
+ memAttribs.TRCDMinNs = 18
+ }
+}
+
+func updateTRPAB(memAttribs *memAttributes) {
+ if memAttribs.TRPABMinNs == 0 {
+ /* JEDEC spec says max of 21ns */
+ memAttribs.TRPABMinNs = 21
+ }
+}
+
+func updateTRPPB(memAttribs *memAttributes) {
+ if memAttribs.TRPPBMinNs == 0 {
+ /* JEDEC spec says max of 18ns */
+ memAttribs.TRPPBMinNs = 18
+ }
+}
+
+func normalizeMemoryAttributes(memAttribs *memAttributes) {
+ if currPlatform == PlatformTGL {
+ /*
+ * TGL does not really use physical organization of dies per package when
+ * generating the SPD. So, set it to 0 here so that deduplication ignores
+ * that field.
+ */
+ memAttribs.DiesPerPackage = 0
+ }
+}
+
+func updateMemoryAttributes(memAttribs *memAttributes) error {
+ updateTCK(memAttribs)
+ if err := updateCAS(memAttribs); err != nil {
+ return err
+ }
+ if err := updateTAAMin(memAttribs); err != nil {
+ return err
+ }
+ updateTRFCAB(memAttribs)
+ updateTRFCPB(memAttribs)
+ updateTRCD(memAttribs)
+ updateTRPAB(memAttribs)
+ updateTRPPB(memAttribs)
+
+ normalizeMemoryAttributes(memAttribs)
+
+ return nil
+}
+
+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++ {
+ if err := updateMemoryAttributes(&memParts.MemParts[i].Attribs); err != nil {
+ log.Fatal(err)
+ }
+
+ 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)
+ }
+}