linux-edac.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping
@ 2022-09-10 19:49 Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 01/18] EDAC/synopsys: Convert sysfs nodes to debugfs ones Serge Semin
                   ` (17 more replies)
  0 siblings, 18 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, James Morse, Robert Richter, linux-arm-kernel,
	linux-edac, linux-kernel

This patchset is a second one in the series created in the framework of my
Baikal-T1 DDRC-related work:

[1: In-progress] EDAC/mc/synopsys: Various fixes and cleanups
Link: https://lore.kernel.org/linux-edac/20220910194237.10142-1-Sergey.Semin@baikalelectronics.ru
[2: In-progress] EDAC/synopsys: Add generic DDRC info and address mapping
Link: ---you are looking at it---
[3: In-progress] EDAC/synopsys: Add generic resources and Baikal-T1 support
Link: https://lore.kernel.org/linux-edac/20220822191957.28546-1-Sergey.Semin@baikalelectronics.ru

Note the patchsets above must be merged in the same order as they are
placed in the list in order to prevent conflicts. Nothing prevents them
from being reviewed synchronously though. Any tests are very welcome.
Thanks in advance.

The second patchset mainly concerns converting the DW uMCTL2 DDRC driver
to being more generic so later it would be extended with our DDR
controller support and further with the new DW uMCTL2 DDRC IP-core
compilations.

The series starts with the Error-injection functionality movement to
DebugFS. Indeed the Debug-parts should be in the dedicated DebugFS. SysFS
is not a place for it, moreover seeing later we'll add some more debug
nodes.

Afterwards even though it isn't advertised but even at this stage the DW
uMCTL2 DDRC driver supports a bit more DDR protocols than it actually
specifies in the mem_ctrl_info.mtype_cap field. So first we suggest to
extend the MCI core memory types enumeration with LPDDR (mDDR) and LPDDR2,
which support can be enabled in the DW uMCTL2 DDR controller. Second we
need to make sure all the possible DDR protocol types are correctly
detected during the DW uMCTL DDRC probe procedure.

Then a bit painful patch goes. Alas we have to deviate the driver from the
EDAC standard private data allocation/initialization pattern. Since we are
going to add the DW uMCTL2 IP-core specific parameters detection procedure
and later on implement additional platform resources requests there is no
other choice but to allocate the driver private data at the early stage of
the device probe procedure even before it's possible to allocate the MCI
descriptor. The DW uMCTL2 DDRC platform resources and configuration info
will be then utilized to properly allocate and initialize the
mem_ctrl_info structure instance.

Third patch in the series is very important. It provides the DW uMCTL2
DDRC parameters detection procedure. The DDRC and ECC parameters detected
at this stage will be then utilized to make the driver working with much
wider set of the DW uMCTL2 revisions and configurations. In particular
from now the driver will retrieve the next DDRC info at the probe stage:
ECC type, SDRAM protocol (DDR type), Full and actual DQ-bus width, SDRAM
and HIF burst length, Core/SDRAM frequency ration, number of SDRAM ranks.
The DDRC parameters structure will be extended with some more fields later
in this and the next patchset. The provided private DDRC parameters
infrastructure can be utilized to implement the platform-specific
capabilities so the platform data and its quirks are replaced with it.

The detected at the probe stage DW uMCTL2 DDRC parameters can be now used
to implement the configuration specific functionality. In particular first
we introduce the conditional ADDRMAP* CSRs mapping since some of these
CSRs and their fields are left unused by the controller in some cases.
Secondly actual DIMM ECC errors grain, ECC corrected bit, full data
pattern and syndrome are determined based on the DDRC parameters.

Afterwards goes a series of the patches which introduce an interface to
generically determine the system address based on the SDRAM address and
vice-versa. Thus we'll be able to report actual PFN and offset in case of
the corrected and uncorrected errors. So first we get to convert the
currently available HIF/SDRAM mapping table utilized for the
errors-injection functionality into a more generic Sys<->SDRAM address
translation interface. Secondly we suggest to conform the SDRAM column
address mapping detection algorithm with what is defined in the DW uMCTL2
DDRC hw reference manual. After adding a handy DebugFS node to read the
HIF/SDRAM mapping and the system address regions support we finally
introduce the erroneous page-frame/offset reporting to the MCI core. Since
the full SDRAM address mapping is now always available we suggest to use
it for the attached memory size calculation, which is a more correct
approach rather than the si_meminfo()-based one.

Link: https://lore.kernel.org/linux-edac/20220822191427.27969-1-Sergey.Semin@baikalelectronics.ru
Changelog v2:
- Rebase onto the latest version of the patchset:
[PATCH v2 00/19] EDAC/mc/synopsys: Various fixes and cleanups
- Just resend.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Michail Ivanov <Michail.Ivanov@baikalelectronics.ru>
Cc: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Cc: Punnaiah Choudary Kalluri <punnaiah.choudary.kalluri@xilinx.com>
Cc: Manish Narani <manish.narani@xilinx.com>
Cc: Dinh Nguyen <dinguyen@kernel.org>
Cc: James Morse <james.morse@arm.com>
Cc: Robert Richter <rric@kernel.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-edac@vger.kernel.org
Cc: linux-kernel@vger.kernel.org

Serge Semin (18):
  EDAC/synopsys: Convert sysfs nodes to debugfs ones
  EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2
  EDAC/synopsys: Extend memtypes supported by controller
  EDAC/synopsys: Detach private data from mci instance
  EDAC/synopsys: Add DDRC basic parameters infrastructure
  EDAC/synopsys: Convert plat-data to plat-init function
  EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only
  EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only
  EDAC/synopsys: Set actual DIMM ECC errors grain
  EDAC/synopsys: Get corrected bit position
  EDAC/synopsys: Read full data pattern on errors
  EDAC/synopsys: Read data syndrome on errors
  EDAC/synopsys: Introduce System/SDRAM address translation interface
  EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure
  EDAC/synopsys: Add HIF/SDRAM mapping debugfs node
  EDAC/synopsys: Add erroneous page-frame/offset reporting
  EDAC/synopsys: Add system address regions support
  EDAC/synopsys: Add mapping-based memory size calculation

 drivers/edac/edac_mc.c       |    2 +
 drivers/edac/synopsys_edac.c | 1772 ++++++++++++++++++++++++----------
 include/linux/edac.h         |    6 +
 3 files changed, 1291 insertions(+), 489 deletions(-)

-- 
2.37.2


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 01/18] EDAC/synopsys: Convert sysfs nodes to debugfs ones
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 02/18] EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2 Serge Semin
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

The DW uMCTL2 DDRC EDAC driver supports creating two sysfs nodes:
"inject_data_error" and "inject_data_poison". First of them is responsible
for setting the error-injecting physical address up. The second one is
supposed to be used to enable the hardware capability of the correctable
and uncorrectable error injection. As you can see the semantics of these
nodes is pure debug. They are even created only if the EDAC_DEBUG kernel
config is enabled. Thus there is no point in having these nodes exported
in the sysfs. Let's move them to DebugFS in the device private directory
as this should have been done in the first place.

While at it we suggest to move the address map initialization procedure
invocation to the DebugFS nodes creating function and create an empty
snps_create_debugfs_nodes() method in case if the EDAC_DEBUG config is
disabled. Thus the DW uMCTL2 DDRC EDAC probe procedure will get to be a
bit simpler.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 221 +++++++++++++++++++----------------
 1 file changed, 122 insertions(+), 99 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 7833bcff3e2e..b2a2f938045c 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -9,6 +9,7 @@
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/edac.h>
+#include <linux/fs.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
@@ -640,6 +641,16 @@ static int snps_setup_irq(struct mem_ctl_info *mci, struct platform_device *pdev
 
 #ifdef CONFIG_EDAC_DEBUG
 
+#define SNPS_DEBUGFS_FOPS(__name, __read, __write) \
+	static const struct file_operations __name = {	\
+		.owner = THIS_MODULE,		\
+		.open = simple_open,		\
+		.read = __read,			\
+		.write = __write,		\
+	}
+
+#define SNPS_DBGFS_BUF_LEN 128
+
 /**
  * snps_data_poison_setup - Update poison registers.
  * @priv:		DDR memory controller private instance data.
@@ -700,90 +711,6 @@ static void snps_data_poison_setup(struct snps_edac_priv *priv)
 	writel(regval, priv->baseaddr + ECC_POISON1_OFST);
 }
 
-static ssize_t inject_data_error_show(struct device *dev,
-				      struct device_attribute *mattr,
-				      char *data)
-{
-	struct mem_ctl_info *mci = to_mci(dev);
-	struct snps_edac_priv *priv = mci->pvt_info;
-
-	return sprintf(data, "Poison0 Addr: 0x%08x\n\rPoison1 Addr: 0x%08x\n\r"
-			"Error injection Address: 0x%lx\n\r",
-			readl(priv->baseaddr + ECC_POISON0_OFST),
-			readl(priv->baseaddr + ECC_POISON1_OFST),
-			priv->poison_addr);
-}
-
-static ssize_t inject_data_error_store(struct device *dev,
-				       struct device_attribute *mattr,
-				       const char *data, size_t count)
-{
-	struct mem_ctl_info *mci = to_mci(dev);
-	struct snps_edac_priv *priv = mci->pvt_info;
-
-	if (kstrtoul(data, 0, &priv->poison_addr))
-		return -EINVAL;
-
-	snps_data_poison_setup(priv);
-
-	return count;
-}
-
-static ssize_t inject_data_poison_show(struct device *dev,
-				       struct device_attribute *mattr,
-				       char *data)
-{
-	struct mem_ctl_info *mci = to_mci(dev);
-	struct snps_edac_priv *priv = mci->pvt_info;
-	const char *errstr;
-	u32 regval;
-
-	regval = readl(priv->baseaddr + ECC_CFG1_OFST);
-	errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ?
-		 "Correctable Error" : "UnCorrectable Error";
-
-	return sprintf(data, "Data Poisoning: %s\n\r", errstr);
-}
-
-static ssize_t inject_data_poison_store(struct device *dev,
-					struct device_attribute *mattr,
-					const char *data, size_t count)
-{
-	struct mem_ctl_info *mci = to_mci(dev);
-	struct snps_edac_priv *priv = mci->pvt_info;
-
-	writel(0, priv->baseaddr + DDR_SWCTL);
-	if (strncmp(data, "CE", 2) == 0)
-		writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST);
-	else
-		writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST);
-	writel(1, priv->baseaddr + DDR_SWCTL);
-
-	return count;
-}
-
-static DEVICE_ATTR_RW(inject_data_error);
-static DEVICE_ATTR_RW(inject_data_poison);
-
-static int snps_create_sysfs_attributes(struct mem_ctl_info *mci)
-{
-	int rc;
-
-	rc = device_create_file(&mci->dev, &dev_attr_inject_data_error);
-	if (rc < 0)
-		return rc;
-	rc = device_create_file(&mci->dev, &dev_attr_inject_data_poison);
-	if (rc < 0)
-		return rc;
-	return 0;
-}
-
-static void snps_remove_sysfs_attributes(struct mem_ctl_info *mci)
-{
-	device_remove_file(&mci->dev, &dev_attr_inject_data_error);
-	device_remove_file(&mci->dev, &dev_attr_inject_data_poison);
-}
-
 static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap)
 {
 	u32 addrmap_row_b2_10;
@@ -1004,7 +931,115 @@ static void snps_setup_address_map(struct snps_edac_priv *priv)
 
 	snps_setup_rank_address_map(priv, addrmap);
 }
-#endif /* CONFIG_EDAC_DEBUG */
+
+static ssize_t snps_inject_data_error_read(struct file *filep, char __user *ubuf,
+					   size_t size, loff_t *offp)
+{
+	struct mem_ctl_info *mci = filep->private_data;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	char buf[SNPS_DBGFS_BUF_LEN];
+	int pos;
+
+	pos = scnprintf(buf, sizeof(buf), "Poison0 Addr: 0x%08x\n\r",
+			readl(priv->baseaddr + ECC_POISON0_OFST));
+	pos += scnprintf(buf + pos, sizeof(buf) - pos, "Poison1 Addr: 0x%08x\n\r",
+			 readl(priv->baseaddr + ECC_POISON1_OFST));
+	pos += scnprintf(buf + pos, sizeof(buf) - pos, "Error injection Address: 0x%lx\n\r",
+			 priv->poison_addr);
+
+	return simple_read_from_buffer(ubuf, size, offp, buf, pos);
+}
+
+static ssize_t snps_inject_data_error_write(struct file *filep, const char __user *ubuf,
+					    size_t size, loff_t *offp)
+{
+	struct mem_ctl_info *mci = filep->private_data;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	int rc;
+
+	rc = kstrtoul_from_user(ubuf, size, 0, &priv->poison_addr);
+	if (rc)
+		return rc;
+
+	snps_data_poison_setup(priv);
+
+	return size;
+}
+
+SNPS_DEBUGFS_FOPS(snps_inject_data_error, snps_inject_data_error_read,
+		  snps_inject_data_error_write);
+
+static ssize_t snps_inject_data_poison_read(struct file *filep, char __user *ubuf,
+					    size_t size, loff_t *offp)
+{
+	struct mem_ctl_info *mci = filep->private_data;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	char buf[SNPS_DBGFS_BUF_LEN];
+	const char *errstr;
+	u32 regval;
+	int pos;
+
+	regval = readl(priv->baseaddr + ECC_CFG1_OFST);
+	errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ?
+		 "Correctable Error" : "UnCorrectable Error";
+
+	pos = scnprintf(buf, sizeof(buf), "Data Poisoning: %s\n\r", errstr);
+
+	return simple_read_from_buffer(ubuf, size, offp, buf, pos);
+}
+
+static ssize_t snps_inject_data_poison_write(struct file *filep, const char __user *ubuf,
+					     size_t size, loff_t *offp)
+{
+	struct mem_ctl_info *mci = filep->private_data;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	char buf[SNPS_DBGFS_BUF_LEN];
+	int rc;
+
+	rc = simple_write_to_buffer(buf, sizeof(buf), offp, ubuf, size);
+	if (rc < 0)
+		return rc;
+
+	writel(0, priv->baseaddr + DDR_SWCTL);
+	if (strncmp(buf, "CE", 2) == 0)
+		writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST);
+	else
+		writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST);
+	writel(1, priv->baseaddr + DDR_SWCTL);
+
+	return size;
+}
+
+SNPS_DEBUGFS_FOPS(snps_inject_data_poison, snps_inject_data_poison_read,
+		  snps_inject_data_poison_write);
+
+/**
+ * snps_create_debugfs_nodes -	Create DebugFS nodes.
+ * @mci:	EDAC memory controller instance.
+ *
+ * Create DW uMCTL2 EDAC driver DebugFS nodes in the device private
+ * DebugFS directory.
+ *
+ * Return: none.
+ */
+static void snps_create_debugfs_nodes(struct mem_ctl_info *mci)
+{
+	struct snps_edac_priv *priv = mci->pvt_info;
+
+	snps_setup_address_map(priv);
+
+	edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci,
+				 &snps_inject_data_error);
+
+	edac_debugfs_create_file("inject_data_poison", 0600, mci->debugfs, mci,
+				 &snps_inject_data_poison);
+}
+
+#else /* !CONFIG_EDAC_DEBUG */
+
+static inline void snps_create_debugfs_nodes(struct mem_ctl_info *mci) {}
+
+#endif /* !CONFIG_EDAC_DEBUG */
 
 /**
  * snps_mc_probe - Check controller and bind driver.
@@ -1070,17 +1105,9 @@ static int snps_mc_probe(struct platform_device *pdev)
 		goto free_edac_mc;
 	}
 
-#ifdef CONFIG_EDAC_DEBUG
-	rc = snps_create_sysfs_attributes(mci);
-	if (rc) {
-		edac_printk(KERN_ERR, EDAC_MC, "Failed to create sysfs entries\n");
-		goto free_edac_mc;
-	}
+	snps_create_debugfs_nodes(mci);
 
-	snps_setup_address_map(priv);
-#endif
-
-	return rc;
+	return 0;
 
 free_edac_mc:
 	edac_mc_free(mci);
@@ -1101,10 +1128,6 @@ static int snps_mc_remove(struct platform_device *pdev)
 
 	snps_disable_irq(priv);
 
-#ifdef CONFIG_EDAC_DEBUG
-	snps_remove_sysfs_attributes(mci);
-#endif
-
 	edac_mc_del_mc(&pdev->dev);
 	edac_mc_free(mci);
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 02/18] EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 01/18] EDAC/synopsys: Convert sysfs nodes to debugfs ones Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 03/18] EDAC/synopsys: Extend memtypes supported by controller Serge Semin
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

These are normal memory types [1] which can be met on the real hardware.
DW uMCTL2 DDRC IP-core can be configured to have them supported [2,3].
Let's extend the EDAC memory types enumeration with the corresponding IDs
then.

[1] https://en.wikipedia.org/wiki/LPDDR
[2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
Databook, Version 3.91a, October 2020, p.501
[3] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
Databook, Version 3.91a, October 2020, p.1717

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/edac_mc.c | 2 ++
 include/linux/edac.h   | 6 ++++++
 2 files changed, 8 insertions(+)

diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
index 634c41ea7804..e353e98e01e2 100644
--- a/drivers/edac/edac_mc.c
+++ b/drivers/edac/edac_mc.c
@@ -151,10 +151,12 @@ const char * const edac_mem_types[] = {
 	[MEM_RDR]	= "Registered-SDR",
 	[MEM_DDR]	= "Unbuffered-DDR",
 	[MEM_RDDR]	= "Registered-DDR",
+	[MEM_LPDDR]	= "Low-Power-(m)DDR-RAM",
 	[MEM_RMBS]	= "RMBS",
 	[MEM_DDR2]	= "Unbuffered-DDR2",
 	[MEM_FB_DDR2]	= "FullyBuffered-DDR2",
 	[MEM_RDDR2]	= "Registered-DDR2",
+	[MEM_LPDDR2]	= "Low-Power-DDR2-RAM",
 	[MEM_XDR]	= "XDR",
 	[MEM_DDR3]	= "Unbuffered-DDR3",
 	[MEM_RDDR3]	= "Registered-DDR3",
diff --git a/include/linux/edac.h b/include/linux/edac.h
index fa4bda2a70f6..89167a4459d5 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -157,6 +157,7 @@ static inline char *mc_event_error_type(const unsigned int err_type)
  *			This is a variant of the DDR memories.
  *			A registered memory has a buffer inside it, hiding
  *			part of the memory details to the memory controller.
+ * @MEM_LPDDR:		Low-Power DDR memory (mDDR).
  * @MEM_RMBS:		Rambus DRAM, used on a few Pentium III/IV controllers.
  * @MEM_DDR2:		DDR2 RAM, as described at JEDEC JESD79-2F.
  *			Those memories are labeled as "PC2-" instead of "PC" to
@@ -167,6 +168,7 @@ static inline char *mc_event_error_type(const unsigned int err_type)
  *			a chip select signal.
  * @MEM_RDDR2:		Registered DDR2 RAM
  *			This is a variant of the DDR2 memories.
+ * @MEM_LPDDR2:		Low-Power DDR2 memory.
  * @MEM_XDR:		Rambus XDR
  *			It is an evolution of the original RAMBUS memories,
  *			created to compete with DDR2. Weren't used on any
@@ -199,10 +201,12 @@ enum mem_type {
 	MEM_RDR,
 	MEM_DDR,
 	MEM_RDDR,
+	MEM_LPDDR,
 	MEM_RMBS,
 	MEM_DDR2,
 	MEM_FB_DDR2,
 	MEM_RDDR2,
+	MEM_LPDDR2,
 	MEM_XDR,
 	MEM_DDR3,
 	MEM_RDDR3,
@@ -230,10 +234,12 @@ enum mem_type {
 #define MEM_FLAG_RDR		BIT(MEM_RDR)
 #define MEM_FLAG_DDR		BIT(MEM_DDR)
 #define MEM_FLAG_RDDR		BIT(MEM_RDDR)
+#define MEM_FLAG_LPDDR		BIT(MEM_LPDDR)
 #define MEM_FLAG_RMBS		BIT(MEM_RMBS)
 #define MEM_FLAG_DDR2		BIT(MEM_DDR2)
 #define MEM_FLAG_FB_DDR2	BIT(MEM_FB_DDR2)
 #define MEM_FLAG_RDDR2		BIT(MEM_RDDR2)
+#define MEM_FLAG_LPDDR2		BIT(MEM_LPDDR2)
 #define MEM_FLAG_XDR		BIT(MEM_XDR)
 #define MEM_FLAG_DDR3		BIT(MEM_DDR3)
 #define MEM_FLAG_RDDR3		BIT(MEM_RDDR3)
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 03/18] EDAC/synopsys: Extend memtypes supported by controller
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 01/18] EDAC/synopsys: Convert sysfs nodes to debugfs ones Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 02/18] EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2 Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 04/18] EDAC/synopsys: Detach private data from mci instance Serge Semin
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

In accordance with [1] the DW uMCTL2 DDR controllers can support the next
DDR protocols: LPDDR, (LP)DDR(2|3|4). If the controller is configured to
support several of these memory chip types only one of these modes will be
able to be enabled at runtime [2]. Taking all of that into account in
order to have a generic DW uMCTL2 DDR controller support in the driver we
need to update the snps_get_mtype() procedure so one would be able to
detect all the currently supported memory types in accordance with the
table defined in [2]. Note alas it's not possible do determine which MEMC
DDR configs were enabled at the IP-core synthesize. Thus we have no choice
but to initialize the mci->mtype_cap field with all the types claimed to
be supported by the reference manual.

While at it convert the MEM_TYPE_* macros to have a unified within the
driver name - attach DDR_MSTR prefix indicating the CSR macro is defined
for.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
Databook, Version 3.91a, October 2020, p.501
[2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
Databook, Version 3.91a, October 2020, p.501

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 59 +++++++++++++++++++++---------------
 1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index b2a2f938045c..5adf6598465a 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -97,6 +97,14 @@
 #define DDRCTL_EWDTH_16			2
 #define DDRCTL_EWDTH_32			1
 #define DDRCTL_EWDTH_64			0
+#define DDR_MSTR_MEM_MASK		GENMASK(5, 0)
+#define DDR_MSTR_MEM_DDR2		0
+#define DDR_MSTR_MEM_DDR3		BIT(0)
+#define DDR_MSTR_MEM_LPDDR		BIT(1)
+#define DDR_MSTR_MEM_LPDDR2		BIT(2)
+#define DDR_MSTR_MEM_LPDDR3		BIT(3)
+#define DDR_MSTR_MEM_DDR4		BIT(4)
+#define DDR_MSTR_MEM_LPDDR4		BIT(5)
 
 /* ECC CFG0 register definitions */
 #define ECC_CFG0_MODE_MASK		GENMASK(2, 0)
@@ -141,13 +149,6 @@
 #define ECC_POISON1_BANK_MASK		GENMASK(26, 24)
 #define ECC_POISON1_ROW_MASK		GENMASK(17, 0)
 
-/* DDR Memory type defines */
-#define MEM_TYPE_DDR3			BIT(0)
-#define MEM_TYPE_DDR2			BIT(2)
-#define MEM_TYPE_LPDDR3			BIT(3)
-#define MEM_TYPE_DDR4			BIT(4)
-#define MEM_TYPE_LPDDR4			BIT(5)
-
 /* DDRC ECC CE & UE poison mask */
 #define ECC_CEPOISON_MASK		GENMASK(1, 0)
 #define ECC_UEPOISON_MASK		BIT(0)
@@ -473,7 +474,7 @@ static enum dev_type snps_get_dtype(const void __iomem *base)
 	u32 regval;
 
 	regval = readl(base + DDR_MSTR_OFST);
-	if (!(regval & MEM_TYPE_DDR4))
+	if (!(regval & DDR_MSTR_MEM_DDR4))
 		return DEV_UNKNOWN;
 
 	regval = FIELD_GET(DDR_MSTR_DEV_CFG_MASK, regval);
@@ -534,21 +535,29 @@ static u32 snps_get_memsize(void)
  */
 static enum mem_type snps_get_mtype(const void __iomem *base)
 {
-	enum mem_type mt;
-	u32 memtype;
+	u32 regval;
 
-	memtype = readl(base + DDR_MSTR_OFST);
+	regval = readl(base + DDR_MSTR_OFST);
+	regval = FIELD_GET(DDR_MSTR_MEM_MASK, regval);
 
-	if ((memtype & MEM_TYPE_DDR3) || (memtype & MEM_TYPE_LPDDR3))
-		mt = MEM_DDR3;
-	else if (memtype & MEM_TYPE_DDR2)
-		mt = MEM_RDDR2;
-	else if ((memtype & MEM_TYPE_LPDDR4) || (memtype & MEM_TYPE_DDR4))
-		mt = MEM_DDR4;
-	else
-		mt = MEM_EMPTY;
+	switch (regval) {
+	case DDR_MSTR_MEM_DDR2:
+		return MEM_DDR2;
+	case DDR_MSTR_MEM_DDR3:
+		return MEM_DDR3;
+	case DDR_MSTR_MEM_LPDDR:
+		return MEM_LPDDR;
+	case DDR_MSTR_MEM_LPDDR2:
+		return MEM_LPDDR2;
+	case DDR_MSTR_MEM_LPDDR3:
+		return MEM_LPDDR3;
+	case DDR_MSTR_MEM_DDR4:
+		return MEM_DDR4;
+	case DDR_MSTR_MEM_LPDDR4:
+		return MEM_LPDDR4;
+	}
 
-	return mt;
+	return MEM_RESERVED;
 }
 
 /**
@@ -596,7 +605,9 @@ static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev)
 	platform_set_drvdata(pdev, mci);
 
 	/* Initialize controller capabilities and configuration */
-	mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR2;
+	mci->mtype_cap = MEM_FLAG_LPDDR | MEM_FLAG_DDR2 | MEM_FLAG_LPDDR2 |
+			 MEM_FLAG_DDR3 | MEM_FLAG_LPDDR3 |
+			 MEM_FLAG_DDR4 | MEM_FLAG_LPDDR4;
 	mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED;
 	mci->scrub_cap = SCRUB_FLAG_HW_SRC;
 	mci->scrub_mode = SCRUB_NONE;
@@ -802,7 +813,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 			COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) &
 					COL_MAX_VAL_MASK) + COL_B9_BASE);
 	if (width == DDRCTL_EWDTH_64) {
-		if (memtype & MEM_TYPE_LPDDR3) {
+		if (memtype & DDR_MSTR_MEM_LPDDR3) {
 			priv->col_shift[10] = ((addrmap[4] &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				((addrmap[4] & COL_MAX_VAL_MASK) +
@@ -822,7 +833,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 				 COL_B11_BASE);
 		}
 	} else if (width == DDRCTL_EWDTH_32) {
-		if (memtype & MEM_TYPE_LPDDR3) {
+		if (memtype & DDR_MSTR_MEM_LPDDR3) {
 			priv->col_shift[10] = (((addrmap[3] >> 24) &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
@@ -842,7 +853,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 				 COL_B10_BASE);
 		}
 	} else {
-		if (memtype & MEM_TYPE_LPDDR3) {
+		if (memtype & DDR_MSTR_MEM_LPDDR3) {
 			priv->col_shift[10] = (((addrmap[3] >> 16) &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				(((addrmap[3] >> 16) & COL_MAX_VAL_MASK) +
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 04/18] EDAC/synopsys: Detach private data from mci instance
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (2 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 03/18] EDAC/synopsys: Extend memtypes supported by controller Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 05/18] EDAC/synopsys: Add DDRC basic parameters infrastructure Serge Semin
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

We are about to add a more comprehensive DW uMCTL2 DDRC parameters
detection procedure and some resources requests (clocks and resets). Since
these parameters will be utilized in the various parts of the driver and
in particular used for the Memory Controller data instance
pre-initialization, they need to be first retrieved before the MCI is
allocated and second preserved in the driver private data. Therefore the
best approach would be to add the parameters structure right into the
driver private data and make sure the data is allocated separately from
the mem_ctl_info instance. For that we suggest to add a new static method
snps_data_create(), which aside with the snps_edac_priv structure
allocation will also perform the private data basic initialization like
CSRs region mapping, device data getting, platform data pointer copying
and spin-lock initialization. In addition to that the snps_mc_init() will
be converted to snps_mc_create(), which from now will be used to allocate
and initialize the mem_ctl_info structure instance.

Note in order to have an access to the snps_edac_priv structure instance,
as before the mem_ctl_info.pvt_info pointer will be utilized.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 137 +++++++++++++++++++++++------------
 1 file changed, 90 insertions(+), 47 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 5adf6598465a..f2f83b24d62c 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -240,6 +240,7 @@ struct snps_ecc_status {
 
 /**
  * struct snps_edac_priv - DDR memory controller private data.
+ * @pdev:		Platform device.
  * @baseaddr:		Base address of the DDR controller.
  * @lock:		Concurrent CSRs access lock.
  * @message:		Buffer for framing the event specific info.
@@ -253,6 +254,7 @@ struct snps_ecc_status {
  * @rank_shift:		Bit shifts for rank bit.
  */
 struct snps_edac_priv {
+	struct platform_device *pdev;
 	void __iomem *baseaddr;
 	spinlock_t lock;
 	char message[SNPS_EDAC_MSG_SIZE];
@@ -460,6 +462,34 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+/**
+ * snps_create_data - Create private data.
+ * @pdev:	platform device.
+ *
+ * Return: Private data instance or negative errno.
+ */
+static struct snps_edac_priv *snps_create_data(struct platform_device *pdev)
+{
+	struct snps_edac_priv *priv;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return ERR_PTR(-ENOMEM);
+
+	priv->baseaddr = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->baseaddr))
+		return ERR_CAST(priv->baseaddr);
+
+	priv->p_data = of_device_get_match_data(&pdev->dev);
+	if (!priv->p_data)
+		return ERR_PTR(-ENODEV);
+
+	priv->pdev = pdev;
+	spin_lock_init(&priv->lock);
+
+	return priv;
+}
+
 /**
  * snps_get_dtype - Return the controller memory width.
  * @base:	DDR memory controller base address.
@@ -591,18 +621,36 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 }
 
 /**
- * snps_mc_init - Initialize one driver instance.
- * @mci:	EDAC memory controller instance.
- * @pdev:	platform device.
+ * snps_mc_create - Create and initialize MC instance.
+ * @priv:	DDR memory controller private data.
+ *
+ * Allocate the EDAC memory controller descriptor and initialize it
+ * using the private data info.
  *
- * Perform initialization of the EDAC memory controller instance and
- * related driver-private data associated with the memory controller the
- * instance is bound to.
+ * Return: MC data instance or negative errno.
  */
-static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev)
+static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv)
 {
-	mci->pdev = &pdev->dev;
-	platform_set_drvdata(pdev, mci);
+	struct edac_mc_layer layers[2];
+	struct mem_ctl_info *mci;
+
+	layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
+	layers[0].size = SNPS_EDAC_NR_CSROWS;
+	layers[0].is_virt_csrow = true;
+	layers[1].type = EDAC_MC_LAYER_CHANNEL;
+	layers[1].size = SNPS_EDAC_NR_CHANS;
+	layers[1].is_virt_csrow = false;
+
+	mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, 0);
+	if (!mci) {
+		edac_printk(KERN_ERR, EDAC_MC,
+			    "Failed memory allocation for mc instance\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	mci->pvt_info = priv;
+	mci->pdev = &priv->pdev->dev;
+	platform_set_drvdata(priv->pdev, mci);
 
 	/* Initialize controller capabilities and configuration */
 	mci->mtype_cap = MEM_FLAG_LPDDR | MEM_FLAG_DDR2 | MEM_FLAG_LPDDR2 |
@@ -622,24 +670,43 @@ static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev)
 	mci->ctl_page_to_phys = NULL;
 
 	snps_init_csrows(mci);
+
+	return mci;
 }
 
+/**
+ * snps_mc_free - Free MC instance.
+ * @mci:	EDAC memory controller instance.
+ *
+ * Just revert what was done in the framework of the snps_mc_create().
+ *
+ * Return: MC data instance or negative errno.
+ */
+static void snps_mc_free(struct mem_ctl_info *mci)
+{
+	struct snps_edac_priv *priv = mci->pvt_info;
+
+	platform_set_drvdata(priv->pdev, NULL);
 
+	edac_mc_free(mci);
+}
 
-static int snps_setup_irq(struct mem_ctl_info *mci, struct platform_device *pdev)
+
+
+static int snps_setup_irq(struct mem_ctl_info *mci)
 {
 	struct snps_edac_priv *priv = mci->pvt_info;
 	int ret, irq;
 
-	irq = platform_get_irq(pdev, 0);
+	irq = platform_get_irq(priv->pdev, 0);
 	if (irq < 0) {
 		edac_printk(KERN_ERR, EDAC_MC,
 			    "No IRQ %d in DT\n", irq);
 		return irq;
 	}
 
-	ret = devm_request_irq(&pdev->dev, irq, snps_irq_handler,
-			       0, dev_name(&pdev->dev), mci);
+	ret = devm_request_irq(&priv->pdev->dev, irq, snps_irq_handler,
+			       0, dev_name(&priv->pdev->dev), mci);
 	if (ret < 0) {
 		edac_printk(KERN_ERR, EDAC_MC, "Failed to request IRQ\n");
 		return ret;
@@ -1063,49 +1130,24 @@ static inline void snps_create_debugfs_nodes(struct mem_ctl_info *mci) {}
  */
 static int snps_mc_probe(struct platform_device *pdev)
 {
-	const struct snps_platform_data *p_data;
-	struct edac_mc_layer layers[2];
 	struct snps_edac_priv *priv;
 	struct mem_ctl_info *mci;
-	void __iomem *baseaddr;
 	int rc;
 
-	baseaddr = devm_platform_ioremap_resource(pdev, 0);
-	if (IS_ERR(baseaddr))
-		return PTR_ERR(baseaddr);
-
-	p_data = of_device_get_match_data(&pdev->dev);
-	if (!p_data)
-		return -ENODEV;
+	priv = snps_create_data(pdev);
+	if (IS_ERR(priv))
+		return PTR_ERR(priv);
 
 	if (!snps_get_ecc_state(baseaddr)) {
 		edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n");
 		return -ENXIO;
 	}
 
-	layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
-	layers[0].size = SNPS_EDAC_NR_CSROWS;
-	layers[0].is_virt_csrow = true;
-	layers[1].type = EDAC_MC_LAYER_CHANNEL;
-	layers[1].size = SNPS_EDAC_NR_CHANS;
-	layers[1].is_virt_csrow = false;
-
-	mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers,
-			    sizeof(struct snps_edac_priv));
-	if (!mci) {
-		edac_printk(KERN_ERR, EDAC_MC,
-			    "Failed memory allocation for mc instance\n");
-		return -ENOMEM;
-	}
-
-	priv = mci->pvt_info;
-	priv->baseaddr = baseaddr;
-	priv->p_data = p_data;
-	spin_lock_init(&priv->lock);
+	mci = snps_mc_create(priv);
+	if (IS_ERR(mci))
+		return PTR_ERR(mci);
 
-	snps_mc_init(mci, pdev);
-
-	rc = snps_setup_irq(mci, pdev);
+	rc = snps_setup_irq(mci);
 	if (rc)
 		goto free_edac_mc;
 
@@ -1121,7 +1163,7 @@ static int snps_mc_probe(struct platform_device *pdev)
 	return 0;
 
 free_edac_mc:
-	edac_mc_free(mci);
+	snps_mc_free(mci);
 
 	return rc;
 }
@@ -1140,7 +1182,8 @@ static int snps_mc_remove(struct platform_device *pdev)
 	snps_disable_irq(priv);
 
 	edac_mc_del_mc(&pdev->dev);
-	edac_mc_free(mci);
+
+	snps_mc_free(mci);
 
 	return 0;
 }
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 05/18] EDAC/synopsys: Add DDRC basic parameters infrastructure
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (3 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 04/18] EDAC/synopsys: Detach private data from mci instance Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 06/18] EDAC/synopsys: Convert plat-data to plat-init function Serge Semin
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Currently the driver supports a single DW uMCTL2 DDRC IP-core: 64-bit DQ
bus, ECC SEC/DED with always on Scrub (HW-src scrub). This makes the
driver application being very limited. In addition to that lacking of any
controller capabilities/parameters infrastructure makes it harder to add
optional features support. Let's overcome all of that by adding a simple
DW uMCTL2 DDRC IP-core parameters infrastructure. It's made of the
snps_ddrc_info structure and a new method snps_get_ddrc_info() introduced
to fill the structure fields in. The structure contains the IP-core
parameters needed to create a more comprehensive driver and will be used
in the driver to activate/de-activate various features:
- ECC Mode: SEC/DED or Advanced X4/X8 ECC features. (Currently SEC/DED is
  only supported.)
- SDRAM mode: (LP)DDR[2-4] memory interfaces. (Required for the HIF/SDRAM
  address translation.)
- Memory Device config: Memory chips detected on the platform. (Applicable
  for DDR4 setups only.)
- DQ-bus width: Maximal DQ-bus width utilized by the device. (Required for
  the Application/HIF address translation.)
- DQ-bus mode: Actual DQ-bus width used to access the memory devices.
  (Required for the HIF/SDRAM address translation and ECC grain calc.)
- HIF/SDRAM burst length: Required for the Scrubber bandwidth
  calculation.
- HIF/SDRAM frequency ratio: Required for the SDRAM bandwidth
  calculation.
- SDRAM ranks number: Required for the Application/HIF address
  translation.

The list can be easily updated should any additional features support is
required to be added in future, but at this stage the driver is fixed in a
few places to have the new infrastructure utilized: SDRAM column address
mapper, MCI csrows initialization.

Note getting all of these parameters in a single method is very suitable
from two perspectives. First it localizes the IP-core parameters detection
thus improving the code readability and maintainability. Second it's very
suitable for the platform-specific quirks implementation. Since some of
the IP-core parameters can't be auto-detected at run-time, they will be
able to be fixed right in the parameters getter by means of the platform
quirks.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 268 +++++++++++++++++++++++++++--------
 1 file changed, 211 insertions(+), 57 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index f2f83b24d62c..b4db70224ee9 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -12,6 +12,7 @@
 #include <linux/fs.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/seq_file.h>
 #include <linux/spinlock.h>
 #include <linux/interrupt.h>
 #include <linux/of.h>
@@ -87,16 +88,21 @@
 /* DDR Software control register */
 #define DDR_SWCTL			0x320
 
+/* ECC Poison pattern registers */
+#define ECC_POISONPAT0_OFST		0x37C
+#define ECC_POISONPAT1_OFST		0x380
+#define ECC_POISONPAT2_OFST		0x384
+
 /* DDR Master Register 0 definitions */
 #define DDR_MSTR_DEV_CFG_MASK		GENMASK(31, 30)
 #define DDR_MSTR_DEV_X4			0x0
 #define DDR_MSTR_DEV_X8			0x1
 #define DDR_MSTR_DEV_X16		0x2
 #define DDR_MSTR_DEV_X32		0x3
+#define DDR_MSTR_ACT_RANKS_MASK		GENMASK(27, 24)
+#define DDR_MSTR_FREQ_RATIO11		BIT(22)
+#define DDR_MSTR_BURST_RDWR		GENMASK(19, 16)
 #define DDR_MSTR_BUSWIDTH_MASK		GENMASK(13, 12)
-#define DDRCTL_EWDTH_16			2
-#define DDRCTL_EWDTH_32			1
-#define DDRCTL_EWDTH_64			0
 #define DDR_MSTR_MEM_MASK		GENMASK(5, 0)
 #define DDR_MSTR_MEM_DDR2		0
 #define DDR_MSTR_MEM_DDR3		BIT(0)
@@ -108,7 +114,6 @@
 
 /* ECC CFG0 register definitions */
 #define ECC_CFG0_MODE_MASK		GENMASK(2, 0)
-#define ECC_CFG0_MODE_SECDED		0x4
 
 /* ECC status register definitions */
 #define ECC_STAT_UE_MASK		GENMASK(23, 16)
@@ -206,6 +211,91 @@
 
 #define RANK_B0_BASE			6
 
+/**
+ * enum snps_dq_width - SDRAM DQ bus width (ECC capable).
+ * SNPS_DQ_32:	32-bit memory data width.
+ * SNPS_DQ_64:	64-bit memory data width.
+ */
+enum snps_dq_width {
+	SNPS_DQ_32 = 2,
+	SNPS_DQ_64 = 3,
+};
+
+/**
+ * enum snps_dq_mode - SDRAM DQ bus mode.
+ * @SNPS_DQ_FULL:	Full DQ bus width.
+ * @SNPS_DQ_HALF:	Half DQ bus width.
+ * @SNPS_DQ_QRTR:	Quarter DQ bus width.
+ */
+enum snps_dq_mode {
+	SNPS_DQ_FULL = 0,
+	SNPS_DQ_HALF = 1,
+	SNPS_DQ_QRTR = 2,
+};
+
+/**
+ * enum snps_burst_length - HIF/SDRAM burst transactions length.
+ * @SNPS_DDR_BL2:	Burst length 2xSDRAM-words.
+ * @SNPS_DDR_BL4:	Burst length 4xSDRAM-words.
+ * @SNPS_DDR_BL8:	Burst length 8xSDRAM-words.
+ * @SNPS_DDR_BL16:	Burst length 16xSDRAM-words.
+ */
+enum snps_burst_length {
+	SNPS_DDR_BL2 = 2,
+	SNPS_DDR_BL4 = 4,
+	SNPS_DDR_BL8 = 8,
+	SNPS_DDR_BL16 = 16,
+};
+
+/**
+ * enum snps_freq_ratio - HIF:SDRAM frequency ratio mode.
+ * @SNPS_FREQ_RATIO11:	1:1 frequency mode.
+ * @SNPS_FREQ_RATIO12:	1:2 frequency mode.
+ */
+enum snps_freq_ratio {
+	SNPS_FREQ_RATIO11 = 1,
+	SNPS_FREQ_RATIO12 = 2,
+};
+
+/**
+ * enum snps_ecc_mode - ECC mode.
+ * @SNPS_ECC_DISABLED:	ECC is disabled/unavailable.
+ * @SNPS_ECC_SECDED:	SEC/DED over 1 beat ECC (SideBand/Inline).
+ * @SNPS_ECC_ADVX4X8:	Advanced ECC X4/X8 (SideBand).
+ */
+enum snps_ecc_mode {
+	SNPS_ECC_DISABLED = 0,
+	SNPS_ECC_SECDED = 4,
+	SNPS_ECC_ADVX4X8 = 5,
+};
+
+/**
+ * struct snps_ddrc_info - DDR controller platform parameters.
+ * @caps:		DDR controller capabilities.
+ * @sdram_mode:		Current SDRAM mode selected.
+ * @dev_cfg:		Current memory device config (if applicable).
+ * @dq_width:		Memory data bus width (width of the DQ signals
+ *			connected to SDRAM chips).
+ * @dq_mode:		Proportion of the DQ bus utilized to access SDRAM.
+ * @sdram_burst_len:	SDRAM burst transaction length.
+ * @hif_burst_len:	HIF burst transaction length (Host Interface).
+ * @freq_ratio:		HIF/SDRAM frequency ratio mode.
+ * @ecc_mode:		ECC mode enabled for the DDR controller (SEC/DED, etc).
+ * @ranks:		Number of ranks enabled to access DIMM (1, 2 or 4).
+ */
+struct snps_ddrc_info {
+	unsigned int caps;
+	enum mem_type sdram_mode;
+	enum dev_type dev_cfg;
+	enum snps_dq_width dq_width;
+	enum snps_dq_mode dq_mode;
+	enum snps_burst_length sdram_burst_len;
+	enum snps_burst_length hif_burst_len;
+	enum snps_freq_ratio freq_ratio;
+	enum snps_ecc_mode ecc_mode;
+	unsigned int ranks;
+};
+
 /**
  * struct snps_ecc_error_info - ECC error log information.
  * @row:	Row number.
@@ -240,6 +330,7 @@ struct snps_ecc_status {
 
 /**
  * struct snps_edac_priv - DDR memory controller private data.
+ * @info:		DDR controller config info.
  * @pdev:		Platform device.
  * @baseaddr:		Base address of the DDR controller.
  * @lock:		Concurrent CSRs access lock.
@@ -254,6 +345,7 @@ struct snps_ecc_status {
  * @rank_shift:		Bit shifts for rank bit.
  */
 struct snps_edac_priv {
+	struct snps_ddrc_info info;
 	struct platform_device *pdev;
 	void __iomem *baseaddr;
 	spinlock_t lock;
@@ -492,23 +584,19 @@ static struct snps_edac_priv *snps_create_data(struct platform_device *pdev)
 
 /**
  * snps_get_dtype - Return the controller memory width.
- * @base:	DDR memory controller base address.
+ * @mstr:	Master CSR value.
  *
  * Get the EDAC device type width appropriate for the current controller
  * configuration.
  *
  * Return: a device type width enumeration.
  */
-static enum dev_type snps_get_dtype(const void __iomem *base)
+static inline enum dev_type snps_get_dtype(u32 mstr)
 {
-	u32 regval;
-
-	regval = readl(base + DDR_MSTR_OFST);
-	if (!(regval & DDR_MSTR_MEM_DDR4))
+	if (!(mstr & DDR_MSTR_MEM_DDR4))
 		return DEV_UNKNOWN;
 
-	regval = FIELD_GET(DDR_MSTR_DEV_CFG_MASK, regval);
-	switch (regval) {
+	switch (FIELD_GET(DDR_MSTR_DEV_CFG_MASK, mstr)) {
 	case DDR_MSTR_DEV_X4:
 		return DEV_X4;
 	case DDR_MSTR_DEV_X8:
@@ -522,24 +610,6 @@ static enum dev_type snps_get_dtype(const void __iomem *base)
 	return DEV_UNKNOWN;
 }
 
-/**
- * snps_get_ecc_state - Return the controller ECC enable/disable status.
- * @base:	DDR memory controller base address.
- *
- * Get the ECC enable/disable status for the controller.
- *
- * Return: a ECC status boolean i.e true/false - enabled/disabled.
- */
-static bool snps_get_ecc_state(void __iomem *base)
-{
-	u32 regval;
-
-	regval = readl(base + ECC_CFG0_OFST);
-	regval = FIELD_GET(ECC_CFG0_MODE_MASK, regval);
-
-	return (regval == ECC_CFG0_MODE_SECDED);
-}
-
 /**
  * snps_get_memsize - Read the size of the attached memory device.
  *
@@ -556,21 +626,16 @@ static u32 snps_get_memsize(void)
 
 /**
  * snps_get_mtype - Returns controller memory type.
- * @base:	Synopsys ECC status structure.
+ * @mstr:	Master CSR value.
  *
  * Get the EDAC memory type appropriate for the current controller
  * configuration.
  *
  * Return: a memory type enumeration.
  */
-static enum mem_type snps_get_mtype(const void __iomem *base)
+static inline enum mem_type snps_get_mtype(u32 mstr)
 {
-	u32 regval;
-
-	regval = readl(base + DDR_MSTR_OFST);
-	regval = FIELD_GET(DDR_MSTR_MEM_MASK, regval);
-
-	switch (regval) {
+	switch (FIELD_GET(DDR_MSTR_MEM_MASK, mstr)) {
 	case DDR_MSTR_MEM_DDR2:
 		return MEM_DDR2;
 	case DDR_MSTR_MEM_DDR3:
@@ -590,6 +655,69 @@ static enum mem_type snps_get_mtype(const void __iomem *base)
 	return MEM_RESERVED;
 }
 
+/**
+ * snps_get_ddrc_info - Get the DDR controller config data.
+ * @priv:	DDR memory controller private data.
+ *
+ * Return: negative errno if no ECC detected, otherwise - zero.
+ */
+static int snps_get_ddrc_info(struct snps_edac_priv *priv)
+{
+	int (*init_plat)(struct snps_edac_priv *priv);
+	u32 regval;
+
+	/* Before getting the DDRC parameters make sure ECC is enabled */
+	regval = readl(priv->baseaddr + ECC_CFG0_OFST);
+
+	priv->info.ecc_mode = FIELD_GET(ECC_CFG0_MODE_MASK, regval);
+	if (priv->info.ecc_mode != SNPS_ECC_SECDED) {
+		edac_printk(KERN_INFO, EDAC_MC, "SEC/DED ECC not enabled\n");
+		return -ENXIO;
+	}
+
+	/* Auto-detect the basic HIF/SDRAM bus parameters */
+	regval = readl(priv->baseaddr + DDR_MSTR_OFST);
+
+	priv->info.sdram_mode = snps_get_mtype(regval);
+	priv->info.dev_cfg = snps_get_dtype(regval);
+
+	priv->info.dq_mode = FIELD_GET(DDR_MSTR_BUSWIDTH_MASK, regval);
+
+	/*
+	 * Assume HIF burst length matches the SDRAM burst length since it's
+	 * not auto-detectable
+	 */
+	priv->info.sdram_burst_len = FIELD_GET(DDR_MSTR_BURST_RDWR, regval) << 1;
+	priv->info.hif_burst_len = priv->info.sdram_burst_len;
+
+	/* Retrieve the current HIF/SDRAM frequency ratio: 1:1 vs 1:2 */
+	priv->info.freq_ratio = !(regval & DDR_MSTR_FREQ_RATIO11) + 1;
+
+	/* Activated ranks field: set bit corresponds to populated rank */
+	priv->info.ranks = FIELD_GET(DDR_MSTR_ACT_RANKS_MASK, regval);
+	priv->info.ranks = hweight_long(priv->info.ranks);
+
+	/* Auto-detect the DQ bus width by using the ECC-poison pattern CSR */
+	writel(0, priv->baseaddr + DDR_SWCTL);
+
+	/*
+	 * If poison pattern [32:64] is changeable then DQ is 64-bit wide.
+	 * Note the feature has been available since IP-core v2.51a.
+	 */
+	regval = readl(priv->baseaddr + ECC_POISONPAT1_OFST);
+	writel(~regval, priv->baseaddr + ECC_POISONPAT1_OFST);
+	if (regval != readl(priv->baseaddr + ECC_POISONPAT1_OFST)) {
+		priv->info.dq_width = SNPS_DQ_64;
+		writel(regval, priv->baseaddr + ECC_POISONPAT1_OFST);
+	} else {
+		priv->info.dq_width = SNPS_DQ_32;
+	}
+
+	writel(1, priv->baseaddr + DDR_SWCTL);
+
+	return 0;
+}
+
 /**
  * snps_init_csrows - Initialize the csrow data.
  * @mci:	EDAC memory controller instance.
@@ -612,10 +740,10 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 		for (j = 0; j < csi->nr_channels; j++) {
 			dimm		= csi->channels[j]->dimm;
 			dimm->edac_mode	= EDAC_SECDED;
-			dimm->mtype	= snps_get_mtype(priv->baseaddr);
+			dimm->mtype	= priv->info.sdram_mode;
 			dimm->nr_pages	= (size >> PAGE_SHIFT) / csi->nr_channels;
 			dimm->grain	= SNPS_EDAC_ERR_GRAIN;
-			dimm->dtype	= snps_get_dtype(priv->baseaddr);
+			dimm->dtype	= priv->info.dev_cfg;
 		}
 	}
 }
@@ -729,6 +857,33 @@ static int snps_setup_irq(struct mem_ctl_info *mci)
 
 #define SNPS_DBGFS_BUF_LEN 128
 
+static int snps_ddrc_info_show(struct seq_file *s, void *data)
+{
+	struct mem_ctl_info *mci = s->private;
+	struct snps_edac_priv *priv = mci->pvt_info;
+
+	seq_printf(s, "SDRAM: %s\n", edac_mem_types[priv->info.sdram_mode]);
+
+	seq_printf(s, "DQ bus: %u/%s\n", (BITS_PER_BYTE << priv->info.dq_width),
+		   priv->info.dq_mode == SNPS_DQ_FULL ? "Full" :
+		   priv->info.dq_mode == SNPS_DQ_HALF ? "Half" :
+		   priv->info.dq_mode == SNPS_DQ_QRTR ? "Quarter" :
+		   "Unknown");
+	seq_printf(s, "Burst: SDRAM %u HIF %u\n", priv->info.sdram_burst_len,
+		   priv->info.hif_burst_len);
+
+	seq_printf(s, "Ranks: %u\n", priv->info.ranks);
+
+	seq_printf(s, "ECC: %s\n",
+		   priv->info.ecc_mode == SNPS_ECC_SECDED ? "SEC/DED" :
+		   priv->info.ecc_mode == SNPS_ECC_ADVX4X8 ? "Advanced X4/X8" :
+		   "Unknown");
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info);
+
 /**
  * snps_data_poison_setup - Update poison registers.
  * @priv:		DDR memory controller private instance data.
@@ -850,12 +1005,8 @@ static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap
 
 static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap)
 {
-	u32 width, memtype;
 	int index;
 
-	memtype = readl(priv->baseaddr + DDR_MSTR_OFST);
-	width = FIELD_GET(DDR_MSTR_BUSWIDTH_MASK, memtype);
-
 	priv->col_shift[0] = 0;
 	priv->col_shift[1] = 1;
 	priv->col_shift[2] = (addrmap[2] & COL_MAX_VAL_MASK) + COL_B2_BASE;
@@ -879,8 +1030,8 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 	priv->col_shift[9] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) ==
 			COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) &
 					COL_MAX_VAL_MASK) + COL_B9_BASE);
-	if (width == DDRCTL_EWDTH_64) {
-		if (memtype & DDR_MSTR_MEM_LPDDR3) {
+	if (priv->info.dq_mode == SNPS_DQ_FULL) {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
 			priv->col_shift[10] = ((addrmap[4] &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				((addrmap[4] & COL_MAX_VAL_MASK) +
@@ -899,8 +1050,8 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 				(((addrmap[4] >> 8) & COL_MAX_VAL_MASK) +
 				 COL_B11_BASE);
 		}
-	} else if (width == DDRCTL_EWDTH_32) {
-		if (memtype & DDR_MSTR_MEM_LPDDR3) {
+	} else if (priv->info.dq_mode == SNPS_DQ_HALF) {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
 			priv->col_shift[10] = (((addrmap[3] >> 24) &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
@@ -920,7 +1071,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 				 COL_B10_BASE);
 		}
 	} else {
-		if (memtype & DDR_MSTR_MEM_LPDDR3) {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
 			priv->col_shift[10] = (((addrmap[3] >> 16) &
 				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
 				(((addrmap[3] >> 16) & COL_MAX_VAL_MASK) +
@@ -941,10 +1092,11 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr
 		}
 	}
 
-	if (width) {
-		for (index = 9; index > width; index--) {
-			priv->col_shift[index] = priv->col_shift[index - width];
-			priv->col_shift[index - width] = 0;
+	if (priv->info.dq_mode) {
+		for (index = 9; index > priv->info.dq_mode; index--) {
+			priv->col_shift[index] =
+				priv->col_shift[index - priv->info.dq_mode];
+			priv->col_shift[index - priv->info.dq_mode] = 0;
 		}
 	}
 
@@ -1106,6 +1258,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci)
 
 	snps_setup_address_map(priv);
 
+	edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci,
+				 &snps_ddrc_info_fops);
+
 	edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci,
 				 &snps_inject_data_error);
 
@@ -1138,10 +1293,9 @@ static int snps_mc_probe(struct platform_device *pdev)
 	if (IS_ERR(priv))
 		return PTR_ERR(priv);
 
-	if (!snps_get_ecc_state(baseaddr)) {
-		edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n");
-		return -ENXIO;
-	}
+	rc = snps_get_ddrc_info(priv);
+	if (rc)
+		return rc;
 
 	mci = snps_mc_create(priv);
 	if (IS_ERR(mci))
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 06/18] EDAC/synopsys: Convert plat-data to plat-init function
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (4 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 05/18] EDAC/synopsys: Add DDRC basic parameters infrastructure Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 07/18] EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only Serge Semin
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Since DW uMCTL2 device info and capabilities infrastructure is now
available there is no point in supporting an additional abstraction like
platform quirks. Instead we can convert the already defined ZynqMP quirk
to the ZynqMP-specific capability and add the platform-specific
initialization function support. This function will be called after the
device parameters are detected and thus fixing some of them if required.
This modification will provide a much more flexible interface of the
platform-specific setups. The platform-specific init callback can be used
not only for the capabilities flags modification, but for example for the
resources requests or custom CSRs alterations.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 68 +++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 33 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index b4db70224ee9..2d323afa288b 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -34,8 +34,8 @@
 #define SNPS_EDAC_MOD_STRING		"snps_edac"
 #define SNPS_EDAC_MOD_VER		"1"
 
-/* DDR ECC Quirks */
-#define SNPS_ZYNQMP_IRQ_REGS		BIT(0)
+/* DDR capabilities */
+#define SNPS_CAP_ZYNQMP			BIT(31)
 
 /* Synopsys uMCTL2 DDR controller registers that are relevant to ECC */
 
@@ -336,7 +336,6 @@ struct snps_ecc_status {
  * @lock:		Concurrent CSRs access lock.
  * @message:		Buffer for framing the event specific info.
  * @stat:		ECC status information.
- * @p_data:		Platform data.
  * @poison_addr:	Data poison address.
  * @row_shift:		Bit shifts for row bit.
  * @col_shift:		Bit shifts for column bit.
@@ -351,7 +350,6 @@ struct snps_edac_priv {
 	spinlock_t lock;
 	char message[SNPS_EDAC_MSG_SIZE];
 	struct snps_ecc_status stat;
-	const struct snps_platform_data *p_data;
 #ifdef CONFIG_EDAC_DEBUG
 	ulong poison_addr;
 	u32 row_shift[18];
@@ -362,14 +360,6 @@ struct snps_edac_priv {
 #endif
 };
 
-/**
- * struct snps_platform_data - Synopsys uMCTL2 DDRC platform data.
- * @quirks:	IP-core specific quirks.
- */
-struct snps_platform_data {
-	u32 quirks;
-};
-
 /**
  * snps_get_error_info - Get the current ECC error info.
  * @priv:	DDR memory controller private instance data.
@@ -484,7 +474,7 @@ static void snps_enable_irq(struct snps_edac_priv *priv)
 	unsigned long flags;
 
 	/* Enable UE/CE Interrupts */
-	if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) {
+	if (priv->info.caps & SNPS_CAP_ZYNQMP) {
 		writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK,
 		       priv->baseaddr + DDR_QOS_IRQ_EN_OFST);
 
@@ -505,7 +495,7 @@ static void snps_disable_irq(struct snps_edac_priv *priv)
 	unsigned long flags;
 
 	/* Disable UE/CE Interrupts */
-	if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) {
+	if (priv->info.caps & SNPS_CAP_ZYNQMP) {
 		writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK,
 		       priv->baseaddr + DDR_QOS_IRQ_DB_OFST);
 
@@ -534,7 +524,7 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id)
 
 	priv = mci->pvt_info;
 
-	if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) {
+	if (priv->info.caps & SNPS_CAP_ZYNQMP) {
 		regval = readl(priv->baseaddr + DDR_QOS_IRQ_STAT_OFST);
 		regval &= (DDR_QOSCE_MASK | DDR_QOSUE_MASK);
 		if (!(regval & ECC_CE_UE_INTR_MASK))
@@ -548,7 +538,7 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id)
 	snps_handle_error(mci, &priv->stat);
 
 
-	if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS)
+	if (priv->info.caps & SNPS_CAP_ZYNQMP)
 		writel(regval, priv->baseaddr + DDR_QOS_IRQ_STAT_OFST);
 
 	return IRQ_HANDLED;
@@ -572,16 +562,26 @@ static struct snps_edac_priv *snps_create_data(struct platform_device *pdev)
 	if (IS_ERR(priv->baseaddr))
 		return ERR_CAST(priv->baseaddr);
 
-	priv->p_data = of_device_get_match_data(&pdev->dev);
-	if (!priv->p_data)
-		return ERR_PTR(-ENODEV);
-
 	priv->pdev = pdev;
 	spin_lock_init(&priv->lock);
 
 	return priv;
 }
 
+/*
+ * zynqmp_init_plat - ZynqMP-specific platform initialization.
+ * @priv:	DDR memory controller private data.
+ *
+ * Return: always zero.
+ */
+static int zynqmp_init_plat(struct snps_edac_priv *priv)
+{
+	priv->info.caps |= SNPS_CAP_ZYNQMP;
+	priv->info.dq_width = SNPS_DQ_64;
+
+	return 0;
+}
+
 /**
  * snps_get_dtype - Return the controller memory width.
  * @mstr:	Master CSR value.
@@ -715,7 +715,10 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv)
 
 	writel(1, priv->baseaddr + DDR_SWCTL);
 
-	return 0;
+	/* Apply platform setups after all the configs auto-detection */
+	init_plat = device_get_match_data(&priv->pdev->dev);
+
+	return init_plat ? init_plat(priv) : 0;
 }
 
 /**
@@ -819,8 +822,6 @@ static void snps_mc_free(struct mem_ctl_info *mci)
 	edac_mc_free(mci);
 }
 
-
-
 static int snps_setup_irq(struct mem_ctl_info *mci)
 {
 	struct snps_edac_priv *priv = mci->pvt_info;
@@ -879,6 +880,15 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data)
 		   priv->info.ecc_mode == SNPS_ECC_ADVX4X8 ? "Advanced X4/X8" :
 		   "Unknown");
 
+	seq_puts(s, "Caps:");
+	if (priv->info.caps) {
+		if (priv->info.caps & SNPS_CAP_ZYNQMP)
+			seq_puts(s, " +ZynqMP");
+	} else {
+		seq_puts(s, " -");
+	}
+	seq_putc(s, '\n');
+
 	return 0;
 }
 
@@ -1342,17 +1352,9 @@ static int snps_mc_remove(struct platform_device *pdev)
 	return 0;
 }
 
-static const struct snps_platform_data zynqmp_edac_def = {
-	.quirks = SNPS_ZYNQMP_IRQ_REGS,
-};
-
-static const struct snps_platform_data snps_edac_def = {
-	.quirks = 0,
-};
-
 static const struct of_device_id snps_edac_match[] = {
-	{ .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = &zynqmp_edac_def },
-	{ .compatible = "snps,ddrc-3.80a", .data = &snps_edac_def },
+	{ .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = zynqmp_init_plat },
+	{ .compatible = "snps,ddrc-3.80a" },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, snps_edac_match);
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 07/18] EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (5 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 06/18] EDAC/synopsys: Convert plat-data to plat-init function Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 08/18] EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only Serge Semin
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

These CSRs contain the SDRAM Bank Groups and row[16]/row[17] bits mapping,
which are applicable for the DDR4 and LPDDR4 memory only. For the rest of
the memories the ADDRMAP[7-8] are unused by the controller and may be read
as garbage (for instance, return an outcome of the previous read
operation). The retrieved values might be perceived by the HIF/SDRAM
mapping detection procedure as normal bit positions, which is wrong. So
let's parse these registers only if they are applicable for the detected
DDR protocol.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 2d323afa288b..a55ae7ff931c 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -1005,12 +1005,15 @@ static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap
 	priv->row_shift[15] = (((addrmap[6] >> 24) & ROW_MAX_VAL_MASK) ==
 				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 24) &
 				ROW_MAX_VAL_MASK) + ROW_B15_BASE);
-	priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] &
-				ROW_MAX_VAL_MASK) + ROW_B16_BASE);
-	priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) &
-				ROW_MAX_VAL_MASK) + ROW_B17_BASE);
+
+	if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) {
+		priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) ==
+					ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] &
+					ROW_MAX_VAL_MASK) + ROW_B16_BASE);
+		priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) ==
+					ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) &
+					ROW_MAX_VAL_MASK) + ROW_B17_BASE);
+	}
 }
 
 static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap)
@@ -1126,6 +1129,10 @@ static void snps_setup_bank_address_map(struct snps_edac_priv *priv, u32 *addrma
 
 static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap)
 {
+	/* Bank group signals are available on the DDR4 memory only */
+	if (priv->info.sdram_mode != MEM_DDR4)
+		return;
+
 	priv->bankgrp_shift[0] = (addrmap[8] &
 				BANKGRP_MAX_VAL_MASK) + BANKGRP_B0_BASE;
 	priv->bankgrp_shift[1] = (((addrmap[8] >> 8) & BANKGRP_MAX_VAL_MASK) ==
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 08/18] EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (6 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 07/18] EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 09/18] EDAC/synopsys: Set actual DIMM ECC errors grain Serge Semin
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

The ADDRMAP[0] CSR contains the SDRAM Rank bits mapping. Obviously it's
applicable for the multi-ranked memory only. If either the attached memory
isn't multi-ranked or the controller simply doesn't support the multi-rank
memory, parsing the ADDRMAP[0] CSR will be not just pointless, but in the
later case erroneous since the CSR may contain garbage data. So make sure
the ADDRMAP[0] register is only parsed if the multi-ranked memory setup
has been detected.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index a55ae7ff931c..87acb683ab5c 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -1143,9 +1143,12 @@ static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap)
 
 static void snps_setup_rank_address_map(struct snps_edac_priv *priv, u32 *addrmap)
 {
-	priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) ==
-				RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] &
-				RANK_MAX_VAL_MASK) + RANK_B0_BASE);
+	/* Ranks mapping is unavailable for the single-ranked memory */
+	if (priv->info.ranks > 1) {
+		priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) ==
+					RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] &
+					RANK_MAX_VAL_MASK) + RANK_B0_BASE);
+	}
 }
 
 /**
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 09/18] EDAC/synopsys: Set actual DIMM ECC errors grain
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (7 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 08/18] EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:49 ` [PATCH RESEND v2 10/18] EDAC/synopsys: Get corrected bit position Serge Semin
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

It was wrong to set the DIMM errors grain parameter to just 1 byte because
DW uMCTL2 DDRC calculates ECC for each SDRAM word and passes it as an
additional byte of data to the memory chips. SDRAM word is the actual
DQ-bus width determined by the DQ-width set during the IP-core synthesize
and the DQ-bus mode (part of the DQ-bus actually used to get data from the
memory chips) selected during the DDR controller initial setup procedure.
Thus let's set the MCI DIMMs grain based on these parameters determined
during the DW uMCTL2 DDRC config getting procedure.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 87acb683ab5c..c0e5476d5c85 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -26,9 +26,6 @@
 /* Number of channels per memory controller */
 #define SNPS_EDAC_NR_CHANS		1
 
-/* Granularity of reported error in bytes */
-#define SNPS_EDAC_ERR_GRAIN		1
-
 #define SNPS_EDAC_MSG_SIZE		256
 
 #define SNPS_EDAC_MOD_STRING		"snps_edac"
@@ -733,9 +730,12 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 	struct snps_edac_priv *priv = mci->pvt_info;
 	struct csrow_info *csi;
 	struct dimm_info *dimm;
-	u32 size, row;
+	u32 size, row, width;
 	int j;
 
+	/* Actual SDRAM-word width for which ECC is calculated */
+	width = 1U << (priv->info.dq_width - priv->info.dq_mode);
+
 	for (row = 0; row < mci->nr_csrows; row++) {
 		csi = mci->csrows[row];
 		size = snps_get_memsize();
@@ -745,7 +745,7 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 			dimm->edac_mode	= EDAC_SECDED;
 			dimm->mtype	= priv->info.sdram_mode;
 			dimm->nr_pages	= (size >> PAGE_SHIFT) / csi->nr_channels;
-			dimm->grain	= SNPS_EDAC_ERR_GRAIN;
+			dimm->grain	= width;
 			dimm->dtype	= priv->info.dev_cfg;
 		}
 	}
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 10/18] EDAC/synopsys: Get corrected bit position
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (8 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 09/18] EDAC/synopsys: Set actual DIMM ECC errors grain Serge Semin
@ 2022-09-10 19:49 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 11/18] EDAC/synopsys: Read full data pattern on errors Serge Semin
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:49 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Since the DQ-bus width is now available in the driver we can use it to
calculate the bit-position corrected by the ECC engine. It can be done
based on the offsets provided in the table [1]. Using info from that table
let's introduce a new inline method snps_get_bitpos() which would provide
the actual CE bit-position based on the value read from the
ECCSTAT.corrected_bit_num field and the DQ-bus width. The method will be
called if a corrected error is detected.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.426-427

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index c0e5476d5c85..1cfbc5a2a564 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -10,6 +10,7 @@
 #include <linux/bits.h>
 #include <linux/edac.h>
 #include <linux/fs.h>
+#include <linux/log2.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/seq_file.h>
@@ -357,6 +358,27 @@ struct snps_edac_priv {
 #endif
 };
 
+/**
+ * snps_get_bitpos - Get DQ-bus corrected bit position.
+ * @bitnum:	Bit number retrieved from the ECCSTAT.corrected_bit_num field.
+ * @dq_width:	Controller DQ-bus width.
+ *
+ * Return: actual corrected DQ-bus bit position starting from 0.
+ */
+static inline u32 snps_get_bitpos(u32 bitnum, enum snps_dq_width dq_width)
+{
+	/* ecc[0] bit */
+	if (bitnum == 0)
+		return BITS_PER_BYTE << dq_width;
+
+	/* ecc[1:x] bit */
+	if (is_power_of_2(bitnum))
+		return (BITS_PER_BYTE << dq_width) + ilog2(bitnum) + 1;
+
+	/* data[0:y] bit */
+	return bitnum - ilog2(bitnum) - 2;
+}
+
 /**
  * snps_get_error_info - Get the current ECC error info.
  * @priv:	DDR memory controller private instance data.
@@ -385,6 +407,8 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	if (!p->ce_cnt)
 		goto ue_err;
 
+	p->ceinfo.bitpos = snps_get_bitpos(p->ceinfo.bitpos, priv->info.dq_width);
+
 	regval = readl(base + ECC_CEADDR0_OFST);
 	p->ceinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval);
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 11/18] EDAC/synopsys: Read full data pattern on errors
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (9 preceding siblings ...)
  2022-09-10 19:49 ` [PATCH RESEND v2 10/18] EDAC/synopsys: Get corrected bit position Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 12/18] EDAC/synopsys: Read data syndrome " Serge Semin
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

DW uMCTL2 DDRC calculates ECC for the DQ-bus word. If non-Full bus width
mode is activated the leftover DQ-bits will be padded with zeros, but the
ECC syndrome is calculated for the whole width anyway [1]. For some reason
the DW uMCTL2 DDRC driver currently doesn't read the whole SDRAM word in
case of the ECC errors even though the 64-bit DQ-bus has been supported
for a long time. Let's fix that by extending the data field of the ECC
error info structure and reading the upper 32-bits part of the data
pattern if an ECC error happens and the DDR controller has been configured
with the 64-bits DQ bus. As before the data will be printed as a part of
the custom error message passed to the edac_mc_handle_error() method.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.424-425

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 1cfbc5a2a564..00417f368893 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -309,7 +309,7 @@ struct snps_ecc_error_info {
 	u32 bank;
 	u32 bankgrp;
 	u32 bitpos;
-	u32 data;
+	u64 data;
 };
 
 /**
@@ -418,6 +418,8 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	p->ceinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
 
 	p->ceinfo.data = readl(base + ECC_CSYND0_OFST);
+	if (priv->info.dq_width == SNPS_DQ_64)
+		p->ceinfo.data |= (u64)readl(base + ECC_CSYND1_OFST) << 32;
 
 	edac_dbg(2, "ECCCSYN0: 0x%08X ECCCSYN1: 0x%08X ECCCSYN2: 0x%08X\n",
 		 readl(base + ECC_CSYND0_OFST), readl(base + ECC_CSYND1_OFST),
@@ -436,6 +438,8 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	p->ueinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
 
 	p->ueinfo.data = readl(base + ECC_UESYND0_OFST);
+	if (priv->info.dq_width == SNPS_DQ_64)
+		p->ueinfo.data |= (u64)readl(base + ECC_UESYND1_OFST) << 32;
 
 out:
 	spin_lock_irqsave(&priv->lock, flags);
@@ -466,7 +470,7 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 		pinf = &p->ceinfo;
 
 		snprintf(priv->message, SNPS_EDAC_MSG_SIZE,
-			 "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x",
+			 "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08llx",
 			 pinf->row, pinf->col, pinf->bank, pinf->bankgrp,
 			 pinf->bitpos, pinf->data);
 
@@ -479,8 +483,9 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 		pinf = &p->ueinfo;
 
 		snprintf(priv->message, SNPS_EDAC_MSG_SIZE,
-			 "Row %d Col %d Bank %d Bank Group %d",
-			 pinf->row, pinf->col, pinf->bank, pinf->bankgrp);
+			 "Row %d Col %d Bank %d Bank Group %d Data 0x%08llx",
+			 pinf->row, pinf->col, pinf->bank, pinf->bankgrp,
+			 pinf->data);
 
 		edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
 				     p->ue_cnt, 0, 0, 0, 0, 0, -1,
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 12/18] EDAC/synopsys: Read data syndrome on errors
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (10 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 11/18] EDAC/synopsys: Read full data pattern on errors Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 13/18] EDAC/synopsys: Introduce System/SDRAM address translation interface Serge Semin
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

In case of the corrected and uncorrected errors DW uMCTL2 DDR controller
preserves the ECC syndrome of the erroneous data pattern in the ECCCSYN2
and ECCUSYN2 CSRs [1]. Seeing the MCI core permits supplying the ECC
syndrome to the error reporting method, let's read it from the CSRs and
pass the value out to the core so to provide more details of the happened
error.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.826, p.837

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 00417f368893..805ab7879000 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -302,6 +302,7 @@ struct snps_ddrc_info {
  * @bankgrp:	Bank group number.
  * @bitpos:	Bit position.
  * @data:	Data causing the error.
+ * @syndrome:	Erroneous data syndrome.
  */
 struct snps_ecc_error_info {
 	u32 row;
@@ -310,6 +311,7 @@ struct snps_ecc_error_info {
 	u32 bankgrp;
 	u32 bitpos;
 	u64 data;
+	u32 syndrome;
 };
 
 /**
@@ -421,9 +423,7 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	if (priv->info.dq_width == SNPS_DQ_64)
 		p->ceinfo.data |= (u64)readl(base + ECC_CSYND1_OFST) << 32;
 
-	edac_dbg(2, "ECCCSYN0: 0x%08X ECCCSYN1: 0x%08X ECCCSYN2: 0x%08X\n",
-		 readl(base + ECC_CSYND0_OFST), readl(base + ECC_CSYND1_OFST),
-		 readl(base + ECC_CSYND2_OFST));
+	p->ceinfo.syndrome = readl(base + ECC_CSYND2_OFST);
 
 ue_err:
 	if (!p->ue_cnt)
@@ -441,6 +441,8 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	if (priv->info.dq_width == SNPS_DQ_64)
 		p->ueinfo.data |= (u64)readl(base + ECC_UESYND1_OFST) << 32;
 
+	p->ueinfo.syndrome = readl(base + ECC_UESYND2_OFST);
+
 out:
 	spin_lock_irqsave(&priv->lock, flags);
 
@@ -475,7 +477,7 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 			 pinf->bitpos, pinf->data);
 
 		edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
-				     p->ce_cnt, 0, 0, 0, 0, 0, -1,
+				     p->ce_cnt, 0, 0, pinf->syndrome, 0, 0, -1,
 				     priv->message, "");
 	}
 
@@ -488,7 +490,7 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 			 pinf->data);
 
 		edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
-				     p->ue_cnt, 0, 0, 0, 0, 0, -1,
+				     p->ue_cnt, 0, 0, pinf->syndrome, 0, 0, -1,
 				     priv->message, "");
 	}
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 13/18] EDAC/synopsys: Introduce System/SDRAM address translation interface
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (11 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 12/18] EDAC/synopsys: Read data syndrome " Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 14/18] EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure Serge Semin
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Currently the address translation is performed only in the framework of
the ECC poison procedures. The available infrastructure is utilized for
the device and driver debugging. Meanwhile it would be very useful to know
not only the SDRAM address of the runtime ECC errors, but the originating
system address exposed on the AXI/AHB ports of the DW uMCTL2 DDR
controller. In order to be able to do so the currently available
infrastructure needs to be properly updated.

First of all it needs to be detached from under the EDAC_DEBUG config,
since it will be always utilized by the driver. Secondly for the sake of
the code simplification we need to split the translation procedure up into
three stages:
1. System<->Application address translation (just type cast for now).
2. Application<->HIF address translation (DQ-bus width based address
shift).
3. HIF<->SDRAM address translation (ADDRMAPx-based mapping).

Currently the 1->3 translation is only supported in the same way as it was
before this modification (the backward address translation will be added
later). Semantically it's the same except the next four aspects. Firstly
ff-value is used as a marker of the unmapped HIF/SDRAM bits instead of
zero-value which in some cases is a valid mapping bit id. Secondly
DQ-width is used to perform the Application<->HIF address translation
instead of the fixed 64-bit DQ-bus width assumption. Thirdly the HIF/SDRAM
address translation procedure searches through the whole dimensions width.
It doesn't stop at the first unmapped bit since in general some of the
row/column/bank/etc bits (especially the column bits, like b10) can be
left unmapped. Finally the number of supported ranks is extended to four -
maximum possible value.

Note while at it the code is simplified a bit. First of all the mapping
table is encapsulated into a dedicated structure (snps_hif_sdram_map).
Secondly the FIELD_GET() helper is utilized to get ADDRMAP CSR fields.
Thirdly a more specific max-value macros have been defined.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 750 +++++++++++++++++++++--------------
 1 file changed, 453 insertions(+), 297 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 805ab7879000..9baa62f6d22b 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -81,7 +81,7 @@
 #define ECC_POISON1_OFST		0xBC
 
 /* DDR Address map0 Registers */
-#define ECC_ADDRMAP0_OFST		0x200
+#define DDR_ADDRMAP0_OFST		0x200
 
 /* DDR Software control register */
 #define DDR_SWCTL			0x320
@@ -156,17 +156,28 @@
 #define ECC_CEPOISON_MASK		GENMASK(1, 0)
 #define ECC_UEPOISON_MASK		BIT(0)
 
-/* DDRC Device config shifts/masks */
-#define DDR_MAX_ROW_SHIFT		18
-#define DDR_MAX_COL_SHIFT		14
-#define DDR_MAX_BANK_SHIFT		3
-#define DDR_MAX_BANKGRP_SHIFT		2
+/* DDRC address mapping parameters */
+#define DDR_ADDRMAP_NREGS		12
 
-#define ROW_MAX_VAL_MASK		0xF
-#define COL_MAX_VAL_MASK		0xF
-#define BANK_MAX_VAL_MASK		0x1F
-#define BANKGRP_MAX_VAL_MASK		0x1F
-#define RANK_MAX_VAL_MASK		0x1F
+#define DDR_MAX_ROW_WIDTH		18
+#define DDR_MAX_COL_WIDTH		14
+#define DDR_MAX_BANK_WIDTH		3
+#define DDR_MAX_BANKGRP_WIDTH		2
+#define DDR_MAX_RANK_WIDTH		2
+
+#define DDR_ADDRMAP_B0_M15		GENMASK(3, 0)
+#define DDR_ADDRMAP_B8_M15		GENMASK(11, 8)
+#define DDR_ADDRMAP_B16_M15		GENMASK(19, 16)
+#define DDR_ADDRMAP_B24_M15		GENMASK(27, 24)
+
+#define DDR_ADDRMAP_B0_M31		GENMASK(4, 0)
+#define DDR_ADDRMAP_B8_M31		GENMASK(12, 8)
+#define DDR_ADDRMAP_B16_M31		GENMASK(20, 16)
+#define DDR_ADDRMAP_B24_M31		GENMASK(28, 24)
+
+#define DDR_ADDRMAP_UNUSED		((u8)-1)
+#define DDR_ADDRMAP_MAX_15		DDR_ADDRMAP_B0_M15
+#define DDR_ADDRMAP_MAX_31		DDR_ADDRMAP_B0_M31
 
 #define ROW_B0_BASE			6
 #define ROW_B1_BASE			7
@@ -208,6 +219,7 @@
 #define BANKGRP_B1_BASE			3
 
 #define RANK_B0_BASE			6
+#define RANK_B1_BASE			7
 
 /**
  * enum snps_dq_width - SDRAM DQ bus width (ECC capable).
@@ -294,6 +306,41 @@ struct snps_ddrc_info {
 	unsigned int ranks;
 };
 
+/**
+ * struct snps_hif_sdram_map - HIF/SDRAM mapping table.
+ * @row:	HIF bit offsets used as row address bits.
+ * @col:	HIF bit offsets used as column address bits.
+ * @bank:	HIF bit offsets used as bank address bits.
+ * @bankgrp:	HIF bit offsets used as bank group address bits.
+ * @rank:	HIF bit offsets used as rank address bits.
+ *
+ * For example, row[0] = 6 means row bit #0 is encoded by the HIF
+ * address bit #6 and vice-versa.
+ */
+struct snps_hif_sdram_map {
+	u8 row[DDR_MAX_ROW_WIDTH];
+	u8 col[DDR_MAX_COL_WIDTH];
+	u8 bank[DDR_MAX_BANK_WIDTH];
+	u8 bankgrp[DDR_MAX_BANKGRP_WIDTH];
+	u8 rank[DDR_MAX_RANK_WIDTH];
+};
+
+/**
+ * struct snps_sdram_addr - SDRAM address.
+ * @row:	Row number.
+ * @col:	Column number.
+ * @bank:	Bank number.
+ * @bankgrp:	Bank group number.
+ * @rank:	Rank number.
+ */
+struct snps_sdram_addr {
+	u16 row;
+	u16 col;
+	u8 bank;
+	u8 bankgrp;
+	u8 rank;
+};
+
 /**
  * struct snps_ecc_error_info - ECC error log information.
  * @row:	Row number.
@@ -331,20 +378,17 @@ struct snps_ecc_status {
 /**
  * struct snps_edac_priv - DDR memory controller private data.
  * @info:		DDR controller config info.
+ * @hif_sdram_map:	HIF/SDRAM mapping table.
  * @pdev:		Platform device.
  * @baseaddr:		Base address of the DDR controller.
  * @lock:		Concurrent CSRs access lock.
  * @message:		Buffer for framing the event specific info.
  * @stat:		ECC status information.
  * @poison_addr:	Data poison address.
- * @row_shift:		Bit shifts for row bit.
- * @col_shift:		Bit shifts for column bit.
- * @bank_shift:		Bit shifts for bank bit.
- * @bankgrp_shift:	Bit shifts for bank group bit.
- * @rank_shift:		Bit shifts for rank bit.
  */
 struct snps_edac_priv {
 	struct snps_ddrc_info info;
+	struct snps_hif_sdram_map hif_sdram_map;
 	struct platform_device *pdev;
 	void __iomem *baseaddr;
 	spinlock_t lock;
@@ -352,14 +396,97 @@ struct snps_edac_priv {
 	struct snps_ecc_status stat;
 #ifdef CONFIG_EDAC_DEBUG
 	ulong poison_addr;
-	u32 row_shift[18];
-	u32 col_shift[14];
-	u32 bank_shift[3];
-	u32 bankgrp_shift[2];
-	u32 rank_shift[1];
 #endif
 };
 
+/**
+ * snps_map_app_to_hif - Map Application address to HIF address.
+ * @priv:	DDR memory controller private instance data.
+ * @app:	Application address (source).
+ * @hif:	HIF address (destination).
+ *
+ * HIF address is used to perform the DQ bus width aligned burst transactions.
+ * So in order to perform the Application-to-HIF address translation we just
+ * need to discard the SDRAM-word bits of the Application address.
+ */
+static void snps_map_app_to_hif(struct snps_edac_priv *priv,
+				u64 app, u64 *hif)
+{
+	*hif = app >> priv->info.dq_width;
+}
+
+/**
+ * snps_map_hif_to_sdram - Map HIF address to SDRAM address.
+ * @priv:	DDR memory controller private instance data.
+ * @hif:	HIF address (source).
+ * @sdram:	SDRAM address (destination).
+ *
+ * HIF-SDRAM address mapping is configured with the ADDRMAPx registers, Based
+ * on the CSRs value the HIF address bits are mapped to the corresponding bits
+ * in the SDRAM rank/bank/column/row. If an SDRAM address bit is unused (there
+ * is no any HIF address bit corresponding to it) it will be set to zero. Using
+ * this fact we can freely set the output SDRAM address with zeros and walk
+ * over the set HIF address bits only. Similarly the unmapped HIF address bits
+ * are just ignored.
+ */
+static void snps_map_hif_to_sdram(struct snps_edac_priv *priv,
+				  u64 hif, struct snps_sdram_addr *sdram)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	sdram->row = 0;
+	for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) {
+		if (map->row[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->row[i]))
+			sdram->row |= BIT(i);
+	}
+
+	sdram->col = 0;
+	for (i = 0; i < DDR_MAX_COL_WIDTH; i++) {
+		if (map->col[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->col[i]))
+			sdram->col |= BIT(i);
+	}
+
+	sdram->bank = 0;
+	for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) {
+		if (map->bank[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->bank[i]))
+			sdram->bank |= BIT(i);
+	}
+
+	sdram->bankgrp = 0;
+	for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) {
+		if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->bankgrp[i]))
+			sdram->bankgrp |= BIT(i);
+	}
+
+	sdram->rank = 0;
+	for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) {
+		if (map->rank[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->rank[i]))
+			sdram->rank |= BIT(i);
+	}
+}
+
+/**
+ * snps_map_sys_to_sdram - Map System address to SDRAM address.
+ * @priv:	DDR memory controller private instance data.
+ * @sys:	System address (source).
+ * @sdram:	SDRAM address (destination).
+ *
+ * Perform a full mapping of the system address (detected on the controller
+ * ports) to the SDRAM address tuple row/column/bank/etc.
+ */
+static void snps_map_sys_to_sdram(struct snps_edac_priv *priv,
+				  dma_addr_t sys, struct snps_sdram_addr *sdram)
+{
+	u64 app, hif;
+
+	app = sys;
+
+	snps_map_app_to_hif(priv, app, &hif);
+
+	snps_map_hif_to_sdram(priv, hif, sdram);
+}
+
 /**
  * snps_get_bitpos - Get DQ-bus corrected bit position.
  * @bitnum:	Bit number retrieved from the ECCSTAT.corrected_bit_num field.
@@ -749,6 +876,301 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv)
 	return init_plat ? init_plat(priv) : 0;
 }
 
+/**
+ * snps_get_hif_row_map - Get HIF/SDRAM-row address map.
+ * @priv:	DDR memory controller private instance data.
+ * @addrmap:	Array with ADDRMAP registers value.
+ *
+ * SDRAM-row address is defined by the fields in the ADDRMAP[5-7,9-11]
+ * registers. Those fields value indicate the HIF address bits used to encode
+ * the DDR row address.
+ */
+static void snps_get_hif_row_map(struct snps_edac_priv *priv, u32 *addrmap)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	u8 map_row_b2_10;
+	int i;
+
+	for (i = 0; i < DDR_MAX_ROW_WIDTH; i++)
+		map->row[i] = DDR_ADDRMAP_UNUSED;
+
+	map->row[0] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[5]) + ROW_B0_BASE;
+	map->row[1] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[5]) + ROW_B1_BASE;
+
+	map_row_b2_10 = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[5]);
+	if (map_row_b2_10 != DDR_ADDRMAP_MAX_15) {
+		for (i = 2; i < 11; i++)
+			map->row[i] = map_row_b2_10 + i + ROW_B0_BASE;
+	} else {
+		map->row[2] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[9]) + ROW_B2_BASE;
+		map->row[3] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[9]) + ROW_B3_BASE;
+		map->row[4] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[9]) + ROW_B4_BASE;
+		map->row[5] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[9]) + ROW_B5_BASE;
+		map->row[6] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[10]) + ROW_B6_BASE;
+		map->row[7] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[10]) + ROW_B7_BASE;
+		map->row[8] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[10]) + ROW_B8_BASE;
+		map->row[9] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[10]) + ROW_B9_BASE;
+		map->row[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[11]) + ROW_B10_BASE;
+	}
+
+	map->row[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[5]);
+	map->row[11] = map->row[11] == DDR_ADDRMAP_MAX_15 ?
+		       DDR_ADDRMAP_UNUSED : map->row[11] + ROW_B11_BASE;
+
+	map->row[12] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[6]);
+	map->row[12] = map->row[12] == DDR_ADDRMAP_MAX_15 ?
+		       DDR_ADDRMAP_UNUSED : map->row[12] + ROW_B12_BASE;
+
+	map->row[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[6]);
+	map->row[13] = map->row[13] == DDR_ADDRMAP_MAX_15 ?
+		       DDR_ADDRMAP_UNUSED : map->row[13] + ROW_B13_BASE;
+
+	map->row[14] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[6]);
+	map->row[14] = map->row[14] == DDR_ADDRMAP_MAX_15 ?
+		       DDR_ADDRMAP_UNUSED : map->row[14] + ROW_B14_BASE;
+
+	map->row[15] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[6]);
+	map->row[15] = map->row[15] == DDR_ADDRMAP_MAX_15 ?
+		       DDR_ADDRMAP_UNUSED : map->row[15] + ROW_B15_BASE;
+
+	if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) {
+		map->row[16] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[7]);
+		map->row[16] = map->row[16] == DDR_ADDRMAP_MAX_15 ?
+			       DDR_ADDRMAP_UNUSED : map->row[16] + ROW_B16_BASE;
+
+		map->row[17] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[7]);
+		map->row[17] = map->row[17] == DDR_ADDRMAP_MAX_15 ?
+			       DDR_ADDRMAP_UNUSED : map->row[17] + ROW_B17_BASE;
+	}
+}
+
+/**
+ * snps_get_hif_col_map - Get HIF/SDRAM-column address map.
+ * @priv:	DDR memory controller private instance data.
+ * @addrmap:	Array with ADDRMAP registers value.
+ *
+ * SDRAM-column address is defined by the fields in the ADDRMAP[2-4]
+ * registers. Those fields value indicate the HIF address bits used to encode
+ * the DDR row address.
+ */
+static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	for (i = 0; i < DDR_MAX_COL_WIDTH; i++)
+		map->col[i] = DDR_ADDRMAP_UNUSED;
+
+	map->col[0] = 0;
+	map->col[1] = 1;
+	map->col[2] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[2]) + COL_B2_BASE;
+	map->col[3] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[2]) + COL_B3_BASE;
+
+	map->col[4] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[2]);
+	map->col[4] = map->col[4] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[4] + COL_B4_BASE;
+
+	map->col[5] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[2]);
+	map->col[5] = map->col[5] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[5] + COL_B5_BASE;
+
+	map->col[6] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[3]);
+	map->col[6] = map->col[6] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[6] + COL_B6_BASE;
+
+	map->col[7] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[3]);
+	map->col[7] = map->col[7] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[7] + COL_B7_BASE;
+
+	map->col[8] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]);
+	map->col[8] = map->col[8] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[8] + COL_B8_BASE;
+
+	map->col[9] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
+	map->col[9] = map->col[9] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[9] + COL_B9_BASE;
+
+	if (priv->info.dq_mode) {
+		for (i = 9; i > priv->info.dq_mode; i--) {
+			map->col[i] = map->col[i - priv->info.dq_mode];
+			map->col[i - priv->info.dq_mode] = DDR_ADDRMAP_UNUSED;
+		}
+	}
+
+	/*
+	 * Per JEDEC DDR2/3/4/mDDR specification, column address bit 10 is
+	 * reserved for indicating auto-precharge, and hence no source
+	 * address bit can be mapped to col[10].
+	 * Per JEDEC specification, column address bit 12 is reserved
+	 * for the Burst-chop status, so no source address bit mapping
+	 * for col[12] either.
+	 */
+	if (priv->info.dq_mode == SNPS_DQ_FULL) {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
+			map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
+			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE;
+
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE;
+		} else {
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE;
+
+			map->col[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]);
+			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B11_BASE;
+		}
+	} else if (priv->info.dq_mode == SNPS_DQ_HALF) {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
+			map->col[10] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
+			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B9_BASE;
+
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE;
+		} else {
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE;
+
+			map->col[13] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
+			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B10_BASE;
+		}
+	} else {
+		if (priv->info.sdram_mode == MEM_LPDDR3) {
+			map->col[10] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]);
+			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B8_BASE;
+
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE;
+		} else {
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]);
+			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B8_BASE;
+
+			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
+			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
+				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B9_BASE;
+		}
+	}
+}
+
+/**
+ * snps_get_hif_bank_map - Get HIF/SDRAM-bank address map.
+ * @priv:	DDR memory controller private instance data.
+ * @addrmap:	Array with ADDRMAP registers value.
+ *
+ * SDRAM-bank address is defined by the fields in the ADDRMAP[1]
+ * register. Those fields value indicate the HIF address bits used to encode
+ * the DDR bank address.
+ */
+static void snps_get_hif_bank_map(struct snps_edac_priv *priv, u32 *addrmap)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	for (i = 0; i < DDR_MAX_BANK_WIDTH; i++)
+		map->bank[i] = DDR_ADDRMAP_UNUSED;
+
+	map->bank[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[1]) + BANK_B0_BASE;
+	map->bank[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[1]) + BANK_B1_BASE;
+
+	map->bank[2] = FIELD_GET(DDR_ADDRMAP_B16_M31, addrmap[1]);
+	map->bank[2] = map->bank[2] == DDR_ADDRMAP_MAX_31 ?
+		       DDR_ADDRMAP_UNUSED : map->bank[2] + BANK_B2_BASE;
+}
+
+/**
+ * snps_get_hif_bankgrp_map - Get HIF/SDRAM-bank group address map.
+ * @priv:	DDR memory controller private instance data.
+ * @addrmap:	Array with ADDRMAP registers value.
+ *
+ * SDRAM-bank group address is defined by the fields in the ADDRMAP[8]
+ * register. Those fields value indicate the HIF address bits used to encode
+ * the DDR bank group address.
+ */
+static void snps_get_hif_bankgrp_map(struct snps_edac_priv *priv, u32 *addrmap)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++)
+		map->bankgrp[i] = DDR_ADDRMAP_UNUSED;
+
+	/* Bank group signals are available on the DDR4 memory only */
+	if (priv->info.sdram_mode != MEM_DDR4)
+		return;
+
+	map->bankgrp[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[8]) + BANKGRP_B0_BASE;
+
+	map->bankgrp[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[8]);
+	map->bankgrp[1] = map->bankgrp[1] == DDR_ADDRMAP_MAX_31 ?
+			  DDR_ADDRMAP_UNUSED : map->bankgrp[1] + BANKGRP_B1_BASE;
+}
+
+/**
+ * snps_get_hif_rank_map - Get HIF/SDRAM-rank address map.
+ * @priv:	DDR memory controller private instance data.
+ * @addrmap:	Array with ADDRMAP registers value.
+ *
+ * SDRAM-rank address is defined by the fields in the ADDRMAP[0]
+ * register. Those fields value indicate the HIF address bits used to encode
+ * the DDR rank address.
+ */
+static void snps_get_hif_rank_map(struct snps_edac_priv *priv, u32 *addrmap)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	for (i = 0; i < DDR_MAX_RANK_WIDTH; i++)
+		map->rank[i] = DDR_ADDRMAP_UNUSED;
+
+	if (priv->info.ranks > 1) {
+		map->rank[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[0]);
+		map->rank[0] = map->rank[0] == DDR_ADDRMAP_MAX_31 ?
+			       DDR_ADDRMAP_UNUSED : map->rank[0] + RANK_B0_BASE;
+	}
+
+	if (priv->info.ranks > 2) {
+		map->rank[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[0]);
+		map->rank[1] = map->rank[1] == DDR_ADDRMAP_MAX_31 ?
+			       DDR_ADDRMAP_UNUSED : map->rank[1] + RANK_B1_BASE;
+	}
+}
+
+/**
+ * snps_get_addr_map - Get HIF/SDRAM/etc address map from CSRs.
+ * @priv:	DDR memory controller private instance data.
+ *
+ * Parse the controller registers content creating the addresses mapping tables.
+ * They will be used for the erroneous and poison addresses encode/decode.
+ */
+static void snps_get_addr_map(struct snps_edac_priv *priv)
+{
+	u32 regval[DDR_ADDRMAP_NREGS];
+	int i;
+
+	for (i = 0; i < DDR_ADDRMAP_NREGS; i++)
+		regval[i] = readl(priv->baseaddr + DDR_ADDRMAP0_OFST + i * 4);
+
+	snps_get_hif_row_map(priv, regval);
+
+	snps_get_hif_col_map(priv, regval);
+
+	snps_get_hif_bank_map(priv, regval);
+
+	snps_get_hif_bankgrp_map(priv, regval);
+
+	snps_get_hif_rank_map(priv, regval);
+}
+
 /**
  * snps_init_csrows - Initialize the csrow data.
  * @mci:	EDAC memory controller instance.
@@ -934,285 +1356,21 @@ DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info);
  */
 static void snps_data_poison_setup(struct snps_edac_priv *priv)
 {
-	int col = 0, row = 0, bank = 0, bankgrp = 0, rank = 0, regval;
-	int index;
-	ulong hif_addr = 0;
-
-	hif_addr = priv->poison_addr >> 3;
-
-	for (index = 0; index < DDR_MAX_ROW_SHIFT; index++) {
-		if (priv->row_shift[index])
-			row |= (((hif_addr >> priv->row_shift[index]) &
-						BIT(0)) << index);
-		else
-			break;
-	}
-
-	for (index = 0; index < DDR_MAX_COL_SHIFT; index++) {
-		if (priv->col_shift[index] || index < 3)
-			col |= (((hif_addr >> priv->col_shift[index]) &
-						BIT(0)) << index);
-		else
-			break;
-	}
-
-	for (index = 0; index < DDR_MAX_BANK_SHIFT; index++) {
-		if (priv->bank_shift[index])
-			bank |= (((hif_addr >> priv->bank_shift[index]) &
-						BIT(0)) << index);
-		else
-			break;
-	}
-
-	for (index = 0; index < DDR_MAX_BANKGRP_SHIFT; index++) {
-		if (priv->bankgrp_shift[index])
-			bankgrp |= (((hif_addr >> priv->bankgrp_shift[index])
-						& BIT(0)) << index);
-		else
-			break;
-	}
+	struct snps_sdram_addr sdram;
+	u32 regval;
 
-	if (priv->rank_shift[0])
-		rank = (hif_addr >> priv->rank_shift[0]) & BIT(0);
+	snps_map_sys_to_sdram(priv, priv->poison_addr, &sdram);
 
-	regval = FIELD_PREP(ECC_POISON0_RANK_MASK, rank) |
-		 FIELD_PREP(ECC_POISON0_COL_MASK, col);
+	regval = FIELD_PREP(ECC_POISON0_RANK_MASK, sdram.rank) |
+		 FIELD_PREP(ECC_POISON0_COL_MASK, sdram.col);
 	writel(regval, priv->baseaddr + ECC_POISON0_OFST);
 
-	regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, bankgrp) |
-		 FIELD_PREP(ECC_POISON1_BANK_MASK, bank) |
-		 FIELD_PREP(ECC_POISON1_ROW_MASK, row);
+	regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, sdram.bankgrp) |
+		 FIELD_PREP(ECC_POISON1_BANK_MASK, sdram.bank) |
+		 FIELD_PREP(ECC_POISON1_ROW_MASK, sdram.row);
 	writel(regval, priv->baseaddr + ECC_POISON1_OFST);
 }
 
-static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap)
-{
-	u32 addrmap_row_b2_10;
-	int index;
-
-	priv->row_shift[0] = (addrmap[5] & ROW_MAX_VAL_MASK) + ROW_B0_BASE;
-	priv->row_shift[1] = ((addrmap[5] >> 8) &
-			ROW_MAX_VAL_MASK) + ROW_B1_BASE;
-
-	addrmap_row_b2_10 = (addrmap[5] >> 16) & ROW_MAX_VAL_MASK;
-	if (addrmap_row_b2_10 != ROW_MAX_VAL_MASK) {
-		for (index = 2; index < 11; index++)
-			priv->row_shift[index] = addrmap_row_b2_10 +
-				index + ROW_B0_BASE;
-
-	} else {
-		priv->row_shift[2] = (addrmap[9] &
-				ROW_MAX_VAL_MASK) + ROW_B2_BASE;
-		priv->row_shift[3] = ((addrmap[9] >> 8) &
-				ROW_MAX_VAL_MASK) + ROW_B3_BASE;
-		priv->row_shift[4] = ((addrmap[9] >> 16) &
-				ROW_MAX_VAL_MASK) + ROW_B4_BASE;
-		priv->row_shift[5] = ((addrmap[9] >> 24) &
-				ROW_MAX_VAL_MASK) + ROW_B5_BASE;
-		priv->row_shift[6] = (addrmap[10] &
-				ROW_MAX_VAL_MASK) + ROW_B6_BASE;
-		priv->row_shift[7] = ((addrmap[10] >> 8) &
-				ROW_MAX_VAL_MASK) + ROW_B7_BASE;
-		priv->row_shift[8] = ((addrmap[10] >> 16) &
-				ROW_MAX_VAL_MASK) + ROW_B8_BASE;
-		priv->row_shift[9] = ((addrmap[10] >> 24) &
-				ROW_MAX_VAL_MASK) + ROW_B9_BASE;
-		priv->row_shift[10] = (addrmap[11] &
-				ROW_MAX_VAL_MASK) + ROW_B10_BASE;
-	}
-
-	priv->row_shift[11] = (((addrmap[5] >> 24) & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[5] >> 24) &
-				ROW_MAX_VAL_MASK) + ROW_B11_BASE);
-	priv->row_shift[12] = ((addrmap[6] & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : ((addrmap[6] &
-				ROW_MAX_VAL_MASK) + ROW_B12_BASE);
-	priv->row_shift[13] = (((addrmap[6] >> 8) & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 8) &
-				ROW_MAX_VAL_MASK) + ROW_B13_BASE);
-	priv->row_shift[14] = (((addrmap[6] >> 16) & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 16) &
-				ROW_MAX_VAL_MASK) + ROW_B14_BASE);
-	priv->row_shift[15] = (((addrmap[6] >> 24) & ROW_MAX_VAL_MASK) ==
-				ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 24) &
-				ROW_MAX_VAL_MASK) + ROW_B15_BASE);
-
-	if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) {
-		priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) ==
-					ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] &
-					ROW_MAX_VAL_MASK) + ROW_B16_BASE);
-		priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) ==
-					ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) &
-					ROW_MAX_VAL_MASK) + ROW_B17_BASE);
-	}
-}
-
-static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap)
-{
-	int index;
-
-	priv->col_shift[0] = 0;
-	priv->col_shift[1] = 1;
-	priv->col_shift[2] = (addrmap[2] & COL_MAX_VAL_MASK) + COL_B2_BASE;
-	priv->col_shift[3] = ((addrmap[2] >> 8) &
-			COL_MAX_VAL_MASK) + COL_B3_BASE;
-	priv->col_shift[4] = (((addrmap[2] >> 16) & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : (((addrmap[2] >> 16) &
-					COL_MAX_VAL_MASK) + COL_B4_BASE);
-	priv->col_shift[5] = (((addrmap[2] >> 24) & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : (((addrmap[2] >> 24) &
-					COL_MAX_VAL_MASK) + COL_B5_BASE);
-	priv->col_shift[6] = ((addrmap[3] & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : ((addrmap[3] &
-					COL_MAX_VAL_MASK) + COL_B6_BASE);
-	priv->col_shift[7] = (((addrmap[3] >> 8) & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 8) &
-					COL_MAX_VAL_MASK) + COL_B7_BASE);
-	priv->col_shift[8] = (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 16) &
-					COL_MAX_VAL_MASK) + COL_B8_BASE);
-	priv->col_shift[9] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) ==
-			COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) &
-					COL_MAX_VAL_MASK) + COL_B9_BASE);
-	if (priv->info.dq_mode == SNPS_DQ_FULL) {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			priv->col_shift[10] = ((addrmap[4] &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				((addrmap[4] & COL_MAX_VAL_MASK) +
-				 COL_B10_BASE);
-			priv->col_shift[11] = (((addrmap[4] >> 8) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[4] >> 8) & COL_MAX_VAL_MASK) +
-				 COL_B11_BASE);
-		} else {
-			priv->col_shift[11] = ((addrmap[4] &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				((addrmap[4] & COL_MAX_VAL_MASK) +
-				 COL_B10_BASE);
-			priv->col_shift[13] = (((addrmap[4] >> 8) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[4] >> 8) & COL_MAX_VAL_MASK) +
-				 COL_B11_BASE);
-		}
-	} else if (priv->info.dq_mode == SNPS_DQ_HALF) {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			priv->col_shift[10] = (((addrmap[3] >> 24) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
-				 COL_B9_BASE);
-			priv->col_shift[11] = ((addrmap[4] &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				((addrmap[4] & COL_MAX_VAL_MASK) +
-				 COL_B10_BASE);
-		} else {
-			priv->col_shift[11] = (((addrmap[3] >> 24) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
-				 COL_B9_BASE);
-			priv->col_shift[13] = ((addrmap[4] &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				((addrmap[4] & COL_MAX_VAL_MASK) +
-				 COL_B10_BASE);
-		}
-	} else {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			priv->col_shift[10] = (((addrmap[3] >> 16) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 16) & COL_MAX_VAL_MASK) +
-				 COL_B8_BASE);
-			priv->col_shift[11] = (((addrmap[3] >> 24) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
-				 COL_B9_BASE);
-		} else {
-			priv->col_shift[11] = (((addrmap[3] >> 16) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 16) & COL_MAX_VAL_MASK) +
-				 COL_B8_BASE);
-			priv->col_shift[13] = (((addrmap[3] >> 24) &
-				COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 :
-				(((addrmap[3] >> 24) & COL_MAX_VAL_MASK) +
-				 COL_B9_BASE);
-		}
-	}
-
-	if (priv->info.dq_mode) {
-		for (index = 9; index > priv->info.dq_mode; index--) {
-			priv->col_shift[index] =
-				priv->col_shift[index - priv->info.dq_mode];
-			priv->col_shift[index - priv->info.dq_mode] = 0;
-		}
-	}
-
-}
-
-static void snps_setup_bank_address_map(struct snps_edac_priv *priv, u32 *addrmap)
-{
-	priv->bank_shift[0] = (addrmap[1] & BANK_MAX_VAL_MASK) + BANK_B0_BASE;
-	priv->bank_shift[1] = ((addrmap[1] >> 8) &
-				BANK_MAX_VAL_MASK) + BANK_B1_BASE;
-	priv->bank_shift[2] = (((addrmap[1] >> 16) &
-				BANK_MAX_VAL_MASK) == BANK_MAX_VAL_MASK) ? 0 :
-				(((addrmap[1] >> 16) & BANK_MAX_VAL_MASK) +
-				 BANK_B2_BASE);
-
-}
-
-static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap)
-{
-	/* Bank group signals are available on the DDR4 memory only */
-	if (priv->info.sdram_mode != MEM_DDR4)
-		return;
-
-	priv->bankgrp_shift[0] = (addrmap[8] &
-				BANKGRP_MAX_VAL_MASK) + BANKGRP_B0_BASE;
-	priv->bankgrp_shift[1] = (((addrmap[8] >> 8) & BANKGRP_MAX_VAL_MASK) ==
-				BANKGRP_MAX_VAL_MASK) ? 0 : (((addrmap[8] >> 8)
-				& BANKGRP_MAX_VAL_MASK) + BANKGRP_B1_BASE);
-
-}
-
-static void snps_setup_rank_address_map(struct snps_edac_priv *priv, u32 *addrmap)
-{
-	/* Ranks mapping is unavailable for the single-ranked memory */
-	if (priv->info.ranks > 1) {
-		priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) ==
-					RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] &
-					RANK_MAX_VAL_MASK) + RANK_B0_BASE);
-	}
-}
-
-/**
- * snps_setup_address_map -	Set Address Map by querying ADDRMAP registers.
- * @priv:		DDR memory controller private instance data.
- *
- * Set Address Map by querying ADDRMAP registers.
- *
- * Return: none.
- */
-static void snps_setup_address_map(struct snps_edac_priv *priv)
-{
-	u32 addrmap[12];
-	int index;
-
-	for (index = 0; index < 12; index++) {
-		u32 addrmap_offset;
-
-		addrmap_offset = ECC_ADDRMAP0_OFST + (index * 4);
-		addrmap[index] = readl(priv->baseaddr + addrmap_offset);
-	}
-
-	snps_setup_row_address_map(priv, addrmap);
-
-	snps_setup_column_address_map(priv, addrmap);
-
-	snps_setup_bank_address_map(priv, addrmap);
-
-	snps_setup_bg_address_map(priv, addrmap);
-
-	snps_setup_rank_address_map(priv, addrmap);
-}
-
 static ssize_t snps_inject_data_error_read(struct file *filep, char __user *ubuf,
 					   size_t size, loff_t *offp)
 {
@@ -1305,10 +1463,6 @@ SNPS_DEBUGFS_FOPS(snps_inject_data_poison, snps_inject_data_poison_read,
  */
 static void snps_create_debugfs_nodes(struct mem_ctl_info *mci)
 {
-	struct snps_edac_priv *priv = mci->pvt_info;
-
-	snps_setup_address_map(priv);
-
 	edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci,
 				 &snps_ddrc_info_fops);
 
@@ -1348,6 +1502,8 @@ static int snps_mc_probe(struct platform_device *pdev)
 	if (rc)
 		return rc;
 
+	snps_get_addr_map(priv);
+
 	mci = snps_mc_create(priv);
 	if (IS_ERR(mci))
 		return PTR_ERR(mci);
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 14/18] EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (12 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 13/18] EDAC/synopsys: Introduce System/SDRAM address translation interface Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 15/18] EDAC/synopsys: Add HIF/SDRAM mapping debugfs node Serge Semin
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

What is currently implemented in the driver by means of the multiple
if-else-if-else statements in fact is described in the hardware reference
manual [1]. It says:
1. All of the column bits shift up 1 bit when only half of the data bus is
in use. In this case, for instance, you need to look at
ADDRMAP3.addrmap_col_b6 instead to determine the value of column address
bit 7.
2. All of the column bits shift up 2 bits when only a quarter of the data
bus is in use. In this case, for instance, you need to look at
ADDRMAP2.addrmap_col_b5 instead to determine the value of column address
bit 7.
3. In addition to the above, the column bit 10 is reserved for the
auto-precharge command in DDR2/3/4/mDDR. So the column bits must be
further shifted up 1 bit when one of these DDR protocols is enabled.

So taking into account all of the notes above and what the column bit 12
is always reserved, the SDRAM column bits mapping procedure can be
significantly simplified. Initially we get to read the mapping as if for
the LPDDR2/3/4 memory with Full DQ-bus utilized. Then we shift the column
bits up in accordance with the detected DQ-bus width mode. That's it.
Simple, canonical and scalable.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 83 ++++++++++++------------------------
 1 file changed, 27 insertions(+), 56 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 9baa62f6d22b..9c4f7d60b820 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -990,8 +990,22 @@ static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap)
 	map->col[9] = map->col[9] == DDR_ADDRMAP_MAX_15 ?
 		      DDR_ADDRMAP_UNUSED : map->col[9] + COL_B9_BASE;
 
+	map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
+	map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE;
+
+	map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]);
+	map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
+		      DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE;
+
+	/*
+	 * In case of the non-Full DQ bus mode the lowest columns are
+	 * unmapped and used by the controller to read the full DQ word
+	 * in multiple cycles (col[0] for the Half bus mode, col[0:1] for
+	 * the Quarter bus mode).
+	 */
 	if (priv->info.dq_mode) {
-		for (i = 9; i > priv->info.dq_mode; i--) {
+		for (i = 11 + priv->info.dq_mode; i >= priv->info.dq_mode; i--) {
 			map->col[i] = map->col[i - priv->info.dq_mode];
 			map->col[i - priv->info.dq_mode] = DDR_ADDRMAP_UNUSED;
 		}
@@ -1001,65 +1015,22 @@ static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap)
 	 * Per JEDEC DDR2/3/4/mDDR specification, column address bit 10 is
 	 * reserved for indicating auto-precharge, and hence no source
 	 * address bit can be mapped to col[10].
+	 */
+	if (priv->info.sdram_mode == MEM_LPDDR || priv->info.sdram_mode == MEM_DDR2 ||
+	    priv->info.sdram_mode == MEM_DDR3 || priv->info.sdram_mode == MEM_DDR4) {
+		for (i = 12 + priv->info.dq_mode; i > 10; i--) {
+			map->col[i] = map->col[i - 1];
+			map->col[i - 1] = DDR_ADDRMAP_UNUSED;
+		}
+	}
+
+	/*
 	 * Per JEDEC specification, column address bit 12 is reserved
 	 * for the Burst-chop status, so no source address bit mapping
 	 * for col[12] either.
 	 */
-	if (priv->info.dq_mode == SNPS_DQ_FULL) {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
-			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE;
-
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE;
-		} else {
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE;
-
-			map->col[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]);
-			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B11_BASE;
-		}
-	} else if (priv->info.dq_mode == SNPS_DQ_HALF) {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			map->col[10] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
-			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B9_BASE;
-
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE;
-		} else {
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE;
-
-			map->col[13] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]);
-			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B10_BASE;
-		}
-	} else {
-		if (priv->info.sdram_mode == MEM_LPDDR3) {
-			map->col[10] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]);
-			map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[10] + COL_B8_BASE;
-
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE;
-		} else {
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]);
-			map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[11] + COL_B8_BASE;
-
-			map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]);
-			map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ?
-				       DDR_ADDRMAP_UNUSED : map->col[13] + COL_B9_BASE;
-		}
-	}
+	map->col[13] = map->col[12];
+	map->col[12] = DDR_ADDRMAP_UNUSED;
 }
 
 /**
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 15/18] EDAC/synopsys: Add HIF/SDRAM mapping debugfs node
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (13 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 14/18] EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 16/18] EDAC/synopsys: Add erroneous page-frame/offset reporting Serge Semin
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Since we are about to add the address mapping to decode the erroneous
SDRAM address it will be useful to have a way to get an info regarding the
most complicated part of the address translation - HIF/SDRAM mapping table
just for in case something gets wrong in the implemented translation
procedures. Let's add the DebugFS node which can be used to print the
HIF/SDRAM mapping table in the hexdump-like manner: first line will
contain the HIF address bit position units, first column will contain the
HIF address bit position tens, the line and column intersection will have
the SDRAM dimension (row/column/bank/etc) and bit position which is used
to encode the corresponding HIF address bit.

Note DW uMCTL2 DDRC IP-core doesn't have a parameter to set the HIF
address width. Instead we've used the maximum value (60 bits) of the
UMCTL2_A_ADDRW synthesize parameter [1]. That parameter defines the
controller ports address bus width and in case if the DQ bus width equals
to eight bits defines the HIF address width too. So its upper constraints
is fully applicable in this case.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.515

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 82 ++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 9c4f7d60b820..26fd797d3f00 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -159,6 +159,7 @@
 /* DDRC address mapping parameters */
 #define DDR_ADDRMAP_NREGS		12
 
+#define DDR_MAX_HIF_WIDTH		60
 #define DDR_MAX_ROW_WIDTH		18
 #define DDR_MAX_COL_WIDTH		14
 #define DDR_MAX_BANK_WIDTH		3
@@ -1318,6 +1319,84 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data)
 
 DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info);
 
+static u8 snps_find_sdram_dim(struct snps_edac_priv *priv, u8 hif, char *dim)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	int i;
+
+	for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) {
+		if (map->row[i] == hif) {
+			*dim = 'r';
+			return i;
+		}
+	}
+
+	for (i = 0; i < DDR_MAX_COL_WIDTH; i++) {
+		if (map->col[i] == hif) {
+			*dim = 'c';
+			return i;
+		}
+	}
+
+	for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) {
+		if (map->bank[i] == hif) {
+			*dim = 'b';
+			return i;
+		}
+	}
+
+	for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) {
+		if (map->bankgrp[i] == hif) {
+			*dim = 'g';
+			return i;
+		}
+	}
+
+	for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) {
+		if (map->rank[i] == hif) {
+			*dim = 'a';
+			return i;
+		}
+	}
+
+	return DDR_ADDRMAP_UNUSED;
+}
+
+static int snps_hif_sdram_map_show(struct seq_file *s, void *data)
+{
+	struct mem_ctl_info *mci = s->private;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	char dim, buf[SNPS_DBGFS_BUF_LEN];
+	const int line_len = 10;
+	u8 bit;
+	int i;
+
+	seq_printf(s, "%3s", "");
+	for (i = 0; i < line_len; i++)
+		seq_printf(s, " %02d ", i);
+
+	for (i = 0; i < DDR_MAX_HIF_WIDTH; i++) {
+		if (i % line_len == 0)
+			seq_printf(s, "\n%02d ", i);
+
+		bit = snps_find_sdram_dim(priv, i, &dim);
+
+		if (bit != DDR_ADDRMAP_UNUSED)
+			scnprintf(buf, SNPS_DBGFS_BUF_LEN, "%c%hhu", dim, bit);
+		else
+			scnprintf(buf, SNPS_DBGFS_BUF_LEN, "--");
+
+		seq_printf(s, "%3s ", buf);
+	}
+	seq_putc(s, '\n');
+
+	seq_puts(s, "r - row, c - column, b - bank, g - bank group, a - rank\n");
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(snps_hif_sdram_map);
+
 /**
  * snps_data_poison_setup - Update poison registers.
  * @priv:		DDR memory controller private instance data.
@@ -1437,6 +1516,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci)
 	edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci,
 				 &snps_ddrc_info_fops);
 
+	edac_debugfs_create_file("hif_sdram_map", 0400, mci->debugfs, mci,
+				 &snps_hif_sdram_map_fops);
+
 	edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci,
 				 &snps_inject_data_error);
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 16/18] EDAC/synopsys: Add erroneous page-frame/offset reporting
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (14 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 15/18] EDAC/synopsys: Add HIF/SDRAM mapping debugfs node Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 17/18] EDAC/synopsys: Add system address regions support Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 18/18] EDAC/synopsys: Add mapping-based memory size calculation Serge Semin
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Since System/SDRAM address translation interface is now available we can
use it to determine the system address causing the ECC faults. In order to
do so first we need to add the System->Application->HIF->SDRAM address
translation procedures based on the DW uMCTL2 DDRC DQ-bus config and
HIF/SDRAM mapping table retrieved on the device probe stage. Secondly for
the sake of simplification we need to convert the snps_ecc_error_info
structure to contain the snps_sdram_addr structure instance, since the
erroneous SDRAM address will now participate in the address translation
chain. Finally we need to perform the SDRAM->System address translation
before passing the later to the edac_mc_handle_error() method.

Note the ECC address rank needs to be retrieved now too in order to
determine a correct system address. But the rank won't be passed to the
MCI core for now since the MCI device is registered with a single ranked
layer 0.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 142 +++++++++++++++++++++++++++++------
 1 file changed, 118 insertions(+), 24 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 26fd797d3f00..946b86149ff8 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -12,6 +12,7 @@
 #include <linux/fs.h>
 #include <linux/log2.h>
 #include <linux/module.h>
+#include <linux/pfn.h>
 #include <linux/platform_device.h>
 #include <linux/seq_file.h>
 #include <linux/spinlock.h>
@@ -344,19 +345,13 @@ struct snps_sdram_addr {
 
 /**
  * struct snps_ecc_error_info - ECC error log information.
- * @row:	Row number.
- * @col:	Column number.
- * @bank:	Bank number.
- * @bankgrp:	Bank group number.
+ * @sdram:	SDRAM address.
  * @bitpos:	Bit position.
  * @data:	Data causing the error.
  * @syndrome:	Erroneous data syndrome.
  */
 struct snps_ecc_error_info {
-	u32 row;
-	u32 col;
-	u32 bank;
-	u32 bankgrp;
+	struct snps_sdram_addr sdram;
 	u32 bitpos;
 	u64 data;
 	u32 syndrome;
@@ -416,6 +411,21 @@ static void snps_map_app_to_hif(struct snps_edac_priv *priv,
 	*hif = app >> priv->info.dq_width;
 }
 
+/**
+ * snps_map_hif_to_app - Map HIF address to Application address.
+ * @priv:	DDR memory controller private instance data.
+ * @hif:	HIF address (source).
+ * @app:	Application address (destination).
+ *
+ * Backward HIF-to-App translation is just the opposite DQ-width-based
+ * shift operation.
+ */
+static void snps_map_hif_to_app(struct snps_edac_priv *priv,
+				u64 hif, u64 *app)
+{
+	*app = hif << priv->info.dq_width;
+}
+
 /**
  * snps_map_hif_to_sdram - Map HIF address to SDRAM address.
  * @priv:	DDR memory controller private instance data.
@@ -467,6 +477,58 @@ static void snps_map_hif_to_sdram(struct snps_edac_priv *priv,
 	}
 }
 
+/**
+ * snps_map_sdram_to_hif - Map SDRAM address to HIF address.
+ * @priv:	DDR memory controller private instance data.
+ * @sdram:	SDRAM address (source).
+ * @hif:	HIF address (destination).
+ *
+ * SDRAM-HIF address mapping is similar to the HIF-SDRAM mapping procedure, but
+ * we'll traverse each SDRAM rank/bank/column/row bit.
+ *
+ * Note the unmapped bits of the SDRAM address components will be just
+ * ignored. So make sure the source address is valid.
+ */
+static void snps_map_sdram_to_hif(struct snps_edac_priv *priv,
+				  struct snps_sdram_addr *sdram, u64 *hif)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	unsigned long addr;
+	int i;
+
+	*hif = 0;
+
+	addr = sdram->row;
+	for_each_set_bit(i, &addr, DDR_MAX_ROW_WIDTH) {
+		if (map->row[i] != DDR_ADDRMAP_UNUSED)
+			*hif |= BIT_ULL(map->row[i]);
+	}
+
+	addr = sdram->col;
+	for_each_set_bit(i, &addr, DDR_MAX_COL_WIDTH) {
+		if (map->col[i] != DDR_ADDRMAP_UNUSED)
+			*hif |= BIT_ULL(map->col[i]);
+	}
+
+	addr = sdram->bank;
+	for_each_set_bit(i, &addr, DDR_MAX_BANK_WIDTH) {
+		if (map->bank[i] != DDR_ADDRMAP_UNUSED)
+			*hif |= BIT_ULL(map->bank[i]);
+	}
+
+	addr = sdram->bankgrp;
+	for_each_set_bit(i, &addr, DDR_MAX_BANKGRP_WIDTH) {
+		if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED)
+			*hif |= BIT_ULL(map->bankgrp[i]);
+	}
+
+	addr = sdram->rank;
+	for_each_set_bit(i, &addr, DDR_MAX_RANK_WIDTH) {
+		if (map->rank[i] != DDR_ADDRMAP_UNUSED)
+			*hif |= BIT_ULL(map->rank[i]);
+	}
+}
+
 /**
  * snps_map_sys_to_sdram - Map System address to SDRAM address.
  * @priv:	DDR memory controller private instance data.
@@ -488,6 +550,27 @@ static void snps_map_sys_to_sdram(struct snps_edac_priv *priv,
 	snps_map_hif_to_sdram(priv, hif, sdram);
 }
 
+/**
+ * snps_map_sdram_to_sys - Map SDRAM address to SDRAM address.
+ * @priv:	DDR memory controller private instance data.
+ * @sys:	System address (source).
+ * @sdram:	SDRAM address (destination).
+ *
+ * Perform a full mapping of the SDRAM address (row/column/bank/etc) to
+ * the system address specific to the controller system bus ports.
+ */
+static void snps_map_sdram_to_sys(struct snps_edac_priv *priv,
+				  struct snps_sdram_addr *sdram, dma_addr_t *sys)
+{
+	u64 app, hif;
+
+	snps_map_sdram_to_hif(priv, sdram, &hif);
+
+	snps_map_hif_to_app(priv, hif, &app);
+
+	*sys = app;
+}
+
 /**
  * snps_get_bitpos - Get DQ-bus corrected bit position.
  * @bitnum:	Bit number retrieved from the ECCSTAT.corrected_bit_num field.
@@ -540,12 +623,13 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 	p->ceinfo.bitpos = snps_get_bitpos(p->ceinfo.bitpos, priv->info.dq_width);
 
 	regval = readl(base + ECC_CEADDR0_OFST);
-	p->ceinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval);
+	p->ceinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval);
+	p->ceinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval);
 
 	regval = readl(base + ECC_CEADDR1_OFST);
-	p->ceinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval);
-	p->ceinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval);
-	p->ceinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
+	p->ceinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval);
+	p->ceinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval);
+	p->ceinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
 
 	p->ceinfo.data = readl(base + ECC_CSYND0_OFST);
 	if (priv->info.dq_width == SNPS_DQ_64)
@@ -558,12 +642,13 @@ static int snps_get_error_info(struct snps_edac_priv *priv)
 		goto out;
 
 	regval = readl(base + ECC_UEADDR0_OFST);
-	p->ueinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval);
+	p->ueinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval);
+	p->ueinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval);
 
 	regval = readl(base + ECC_UEADDR1_OFST);
-	p->ueinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval);
-	p->ueinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval);
-	p->ueinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
+	p->ueinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval);
+	p->ueinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval);
+	p->ueinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval);
 
 	p->ueinfo.data = readl(base + ECC_UESYND0_OFST);
 	if (priv->info.dq_width == SNPS_DQ_64)
@@ -595,17 +680,22 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 {
 	struct snps_edac_priv *priv = mci->pvt_info;
 	struct snps_ecc_error_info *pinf;
+	dma_addr_t sys;
 
 	if (p->ce_cnt) {
 		pinf = &p->ceinfo;
 
 		snprintf(priv->message, SNPS_EDAC_MSG_SIZE,
-			 "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08llx",
-			 pinf->row, pinf->col, pinf->bank, pinf->bankgrp,
+			 "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Bit %d Data 0x%08llx",
+			 pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank,
+			 pinf->sdram.bankgrp, pinf->sdram.rank,
 			 pinf->bitpos, pinf->data);
 
-		edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
-				     p->ce_cnt, 0, 0, pinf->syndrome, 0, 0, -1,
+		snps_map_sdram_to_sys(priv, &pinf->sdram, &sys);
+
+		edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt,
+				     PHYS_PFN(sys), offset_in_page(sys),
+				     pinf->syndrome, 0, 0, -1,
 				     priv->message, "");
 	}
 
@@ -613,12 +703,16 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *
 		pinf = &p->ueinfo;
 
 		snprintf(priv->message, SNPS_EDAC_MSG_SIZE,
-			 "Row %d Col %d Bank %d Bank Group %d Data 0x%08llx",
-			 pinf->row, pinf->col, pinf->bank, pinf->bankgrp,
+			 "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Data 0x%08llx",
+			 pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank,
+			 pinf->sdram.bankgrp, pinf->sdram.rank,
 			 pinf->data);
 
-		edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
-				     p->ue_cnt, 0, 0, pinf->syndrome, 0, 0, -1,
+		snps_map_sdram_to_sys(priv, &pinf->sdram, &sys);
+
+		edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt,
+				     PHYS_PFN(sys), offset_in_page(sys),
+				     pinf->syndrome, 0, 0, -1,
 				     priv->message, "");
 	}
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 17/18] EDAC/synopsys: Add system address regions support
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (15 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 16/18] EDAC/synopsys: Add erroneous page-frame/offset reporting Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  2022-09-10 19:50 ` [PATCH RESEND v2 18/18] EDAC/synopsys: Add mapping-based memory size calculation Serge Semin
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Aside with the Application, HIF and SDRAM address spaces DW uMCTL2 DDR
controller supports one more level of the address abstraction. It's the
System Address Regions (SARs). By default SARs are disabled by means of
the IP-core synthesize parameter UMCTL2_A_NSAR being set to zero. In that
case the System and Application address spaces match. But if that
parameter is set to non-zero value (but less than or equal to 4), then
it's possible to define the selected number of the disjoint memory regions
mapping to the SDRAM as consecutive addresses. So if the SARs are
available on the particular DW uMCTL2 DDR controller, they need to be
taken into account in order to get a correct Physical/DMA address by the
SDRAM address and vice-versa.

The SAR/Application address mapping support is implemented in the similar
way as it has been done for the HIF/SDRAM address translation. First we
get to read the mapping from the SARBASEn and SARSIZEn CSRs and save it in
the System/Application address mapping table as the regions base address,
size and offset. Second the detected mapping table is utilized to
translate the system addresses to the application addresses and vice-versa
in the framework of the sys-to-SDRAM and SDRAM-to-sys address translation
chain. Thus the implemented functionality is integrated into the code,
which requires to translate the addresses: ECC errors detection and ECC
data poisoning.

Note aside with the number of SARs there is the aUMCTL2_SARMINSIZE IP-core
parameter which indicates the SARs minimal block size. Alas it isn't
auto-detectable, but it's critical to have a correct mapping table. So
the suggested functionality expects it being specified for each particular
controller otherwise the system address regions support will be forcibly
disabled in the driver.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 191 ++++++++++++++++++++++++++++++++++-
 1 file changed, 188 insertions(+), 3 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 946b86149ff8..84bede647947 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -15,6 +15,7 @@
 #include <linux/pfn.h>
 #include <linux/platform_device.h>
 #include <linux/seq_file.h>
+#include <linux/sizes.h>
 #include <linux/spinlock.h>
 #include <linux/interrupt.h>
 #include <linux/of.h>
@@ -92,6 +93,10 @@
 #define ECC_POISONPAT1_OFST		0x380
 #define ECC_POISONPAT2_OFST		0x384
 
+/* DDR SAR0 registers */
+#define DDR_SARBASE0_OFST		0xF04
+#define DDR_SARSIZE0_OFST		0xF08
+
 /* DDR Master Register 0 definitions */
 #define DDR_MSTR_DEV_CFG_MASK		GENMASK(31, 30)
 #define DDR_MSTR_DEV_X4			0x0
@@ -223,6 +228,10 @@
 #define RANK_B0_BASE			6
 #define RANK_B1_BASE			7
 
+/* DDRC system address parameters */
+#define DDR_MAX_NSAR			4
+#define DDR_MIN_SARSIZE			SZ_256M
+
 /**
  * enum snps_dq_width - SDRAM DQ bus width (ECC capable).
  * SNPS_DQ_32:	32-bit memory data width.
@@ -308,6 +317,24 @@ struct snps_ddrc_info {
 	unsigned int ranks;
 };
 
+/**
+ * struct snps_sys_app_map - System/Application mapping table.
+ * @nsar:	Number of SARs enabled on the controller (max 4).
+ * @minsize:	Minimal block size (from 256MB to 32GB).
+ * @sar.base:	SAR base address aligned to minsize.
+ * @sar.size:	SAR size aligned to minsize.
+ * @sar.ofst:	SAR address offset.
+ */
+struct snps_sys_app_map {
+	u8 nsar;
+	u64 minsize;
+	struct {
+		u64 base;
+		u64 size;
+		u64 ofst;
+	} sar[DDR_MAX_NSAR];
+};
+
 /**
  * struct snps_hif_sdram_map - HIF/SDRAM mapping table.
  * @row:	HIF bit offsets used as row address bits.
@@ -374,6 +401,7 @@ struct snps_ecc_status {
 /**
  * struct snps_edac_priv - DDR memory controller private data.
  * @info:		DDR controller config info.
+ * @sys_app_map:	Sys/App mapping table.
  * @hif_sdram_map:	HIF/SDRAM mapping table.
  * @pdev:		Platform device.
  * @baseaddr:		Base address of the DDR controller.
@@ -384,6 +412,7 @@ struct snps_ecc_status {
  */
 struct snps_edac_priv {
 	struct snps_ddrc_info info;
+	struct snps_sys_app_map sys_app_map;
 	struct snps_hif_sdram_map hif_sdram_map;
 	struct platform_device *pdev;
 	void __iomem *baseaddr;
@@ -395,6 +424,77 @@ struct snps_edac_priv {
 #endif
 };
 
+/**
+ * snps_map_sys_to_app - Map System address to Application address.
+ * @priv:	DDR memory controller private instance data.
+ * @sys:	System address (source).
+ * @app:	Application address (destination).
+ *
+ * System address space is used to define disjoint memory regions
+ * mapped then to the contiguous application memory space:
+ *
+ *  System Address Space (SAR) <-> Application Address Space
+ *        +------+                        +------+
+ *        | SAR0 |----------------------->| Reg0 |
+ *        +------+       -offset          +------+
+ *        | ...  |           +----------->| Reg1 |
+ *        +------+           |            +------+
+ *        | SAR1 |-----------+            | ...  |
+ *        +------+
+ *        | ...  |
+ *
+ * The translation is done by applying the corresponding SAR offset
+ * to the inbound system address. Note according to the hardware reference
+ * manual the same mapping is applied to the addresses up to the next
+ * SAR base address irrespective to the region size.
+ */
+static void snps_map_sys_to_app(struct snps_edac_priv *priv,
+				dma_addr_t sys, u64 *app)
+{
+	struct snps_sys_app_map *map = &priv->sys_app_map;
+	u64 ofst;
+	int i;
+
+	ofst = 0;
+	for (i = 0; i < map->nsar; i++) {
+		if (sys < map->sar[i].base)
+			break;
+
+		ofst = map->sar[i].ofst;
+	}
+
+	*app = sys - ofst;
+}
+
+/**
+ * snps_map_sys_to_app - Map Application address to System address.
+ * @priv:	DDR memory controller private instance data.
+ * @app:	Application address (source).
+ * @sys:	System address (destination).
+ *
+ * Backward App-to-sys translation is easier because the application address
+ * space is contiguous. So we just need to add the offset corresponding
+ * to the region the passed address belongs to. Note the later offset is applied
+ * to all the addresses above the last available region.
+ */
+static void snps_map_app_to_sys(struct snps_edac_priv *priv,
+				u64 app, dma_addr_t *sys)
+{
+	struct snps_sys_app_map *map = &priv->sys_app_map;
+	u64 ofst, size;
+	int i;
+
+	ofst = 0;
+	for (i = 0, size = 0; i < map->nsar; i++) {
+		ofst = map->sar[i].ofst;
+		size += map->sar[i].size;
+		if (app < size)
+			break;
+	}
+
+	*sys = app + ofst;
+}
+
 /**
  * snps_map_app_to_hif - Map Application address to HIF address.
  * @priv:	DDR memory controller private instance data.
@@ -543,7 +643,7 @@ static void snps_map_sys_to_sdram(struct snps_edac_priv *priv,
 {
 	u64 app, hif;
 
-	app = sys;
+	snps_map_sys_to_app(priv, sys, &app);
 
 	snps_map_app_to_hif(priv, app, &hif);
 
@@ -568,7 +668,7 @@ static void snps_map_sdram_to_sys(struct snps_edac_priv *priv,
 
 	snps_map_hif_to_app(priv, hif, &app);
 
-	*sys = app;
+	snps_map_app_to_sys(priv, app, sys);
 }
 
 /**
@@ -971,6 +1071,52 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv)
 	return init_plat ? init_plat(priv) : 0;
 }
 
+/**
+ * snps_get_sys_app_map - Get System/Application address map.
+ * @priv:	DDR memory controller private instance data.
+ * @sarregs:	Array with SAR registers value.
+ *
+ * System address regions are defined by the SARBASEn and SARSIZEn registers.
+ * Controller reference manual requires the base addresses and sizes creating
+ * a set of ascending non-overlapped regions in order to have a linear
+ * application address space. Doing otherwise causes unpredictable results.
+ */
+static void snps_get_sys_app_map(struct snps_edac_priv *priv, u32 *sarregs)
+{
+	struct snps_sys_app_map *map = &priv->sys_app_map;
+	int i, ofst;
+
+	/*
+	 * SARs are supposed to be initialized in the ascending non-overlapped
+	 * order: base[i - 1] < base[i] < etc. If that rule is broken for a SAR
+	 * it's considered as no more SARs have been enabled, so the detection
+	 * procedure will halt. Having the very first SAR with zero base
+	 * address only makes sense if there is a consequent SAR.
+	 */
+	for (i = 0, ofst = 0; i < DDR_MAX_NSAR; i++) {
+		map->sar[i].base = sarregs[2 * i] * map->minsize;
+		if (map->sar[i].base)
+			map->nsar = i + 1;
+		else if (i && map->sar[i].base <= map->sar[i - 1].base)
+			break;
+
+		map->sar[i].size = (sarregs[2 * i + 1] + 1) * map->minsize;
+		map->sar[i].ofst = map->sar[i].base - ofst;
+		ofst += map->sar[i].size;
+	}
+
+	/*
+	 * SAR block size isn't auto-detectable. If one isn't specified for the
+	 * platform there is a good chance to have invalid mapping of the
+	 * detected SARs. So proceed with 1:1 mapping then.
+	 */
+	if (!map->minsize && map->nsar) {
+		edac_printk(KERN_WARNING, EDAC_MC,
+			    "No block size specified. Discard SARs mapping\n");
+		map->nsar = 0;
+	}
+}
+
 /**
  * snps_get_hif_row_map - Get HIF/SDRAM-row address map.
  * @priv:	DDR memory controller private instance data.
@@ -1220,9 +1366,14 @@ static void snps_get_hif_rank_map(struct snps_edac_priv *priv, u32 *addrmap)
  */
 static void snps_get_addr_map(struct snps_edac_priv *priv)
 {
-	u32 regval[DDR_ADDRMAP_NREGS];
+	u32 regval[max(DDR_ADDRMAP_NREGS, 2 * DDR_MAX_NSAR)];
 	int i;
 
+	for (i = 0; i < 2 * DDR_MAX_NSAR; i++)
+		regval[i] = readl(priv->baseaddr + DDR_SARBASE0_OFST + i * 4);
+
+	snps_get_sys_app_map(priv, regval);
+
 	for (i = 0; i < DDR_ADDRMAP_NREGS; i++)
 		regval[i] = readl(priv->baseaddr + DDR_ADDRMAP0_OFST + i * 4);
 
@@ -1413,6 +1564,37 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data)
 
 DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info);
 
+static int snps_sys_app_map_show(struct seq_file *s, void *data)
+{
+	struct mem_ctl_info *mci = s->private;
+	struct snps_edac_priv *priv = mci->pvt_info;
+	struct snps_sys_app_map *map = &priv->sys_app_map;
+	u64 size;
+	int i;
+
+	if (!map->nsar) {
+		seq_puts(s, "No SARs detected\n");
+		return 0;
+	}
+
+	seq_printf(s, "%9s %-37s %-18s %-37s\n",
+		   "", "System address", "Offset", "App address");
+
+	for (i = 0, size = 0; i < map->nsar; i++) {
+		seq_printf(s, "Region %d: ", i);
+		seq_printf(s, "0x%016llx-0x%016llx ", map->sar[i].base,
+			   map->sar[i].base + map->sar[i].size - 1);
+		seq_printf(s, "0x%016llx ", map->sar[i].ofst);
+		seq_printf(s, "0x%016llx-0x%016llx\n", size,
+			   size + map->sar[i].size - 1);
+		size += map->sar[i].size;
+	}
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(snps_sys_app_map);
+
 static u8 snps_find_sdram_dim(struct snps_edac_priv *priv, u8 hif, char *dim)
 {
 	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
@@ -1610,6 +1792,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci)
 	edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci,
 				 &snps_ddrc_info_fops);
 
+	edac_debugfs_create_file("sys_app_map", 0400, mci->debugfs, mci,
+				 &snps_sys_app_map_fops);
+
 	edac_debugfs_create_file("hif_sdram_map", 0400, mci->debugfs, mci,
 				 &snps_hif_sdram_map_fops);
 
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH RESEND v2 18/18] EDAC/synopsys: Add mapping-based memory size calculation
  2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
                   ` (16 preceding siblings ...)
  2022-09-10 19:50 ` [PATCH RESEND v2 17/18] EDAC/synopsys: Add system address regions support Serge Semin
@ 2022-09-10 19:50 ` Serge Semin
  17 siblings, 0 replies; 19+ messages in thread
From: Serge Semin @ 2022-09-10 19:50 UTC (permalink / raw)
  To: Michal Simek, Borislav Petkov, Mauro Carvalho Chehab, Tony Luck,
	James Morse, Robert Richter
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Michail Ivanov,
	Pavel Parkhomenko, Punnaiah Choudary Kalluri, Manish Narani,
	Dinh Nguyen, linux-arm-kernel, linux-edac, linux-kernel

Currently the size of the memory attached to the controller is retrieved
by means of the si_meminfo() method. It isn't quite correct because the
system may have more than one memory controller. There is a better and
more portable approach available to find out the attached memory size.
Since now we have the full HIF/SDRAM mapping table available right in the
device probe procedure and the DQ-bus width is detected at that stage too,
that info can be used to calculate the total memory size accessible
over the corresponding DW uMCTL2 DDR controller. It can be done since the
hardware reference manual demands that none two SDRAM bits are mapped to
the same HIF bit [1] and that the unused SDRAM address bits mapping must
be disabled [2].

Note the size calculation procedure takes the ranks mapping into account.
That part will be removed after we add the multi-ranked MC registration.

[1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.108
[2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2)
    Databook, Version 3.91a, October 2020, p.109

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
---
 drivers/edac/synopsys_edac.c | 66 ++++++++++++++++++++++++++----------
 1 file changed, 49 insertions(+), 17 deletions(-)

diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c
index 84bede647947..e1e5468acbd2 100644
--- a/drivers/edac/synopsys_edac.c
+++ b/drivers/edac/synopsys_edac.c
@@ -960,20 +960,6 @@ static inline enum dev_type snps_get_dtype(u32 mstr)
 	return DEV_UNKNOWN;
 }
 
-/**
- * snps_get_memsize - Read the size of the attached memory device.
- *
- * Return: the memory size in bytes.
- */
-static u32 snps_get_memsize(void)
-{
-	struct sysinfo inf;
-
-	si_meminfo(&inf);
-
-	return inf.totalram * inf.mem_unit;
-}
-
 /**
  * snps_get_mtype - Returns controller memory type.
  * @mstr:	Master CSR value.
@@ -1388,6 +1374,51 @@ static void snps_get_addr_map(struct snps_edac_priv *priv)
 	snps_get_hif_rank_map(priv, regval);
 }
 
+/**
+ * snps_get_sdram_size - Calculate SDRAM size.
+ * @priv:	DDR memory controller private data.
+ *
+ * The total size of the attached memory is calculated based on the HIF/SDRAM
+ * mapping table. It can be done since the hardware reference manual demands
+ * that none two SDRAM bits should be mapped to the same HIF bit and that the
+ * unused SDRAM address bits mapping must be disabled.
+ *
+ * Return: the memory size in bytes.
+ */
+static u64 snps_get_sdram_size(struct snps_edac_priv *priv)
+{
+	struct snps_hif_sdram_map *map = &priv->hif_sdram_map;
+	u64 size = 0;
+	int i;
+
+	for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) {
+		if (map->row[i] != DDR_ADDRMAP_UNUSED)
+			size++;
+	}
+
+	for (i = 0; i < DDR_MAX_COL_WIDTH; i++) {
+		if (map->col[i] != DDR_ADDRMAP_UNUSED)
+			size++;
+	}
+
+	for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) {
+		if (map->bank[i] != DDR_ADDRMAP_UNUSED)
+			size++;
+	}
+
+	for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) {
+		if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED)
+			size++;
+	}
+
+	for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) {
+		if (map->rank[i] != DDR_ADDRMAP_UNUSED)
+			size++;
+	}
+
+	return 1ULL << (size + priv->info.dq_width);
+}
+
 /**
  * snps_init_csrows - Initialize the csrow data.
  * @mci:	EDAC memory controller instance.
@@ -1400,7 +1431,8 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 	struct snps_edac_priv *priv = mci->pvt_info;
 	struct csrow_info *csi;
 	struct dimm_info *dimm;
-	u32 size, row, width;
+	u32 row, width;
+	u64 size;
 	int j;
 
 	/* Actual SDRAM-word width for which ECC is calculated */
@@ -1408,13 +1440,13 @@ static void snps_init_csrows(struct mem_ctl_info *mci)
 
 	for (row = 0; row < mci->nr_csrows; row++) {
 		csi = mci->csrows[row];
-		size = snps_get_memsize();
+		size = snps_get_sdram_size(priv);
 
 		for (j = 0; j < csi->nr_channels; j++) {
 			dimm		= csi->channels[j]->dimm;
 			dimm->edac_mode	= EDAC_SECDED;
 			dimm->mtype	= priv->info.sdram_mode;
-			dimm->nr_pages	= (size >> PAGE_SHIFT) / csi->nr_channels;
+			dimm->nr_pages	= PHYS_PFN(size) / csi->nr_channels;
 			dimm->grain	= width;
 			dimm->dtype	= priv->info.dev_cfg;
 		}
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2022-09-10 19:52 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-10 19:49 [PATCH RESEND v2 00/18] EDAC/synopsys: Add generic DDRC info and address mapping Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 01/18] EDAC/synopsys: Convert sysfs nodes to debugfs ones Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 02/18] EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2 Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 03/18] EDAC/synopsys: Extend memtypes supported by controller Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 04/18] EDAC/synopsys: Detach private data from mci instance Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 05/18] EDAC/synopsys: Add DDRC basic parameters infrastructure Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 06/18] EDAC/synopsys: Convert plat-data to plat-init function Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 07/18] EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 08/18] EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 09/18] EDAC/synopsys: Set actual DIMM ECC errors grain Serge Semin
2022-09-10 19:49 ` [PATCH RESEND v2 10/18] EDAC/synopsys: Get corrected bit position Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 11/18] EDAC/synopsys: Read full data pattern on errors Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 12/18] EDAC/synopsys: Read data syndrome " Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 13/18] EDAC/synopsys: Introduce System/SDRAM address translation interface Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 14/18] EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 15/18] EDAC/synopsys: Add HIF/SDRAM mapping debugfs node Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 16/18] EDAC/synopsys: Add erroneous page-frame/offset reporting Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 17/18] EDAC/synopsys: Add system address regions support Serge Semin
2022-09-10 19:50 ` [PATCH RESEND v2 18/18] EDAC/synopsys: Add mapping-based memory size calculation Serge Semin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).