/* SPDX-License-Identifier: GPL-2.0-or-later */
package main

import (
	"encoding/json"
	"fmt"
)

/* ------------------------------------------------------------------------------------------ */
/*                                     LP5-defined types                                      */
/* ------------------------------------------------------------------------------------------ */

type lp5 struct {
}

type LP5MemAttributes struct {
	/* Primary attributes - must be provided by JSON file for each part */
	DensityPerDieGb    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.
	 */
	LP5X bool

	/* Timing parameters */
	TRFCABNs   int
	TRFCPBNs   int
	TRPABMinNs int
	TRPPBMinNs int
	TCKMinPs   int
	TAAMinPs   int
	TRCDMinNs  int
}

type LP5DensityParams struct {
	DensityEncoding          byte
	RowAddressBitsx8Channel  int
	RowAddressBitsx16Channel int
	TRFCABNs                 int
	TRFCPBNs                 int
}

type LP5SpeedParams struct {
	defaultTCKMinPs int
	MaxCASLatency   int
}

type LP5BankArchParams struct {
	NumBanks         int
	BankGroups       int
	BurstAddressBits int
}

type LP5SPDAttribFunc func(*LP5MemAttributes) byte

type LP5SPDAttribTableEntry struct {
	constVal byte
	getVal   LP5SPDAttribFunc
}

type LP5SetFunc func(*LP5MemAttributes) int

type LP5Set struct {
	SPDRevision           byte
	getBankArch           LP5SetFunc
	optionalFeatures      byte
	otherOptionalFeatures byte
	busWidthEncoding      byte
	speedToTCKMinPs       map[int]int
}

/* ------------------------------------------------------------------------------------------ */
/*                                         Constants                                          */
/* ------------------------------------------------------------------------------------------ */

const (
	/* SPD Byte Index */
	LP5SPDIndexSize                            = 0
	LP5SPDIndexRevision                        = 1
	LP5SPDIndexMemoryType                      = 2
	LP5SPDIndexModuleType                      = 3
	LP5SPDIndexDensityBanks                    = 4
	LP5SPDIndexAddressing                      = 5
	LP5SPDIndexPackageType                     = 6
	LP5SPDIndexOptionalFeatures                = 7
	LP5SPDIndexOtherOptionalFeatures           = 9
	LP5SPDIndexModuleOrganization              = 12
	LP5SPDIndexBusWidth                        = 13
	LP5SPDIndexTimebases                       = 17
	LP5SPDIndexTCKMin                          = 18
	LP5SPDIndexTAAMin                          = 24
	LP5SPDIndexTRCDMin                         = 26
	LP5SPDIndexTRPABMin                        = 27
	LP5SPDIndexTRPPBMin                        = 28
	LP5SPDIndexTRFCABMinLSB                    = 29
	LP5SPDIndexTRFCABMinMSB                    = 30
	LP5SPDIndexTRFCPBMinLSB                    = 31
	LP5SPDIndexTRFCPBMinMSB                    = 32
	LP5SPDIndexTRPPBMinFineOffset              = 120
	LP5SPDIndexTRPABMinFineOffset              = 121
	LP5SPDIndexTRCDMinFineOffset               = 122
	LP5SPDIndexTAAMinFineOffset                = 123
	LP5SPDIndexTCKMinFineOffset                = 125
	LP5SPDIndexManufacturerPartNumberStartByte = 329
	LP5SPDIndexManufacturerPartNumberEndByte   = 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 LPDDR5.
	 */
	LP5SPDValueSize = 0x23

	/*
	 * Revision 1.0. Expected by ADL
	 */
	LP5SPDValueRevision1_0 = 0x10
	/*
	 * Revision 1.1. Expected by Mendocino
	 */
	LP5SPDValueRevision1_1 = 0x11

	/*
	 * As per advisory #616599, ADL MRC expects LPDDR5 memory type = 0x13.
	 * From JEDEC spec, LPDDR5X memory type = 0x15.
	 */
	LP5SPDValueMemoryType  = 0x13
	LP5XSPDValueMemoryType = 0x15

	/*
	 * 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. LPDDR5 only has memory down solution.
	 * Hence this is not hybrid non-DIMM solution.
	 * Set to 0x0E.
	 */
	LP5SPDValueModuleType = 0x0e

	/*
	 * From JEDEC spec:
	 * 3:2 (MTB) = 00 (0.125ns)
	 * 1:0 (FTB) = 00 (1ps)
	 * Set to 0x00.
	 */
	LP5SPDValueTimebases = 0x00

	/* As per JEDEC spec, unused digits of manufacturer part number are left as blank. */
	LP5SPDValueManufacturerPartNumberBlank = 0x20
)

const (
	// The column addresses are the same for x8 & x16 and for all Bank Architectures.
	LP5ColAddressBits = 6
)

const (
	// LPDDR5 has a flexible bank architecture with three programmable bank modes: BG, 8B, 16B.
	LP5BGBankArch = iota
	LP58BBankArch
	LP516BBankArch
)

/* ------------------------------------------------------------------------------------------ */
/*                                    Global variables                                        */
/* ------------------------------------------------------------------------------------------ */

var LP5PlatformSetMap = map[int][]int{
	0: {PlatformMTL, PlatformADL},
	1: {PlatformPHX, PlatformMDN},
}

var LP5SetInfo = map[int]LP5Set{
	0: {
		SPDRevision: LP5SPDValueRevision1_0,
		getBankArch: LP5GetBankArchSet0,
		/*
		 * From JEDEC spec:
		 * 5:4 (Maximum Activate Window) = 00 (8192 * tREFI)
		 * 3:0 (Maximum Activate Count) = 1000 (Unlimited MAC)
		 * Set to 0x08.
		 */
		optionalFeatures: 0x08,
		/*
		 * For ADL (as per advisory #616599):
		 * 7:5 (Number of system channels) = 000 (1 channel always)
		 * 4:3 (Bus width extension) = 00 (no ECC)
		 * 2:0 (Bus width) = 001 (x16 always)
		 * Set to 0x01.
		 */
		busWidthEncoding: 0x01,
		/*
		 * TCKMinPs:
		 * LPDDR5 has two clocks: the command/address clock (CK) and the data clock (WCK). They are
		 * related by the WCK:CK ratio, which can be either 4:1 or 2:1. On ADL, 4:1 is used.
		 * For ADL, the MRC expects the tCKmin to encode the CK cycle time.
		 *   tCKmin   = 1 / CK rate
		 *            = 1 / (WCK rate / WCK:CK)
		 *            = 1 / (speed grade / 2 / WCK:CK)      // "double data rate"
		 */
		speedToTCKMinPs: map[int]int{
			8533: 937,  /* 1 / (8533 / 2 / 4) */
			7500: 1066, /* 1 / (7500 / 2 / 4) */
			6400: 1250, /* 1 / (6400 / 2 / 4) */
			5500: 1455, /* 1 / (5500 / 2 / 4) */
		},
	},
	1: {
		SPDRevision: LP5SPDValueRevision1_1,
		getBankArch: LP5GetBankArchSet1,
		/*
		 * For Mendocino (as per advisory b/211510456):
		 * 5:4 (Maximum Activate Window) = 01 (4096 * tREFI)
		 * 3:0 (Maximum Activate Count) = 1000 (Unlimited MAC)
		 * Set to 0x18.
		 */
		optionalFeatures: 0x18,
		/*
		 * For Mendocino (as per advisory b/211510456):
		 * 7:6 (PPR) = 1 (Post Package Repair is supported)
		 * Set to 0x40.
		 */
		otherOptionalFeatures: 0x40,
		/*
		 * For Mendocino (as per advisory b/211510456):
		 * 7:5 (Number of system channels) = 000 (1 channel always)
		 * 4:3 (Bus width extension) = 00 (no ECC)
		 * 2:0 (Bus width) = 010 (x32 always)
		 * Set to 0x02.
		 */
		busWidthEncoding: 0x02,
	},
}

var LP5PartAttributeMap = map[string]LP5MemAttributes{}
var LP5CurrSet int

/*
 * DensityEncoding: Maps the die density in Gb to the SPD encoding of the die density
 * as per JESD 21-C.
 *
 * RowAddressBits: Maps the die density to the number of row address bits.
 * Tables 6-11 in JESD209-5B (same for all three bank modes).
 *
 * TRFCABNs/TRFCPBNs: Maps the die density to the refresh timings.
 * Tables 235 and 236 in JESD209-5B (same for all three bank modes).
 */
var LP5DensityGbToSPDEncoding = map[int]LP5DensityParams{
	4: {
		DensityEncoding:          0x4,
		RowAddressBitsx8Channel:  15,
		RowAddressBitsx16Channel: 14,
		TRFCABNs:                 180,
		TRFCPBNs:                 90,
	},
	6: {
		DensityEncoding:          0xb,
		RowAddressBitsx8Channel:  16,
		RowAddressBitsx16Channel: 15,
		TRFCABNs:                 210,
		TRFCPBNs:                 120,
	},
	8: {
		DensityEncoding:          0x5,
		RowAddressBitsx8Channel:  16,
		RowAddressBitsx16Channel: 15,
		TRFCABNs:                 210,
		TRFCPBNs:                 120,
	},
	12: {
		DensityEncoding:          0x8,
		RowAddressBitsx8Channel:  17,
		RowAddressBitsx16Channel: 16,
		TRFCABNs:                 280,
		TRFCPBNs:                 140,
	},
	16: {
		DensityEncoding:          0x6,
		RowAddressBitsx8Channel:  17,
		RowAddressBitsx16Channel: 16,
		TRFCABNs:                 280,
		TRFCPBNs:                 140,
	},
	24: {
		DensityEncoding:          0x9,
		RowAddressBitsx8Channel:  18,
		RowAddressBitsx16Channel: 17,
		TRFCABNs:                 380,
		TRFCPBNs:                 190,
	},
	32: {
		DensityEncoding:          0x7,
		RowAddressBitsx8Channel:  18,
		RowAddressBitsx16Channel: 17,
		TRFCABNs:                 380,
		TRFCPBNs:                 190,
	},
}

/*
 * Maps the number of banks to the SPD encoding as per JESD 21-C.
 */
var LP5NumBanksEncoding = map[int]byte{
	4:  0x0,
	8:  0x1,
	16: 0x2,
}

/*
 * Maps the Bank Group bits to the SPD encoding as per JESD 21-C.
 */
var LP5BankGroupsEncoding = map[int]byte{
	1: 0x0,
	2: 0x1,
	4: 0x2,
}

/*
 * Maps the number of row address bits to the SPD encoding as per JESD 21-C.
 */
var LP5RowAddressBitsEncoding = map[int]byte{
	14: 0x2,
	15: 0x3,
	16: 0x4,
	17: 0x5,
	18: 0x6,
}

/*
 * Maps the number of column address bits to the SPD encoding as per JESD 21-C.
 */
var LP5ColAddressBitsEncoding = map[int]byte{
	9:  0x0,
	10: 0x1,
	11: 0x2,
	12: 0x3,
}

var LP5BankArchToSPDEncoding = map[int]LP5BankArchParams{
	LP5BGBankArch: {
		NumBanks:         4,
		BankGroups:       4,
		BurstAddressBits: 4,
	},
	LP58BBankArch: {
		NumBanks:         8,
		BankGroups:       1,
		BurstAddressBits: 5,
	},
	LP516BBankArch: {
		NumBanks:         16,
		BankGroups:       1,
		BurstAddressBits: 4,
	},
}

/*
 * TCKMinPs:
 * Data sheets recommend encoding the the WCK cycle time.
 *   tCKmin   = 1 / WCK rate
 *            = 1 / (speed grade / 2)      // "double data rate"
 *
 * MaxCASLatency:
 * From Table 220 of JESD209-5B, using a 4:1 WCK:CK ratio and Set 0.
 */
var LP5SpeedMbpsToSPDEncoding = map[int]LP5SpeedParams{
	8533: {
		defaultTCKMinPs: 234, /* 1 / (8533 / 2) */
		MaxCASLatency:   23,
	},
	7500: {
		defaultTCKMinPs: 266, /* 1 / (7500 / 2) */
		MaxCASLatency:   20,
	},
	6400: {
		defaultTCKMinPs: 312, /* 1 / (6400 / 2) */
		MaxCASLatency:   17,
	},
	5500: {
		defaultTCKMinPs: 363, /* 1 / (5500 / 2) */
		MaxCASLatency:   15,
	},
}

var LP5SPDAttribTable = map[int]LP5SPDAttribTableEntry{
	LP5SPDIndexSize:                  {constVal: LP5SPDValueSize},
	LP5SPDIndexRevision:              {getVal: LP5EncodeSPDRevision},
	LP5SPDIndexMemoryType:            {getVal: LP5EncodeMemoryType},
	LP5SPDIndexModuleType:            {constVal: LP5SPDValueModuleType},
	LP5SPDIndexDensityBanks:          {getVal: LP5EncodeDensityBanks},
	LP5SPDIndexAddressing:            {getVal: LP5EncodeSdramAddressing},
	LP5SPDIndexPackageType:           {getVal: LP5EncodePackageType},
	LP5SPDIndexOptionalFeatures:      {getVal: LP5EncodeOptionalFeatures},
	LP5SPDIndexOtherOptionalFeatures: {getVal: LP5EncodeOtherOptionalFeatures},
	LP5SPDIndexModuleOrganization:    {getVal: LP5EncodeModuleOrganization},
	LP5SPDIndexBusWidth:              {getVal: LP5EncodeBusWidth},
	LP5SPDIndexTimebases:             {constVal: LP5SPDValueTimebases},
	LP5SPDIndexTCKMin:                {getVal: LP5EncodeTCKMin},
	LP5SPDIndexTCKMinFineOffset:      {getVal: LP5EncodeTCKMinFineOffset},
	LP5SPDIndexTAAMin:                {getVal: LP5EncodeTAAMin},
	LP5SPDIndexTAAMinFineOffset:      {getVal: LP5EncodeTAAMinFineOffset},
	LP5SPDIndexTRCDMin:               {getVal: LP5EncodeTRCDMin},
	LP5SPDIndexTRCDMinFineOffset:     {getVal: LP5EncodeTRCDMinFineOffset},
	LP5SPDIndexTRPABMin:              {getVal: LP5EncodeTRPABMin},
	LP5SPDIndexTRPABMinFineOffset:    {getVal: LP5EncodeTRPABMinFineOffset},
	LP5SPDIndexTRPPBMin:              {getVal: LP5EncodeTRPPBMin},
	LP5SPDIndexTRPPBMinFineOffset:    {getVal: LP5EncodeTRPPBMinFineOffset},
	LP5SPDIndexTRFCABMinLSB:          {getVal: LP5EncodeTRFCABMinLsb},
	LP5SPDIndexTRFCABMinMSB:          {getVal: LP5EncodeTRFCABMinMsb},
	LP5SPDIndexTRFCPBMinLSB:          {getVal: LP5EncodeTRFCPBMinLsb},
	LP5SPDIndexTRFCPBMinMSB:          {getVal: LP5EncodeTRFCPBMinMsb},
}

/* ------------------------------------------------------------------------------------------ */
/*                                        Functions                                           */
/* ------------------------------------------------------------------------------------------ */
func LP5EncodeSPDRevision(memAttribs *LP5MemAttributes) byte {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false {
		return 0
	}

	return f.SPDRevision
}

func LP5GetBankArchSet0(memAttribs *LP5MemAttributes) int {
	// ADL will use 8B mode for all parts.
	return LP58BBankArch
}

func LP5GetBankArchSet1(memAttribs *LP5MemAttributes) int {
	/*
	 * Mendocino does not support 8B. It uses 16B Bank Architecture for speed <= 3200 Mbps.
	 * It uses BG Bank Architecture for speed > 3200 Mbps.
	 */
	if memAttribs.SpeedMbps <= 3200 {
		return LP516BBankArch
	}
	return LP5BGBankArch
}

func LP5GetBankArch(memAttribs *LP5MemAttributes) int {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false || f.getBankArch == nil {
		return LP5BGBankArch
	}

	return f.getBankArch(memAttribs)
}

func LP5GetNumBanks(memAttribs *LP5MemAttributes) int {
	return LP5BankArchToSPDEncoding[LP5GetBankArch(memAttribs)].NumBanks
}

func LP5GetBankGroups(memAttribs *LP5MemAttributes) int {
	return LP5BankArchToSPDEncoding[LP5GetBankArch(memAttribs)].BankGroups
}

func LP5EncodeMemoryType(memAttribs *LP5MemAttributes) byte {
	var b byte

	// Mendocino supports LP5x, but doesn't support 0x15 as a memory type currently.
	// Temporary workaround until it's supported with ABL changes
	if memAttribs.LP5X && LP5CurrSet != 1 {
		b = LP5XSPDValueMemoryType
	} else {
		b = LP5SPDValueMemoryType
	}
	return b
}

func LP5EncodeDensityBanks(memAttribs *LP5MemAttributes) byte {
	var b byte

	// 3:0 Density per die.
	b = LP5DensityGbToSPDEncoding[memAttribs.DensityPerDieGb].DensityEncoding

	// 5:4 Bank address bits.
	b |= LP5NumBanksEncoding[LP5GetNumBanks(memAttribs)] << 4
	// 7:6 Bank group bits.
	b |= LP5BankGroupsEncoding[LP5GetBankGroups(memAttribs)] << 6

	return b
}

func LP5GetBurstAddressBits(memAttribs *LP5MemAttributes) int {
	return LP5BankArchToSPDEncoding[LP5GetBankArch(memAttribs)].BurstAddressBits
}

func LP5EncodeSdramAddressing(memAttribs *LP5MemAttributes) byte {
	var b byte

	// 2:0 Column address bits.
	b = LP5ColAddressBitsEncoding[LP5ColAddressBits+LP5GetBurstAddressBits(memAttribs)]

	// 5:3 Row address bits.
	density := memAttribs.DensityPerDieGb
	var rowAddressBits int
	if memAttribs.BitWidthPerChannel == 8 {
		rowAddressBits = LP5DensityGbToSPDEncoding[density].RowAddressBitsx8Channel
	} else {
		rowAddressBits = LP5DensityGbToSPDEncoding[density].RowAddressBitsx16Channel
	}
	b |= LP5RowAddressBitsEncoding[rowAddressBits] << 3

	return b
}

func LP5EncodePackageType(memAttribs *LP5MemAttributes) byte {
	var b byte

	// 1:0 Signal loading index.
	b = 1

	// 3:2 Channels per package.
	// Channels per package = package width (e.g. x32) / bitWidthPerChannel (x8 or x16).
	// This can equivalently be calculated as diesPerPackage / ranksPerChannel.
	// This calculation is used to avoid adding a redundant attribute for package width.
	channels := memAttribs.DiesPerPackage / memAttribs.RanksPerChannel
	b |= byte(channels>>1) << 2

	// 6:4 Dies per package.
	b |= (byte(memAttribs.DiesPerPackage) - 1) << 4

	// 7:7 Package type.
	var packageType byte
	if memAttribs.DiesPerPackage > 1 {
		packageType = 1 // Non-Monolithic
	} else {
		packageType = 0 // Monolithic
	}
	b |= packageType << 7

	return b
}

func LP5EncodeModuleOrganization(memAttribs *LP5MemAttributes) byte {
	var b byte

	// 2:0 Device data width per channel
	b = byte(memAttribs.BitWidthPerChannel / 8)

	// 5:3 Package ranks per channel
	b |= byte(memAttribs.RanksPerChannel-1) << 3

	return b
}

func LP5EncodeOptionalFeatures(memAttribs *LP5MemAttributes) byte {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false {
		return 0
	}

	return f.optionalFeatures
}

func LP5EncodeOtherOptionalFeatures(memAttribs *LP5MemAttributes) byte {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false {
		return 0
	}

	return f.otherOptionalFeatures
}

func LP5EncodeBusWidth(memAttribs *LP5MemAttributes) byte {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false {
		return 0
	}

	return f.busWidthEncoding
}

func LP5GetTCKMinPs(memAttribs *LP5MemAttributes) int {
	f, ok := LP5SetInfo[LP5CurrSet]

	if ok == false || f.speedToTCKMinPs == nil {
		return LP5SpeedMbpsToSPDEncoding[memAttribs.SpeedMbps].defaultTCKMinPs
	}

	tCKMinPs, ok := f.speedToTCKMinPs[memAttribs.SpeedMbps]
	if ok == false || tCKMinPs == 0 {
		fmt.Printf("TCKMinPs not defined for speed %d(Mbps) in LP5Set %d\n", memAttribs.SpeedMbps, LP5CurrSet)
	}

	return tCKMinPs
}

func LP5EncodeTCKMin(memAttribs *LP5MemAttributes) byte {
	return convPsToMtbByte(memAttribs.TCKMinPs)
}

func LP5EncodeTCKMinFineOffset(memAttribs *LP5MemAttributes) byte {
	return convPsToFtbByte(memAttribs.TCKMinPs)
}

func LP5EncodeTAAMin(memAttribs *LP5MemAttributes) byte {
	return convPsToMtbByte(memAttribs.TAAMinPs)
}

func LP5EncodeTAAMinFineOffset(memAttribs *LP5MemAttributes) byte {
	return convPsToFtbByte(memAttribs.TAAMinPs)
}

func LP5EncodeTRCDMin(memAttribs *LP5MemAttributes) byte {
	return convNsToMtbByte(memAttribs.TRCDMinNs)
}

func LP5EncodeTRCDMinFineOffset(memAttribs *LP5MemAttributes) byte {
	return convNsToFtbByte(memAttribs.TRCDMinNs)
}

func LP5EncodeTRPABMin(memAttribs *LP5MemAttributes) byte {
	return convNsToMtbByte(memAttribs.TRPABMinNs)
}

func LP5EncodeTRPABMinFineOffset(memAttribs *LP5MemAttributes) byte {
	return convNsToFtbByte(memAttribs.TRPABMinNs)
}

func LP5EncodeTRPPBMin(memAttribs *LP5MemAttributes) byte {
	return convNsToMtbByte(memAttribs.TRPPBMinNs)
}

func LP5EncodeTRPPBMinFineOffset(memAttribs *LP5MemAttributes) byte {
	return convNsToFtbByte(memAttribs.TRPPBMinNs)
}

func LP5EncodeTRFCABMinMsb(memAttribs *LP5MemAttributes) byte {
	return byte((convNsToMtb(memAttribs.TRFCABNs) >> 8) & 0xff)
}

func LP5EncodeTRFCABMinLsb(memAttribs *LP5MemAttributes) byte {
	return byte(convNsToMtb(memAttribs.TRFCABNs) & 0xff)
}

func LP5EncodeTRFCPBMinMsb(memAttribs *LP5MemAttributes) byte {
	return byte((convNsToMtb(memAttribs.TRFCPBNs) >> 8) & 0xff)
}

func LP5EncodeTRFCPBMinLsb(memAttribs *LP5MemAttributes) byte {
	return byte(convNsToMtb(memAttribs.TRFCPBNs) & 0xff)
}

func LP5UpdateTCKMin(memAttribs *LP5MemAttributes) {
	if memAttribs.TCKMinPs == 0 {
		memAttribs.TCKMinPs = LP5GetTCKMinPs(memAttribs)
	}
}

func LP5UpdateTAAMin(memAttribs *LP5MemAttributes) {
	if memAttribs.TAAMinPs == 0 {
		maxCAS := LP5SpeedMbpsToSPDEncoding[memAttribs.SpeedMbps].MaxCASLatency
		memAttribs.TAAMinPs = memAttribs.TCKMinPs * maxCAS
	}
}

func LP5UpdateTRFCAB(memAttribs *LP5MemAttributes) {
	if memAttribs.TRFCABNs == 0 {
		memAttribs.TRFCABNs = LP5DensityGbToSPDEncoding[memAttribs.DensityPerDieGb].TRFCABNs
	}
}

func LP5UpdateTRFCPB(memAttribs *LP5MemAttributes) {
	if memAttribs.TRFCPBNs == 0 {
		memAttribs.TRFCPBNs = LP5DensityGbToSPDEncoding[memAttribs.DensityPerDieGb].TRFCPBNs
	}
}

func LP5UpdateTRCD(memAttribs *LP5MemAttributes) {
	if memAttribs.TRCDMinNs == 0 {
		/* Table 372 from JESD209-5B */
		memAttribs.TRCDMinNs = 18
	}
}

func LP5UpdateTRPAB(memAttribs *LP5MemAttributes) {
	if memAttribs.TRPABMinNs == 0 {
		/* Table 372 from JESD209-5B */
		memAttribs.TRPABMinNs = 21
	}
}

func LP5UpdateTRPPB(memAttribs *LP5MemAttributes) {
	if memAttribs.TRPPBMinNs == 0 {
		/* Table 372 from JESD209-5B */
		memAttribs.TRPPBMinNs = 18
	}
}

func lp5UpdateMemoryAttributes(memAttribs *LP5MemAttributes) {
	LP5UpdateTCKMin(memAttribs)
	LP5UpdateTAAMin(memAttribs)
	LP5UpdateTRFCAB(memAttribs)
	LP5UpdateTRFCPB(memAttribs)
	LP5UpdateTRCD(memAttribs)
	LP5UpdateTRPAB(memAttribs)
	LP5UpdateTRPPB(memAttribs)
}

func LP5ValidateDensity(density int) error {
	if _, ok := LP5DensityGbToSPDEncoding[density]; !ok {
		return fmt.Errorf("Incorrect density per die: %d Gb", density)
	}
	return nil
}

func LP5ValidateDies(dies int) error {
	if dies != 2 && dies != 4 && dies != 8 {
		return fmt.Errorf("Incorrect dies: %d", dies)
	}
	return nil
}

func LP5ValidateDataWidth(width int) error {
	if width != 8 && width != 16 {
		return fmt.Errorf("Incorrect bit width: %d", width)
	}
	return nil
}

func LP5ValidateRanks(ranks int) error {
	if ranks != 1 && ranks != 2 {
		return fmt.Errorf("Incorrect ranks: %d", ranks)
	}
	return nil
}

func LP5ValidateSpeed(speed int) error {
	if _, ok := LP5SpeedMbpsToSPDEncoding[speed]; !ok {
		return fmt.Errorf("Incorrect speed: %d Mbps", speed)
	}
	return nil
}

func lp5ValidateMemPartAttributes(memAttribs *LP5MemAttributes) error {
	if err := LP5ValidateDensity(memAttribs.DensityPerDieGb); err != nil {
		return err
	}
	if err := LP5ValidateDies(memAttribs.DiesPerPackage); err != nil {
		return err
	}
	if err := LP5ValidateDataWidth(memAttribs.BitWidthPerChannel); err != nil {
		return err
	}
	if err := LP5ValidateRanks(memAttribs.RanksPerChannel); err != nil {
		return err
	}
	if err := LP5ValidateSpeed(memAttribs.SpeedMbps); err != nil {
		return err
	}

	return nil
}

func LP5IsManufacturerPartNumberByte(index int) bool {
	if index >= LP5SPDIndexManufacturerPartNumberStartByte &&
		index <= LP5SPDIndexManufacturerPartNumberEndByte {
		return true
	}
	return false
}

/* ------------------------------------------------------------------------------------------ */
/*                                  Interface Functions                                       */
/* ------------------------------------------------------------------------------------------ */

func (lp5) getSetMap() map[int][]int {
	return LP5PlatformSetMap
}

func (lp5) addNewPart(name string, attribs interface{}) error {
	var lp5Attributes LP5MemAttributes
	eByte, err := json.Marshal(attribs)
	if err != nil {
		return err
	}

	if err := json.Unmarshal(eByte, &lp5Attributes); err != nil {
		return err
	}

	if err := lp5ValidateMemPartAttributes(&lp5Attributes); err != nil {
		return err
	}

	LP5PartAttributeMap[name] = lp5Attributes
	return nil
}

func (lp5) getSPDAttribs(name string, set int) (interface{}, error) {
	lp5Attributes := LP5PartAttributeMap[name]

	LP5CurrSet = set

	lp5UpdateMemoryAttributes(&lp5Attributes)

	return lp5Attributes, nil
}

func (lp5) getSPDLen() int {
	return 512
}

func (lp5) getSPDByte(index int, attribs interface{}) byte {
	e, ok := LP5SPDAttribTable[index]
	if !ok {
		if LP5IsManufacturerPartNumberByte(index) {
			return LP5SPDValueManufacturerPartNumberBlank
		}
		return 0x00
	}

	if e.getVal != nil {
		var lp5Attribs LP5MemAttributes
		lp5Attribs = attribs.(LP5MemAttributes)
		return e.getVal(&lp5Attribs)
	}

	return e.constVal
}