[v2,5/6] can: m_can: Add ECC functionality for message RAM

Message ID 20221109100928.109478-6-vivek.2311@samsung.com
State New
Headers
Series can: mcan: Add MCAN support for FSD SoC |

Commit Message

Vivek Yadav Nov. 9, 2022, 10:09 a.m. UTC
  Whenever MCAN Buffers and FIFOs are stored on message ram, there are
inherent risks of corruption known as single-bit errors.

Enable error correction code (ECC) data integrity check for Message RAM
to create valid ECC checksums.

ECC uses a respective number of bits, which are added to each word as a
parity and that will raise the error signal on the corruption in the
Interrupt Register(IR).

This indicates either bit error detected and Corrected(BEC) or No bit
error detected when reading from Message RAM.

Signed-off-by: Chandrasekar R <rcsekar@samsung.com>
Signed-off-by: Vivek Yadav <vivek.2311@samsung.com>
---
 drivers/net/can/m_can/m_can.c          | 48 +++++++++++++++-
 drivers/net/can/m_can/m_can.h          | 17 ++++++
 drivers/net/can/m_can/m_can_platform.c | 76 ++++++++++++++++++++++++--
 3 files changed, 135 insertions(+), 6 deletions(-)
  

Comments

Krzysztof Kozlowski Nov. 9, 2022, 11:20 a.m. UTC | #1
On 09/11/2022 11:09, Vivek Yadav wrote:
> Whenever MCAN Buffers and FIFOs are stored on message ram, there are
> inherent risks of corruption known as single-bit errors.
> 
> Enable error correction code (ECC) data integrity check for Message RAM
> to create valid ECC checksums.
> 
> ECC uses a respective number of bits, which are added to each word as a
> parity and that will raise the error signal on the corruption in the
> Interrupt Register(IR).
> 
> This indicates either bit error detected and Corrected(BEC) or No bit
> error detected when reading from Message RAM.
> 
> Signed-off-by: Chandrasekar R <rcsekar@samsung.com>
> Signed-off-by: Vivek Yadav <vivek.2311@samsung.com>

(...)

>  
> +static int m_can_plat_init(struct m_can_classdev *cdev)
> +{
> +	struct  m_can_ecc_regmap *ecc_cfg = &cdev->ecc_cfg_sys;
> +	struct device_node *np = cdev->dev->of_node;
> +	int ret = 0;
> +
> +	if (cdev->mram_cfg_flag != ECC_ENABLE) {
> +		/* Initialize mcan message ram */
> +		ret = m_can_init_ram(cdev);
> +
> +		if (ret)
> +			return ret;
> +
> +		cdev->mram_cfg_flag = ECC_ENABLE;
> +	}
> +
> +	if (ecc_cfg->ecc_cfg_flag != ECC_ENABLE) {
> +		/* configure error code check for mram */
> +		if (!ecc_cfg->syscon) {
> +			ecc_cfg->syscon =
> +			syscon_regmap_lookup_by_phandle_args(np,
> +							     "tesla,mram-ecc-cfg"
> +							     , 1,

, goes to previous line

> +							     &ecc_cfg->reg);
> +		}
> +
> +		if (IS_ERR(ecc_cfg->syscon)) {
> +			dev_err(cdev->dev, "couldn't get the syscon reg!\n");

Didn't you just break all platforms using ECC?

Best regards,
Krzysztof
  

Patch

diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c
index a776cab1a5a4..ddff615ccad4 100644
--- a/drivers/net/can/m_can/m_can.c
+++ b/drivers/net/can/m_can/m_can.c
@@ -307,6 +307,14 @@  enum m_can_reg {
 #define TX_EVENT_MM_MASK	GENMASK(31, 24)
 #define TX_EVENT_TXTS_MASK	GENMASK(15, 0)
 
+/* ECC Config Bits */
+#define MCAN_ECC_CFG_VALID    BIT(5)
+#define MCAN_ECC_ENABLE       BIT(3)
+#define MCAN_ECC_INIT_ENABLE  BIT(1)
+#define MCAN_ECC_INIT_DONE    BIT(0)
+#define MCAN_ECC_REG_MASK     GENMASK(5, 0)
+#define MCAN_ECC_INIT_TIMEOUT 100
+
 /* The ID and DLC registers are adjacent in M_CAN FIFO memory,
  * and we can save a (potentially slow) bus round trip by combining
  * reads and writes to them.
@@ -1516,9 +1524,9 @@  static int m_can_dev_setup(struct m_can_classdev *cdev)
 	}
 
 	if (cdev->ops->init)
-		cdev->ops->init(cdev);
+		err = cdev->ops->init(cdev);
 
-	return 0;
+	return err;
 }
 
 static void m_can_stop(struct net_device *dev)
@@ -1535,6 +1543,39 @@  static void m_can_stop(struct net_device *dev)
 	cdev->can.state = CAN_STATE_STOPPED;
 }
 
+int m_can_config_mram_ecc_check(struct m_can_classdev *cdev, bool enable)
+{
+	struct  m_can_ecc_regmap *ecc_cfg = &cdev->ecc_cfg_sys;
+	int val, ret;
+
+	val = FIELD_PREP(MCAN_ECC_REG_MASK, MCAN_ECC_ENABLE |
+			 MCAN_ECC_CFG_VALID | MCAN_ECC_INIT_ENABLE);
+	regmap_clear_bits(ecc_cfg->syscon, ecc_cfg->reg, val);
+
+	if (enable) {
+		val = FIELD_PREP(MCAN_ECC_REG_MASK, MCAN_ECC_ENABLE |
+				 MCAN_ECC_INIT_ENABLE);
+		regmap_set_bits(ecc_cfg->syscon, ecc_cfg->reg, val);
+	}
+
+	/* after enable or disable, valid flag need to be set*/
+	val = FIELD_PREP(MCAN_ECC_REG_MASK, MCAN_ECC_CFG_VALID);
+	regmap_set_bits(ecc_cfg->syscon, ecc_cfg->reg, val);
+
+	if (enable) {
+		/* Poll for completion */
+		ret = regmap_read_poll_timeout(ecc_cfg->syscon, ecc_cfg->reg,
+					       val,
+					       (val & MCAN_ECC_INIT_DONE), 5,
+					       MCAN_ECC_INIT_TIMEOUT);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static int m_can_close(struct net_device *dev)
 {
 	struct m_can_classdev *cdev = netdev_priv(dev);
@@ -1557,6 +1598,9 @@  static int m_can_close(struct net_device *dev)
 	if (cdev->is_peripheral)
 		can_rx_offload_disable(&cdev->offload);
 
+	if (cdev->ops->deinit)
+		cdev->ops->deinit(cdev);
+
 	close_candev(dev);
 
 	phy_power_off(cdev->transceiver);
diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h
index 401410022823..9821b135a2be 100644
--- a/drivers/net/can/m_can/m_can.h
+++ b/drivers/net/can/m_can/m_can.h
@@ -19,6 +19,7 @@ 
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
 #include <linux/of.h>
@@ -26,6 +27,7 @@ 
 #include <linux/phy/phy.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/pm_runtime.h>
+#include <linux/regmap.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 
@@ -52,12 +54,23 @@  enum m_can_mram_cfg {
 	MRAM_CFG_NUM,
 };
 
+enum m_can_ecc_cfg {
+	ECC_DISABLE = 0,
+	ECC_ENABLE,
+};
+
 /* address offset and element number for each FIFO/Buffer in the Message RAM */
 struct mram_cfg {
 	u16 off;
 	u8  num;
 };
 
+struct m_can_ecc_regmap {
+	struct regmap *syscon;  /* for mram ecc ctrl. reg. access */
+	unsigned int reg;       /* register index within syscon */
+	u8 ecc_cfg_flag;
+};
+
 struct m_can_classdev;
 struct m_can_ops {
 	/* Device specific call backs */
@@ -68,6 +81,7 @@  struct m_can_ops {
 	int (*write_fifo)(struct m_can_classdev *cdev, int addr_offset,
 			  const void *val, size_t val_count);
 	int (*init)(struct m_can_classdev *cdev);
+	int (*deinit)(struct m_can_classdev *cdev);
 };
 
 struct m_can_classdev {
@@ -92,7 +106,9 @@  struct m_can_classdev {
 	int pm_clock_support;
 	int is_peripheral;
 
+	struct m_can_ecc_regmap ecc_cfg_sys; /* ecc config via syscon regmap */
 	struct mram_cfg mcfg[MRAM_CFG_NUM];
+	u8 mram_cfg_flag;
 };
 
 struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, int sizeof_priv);
@@ -104,4 +120,5 @@  int m_can_init_ram(struct m_can_classdev *priv);
 
 int m_can_class_suspend(struct device *dev);
 int m_can_class_resume(struct device *dev);
+int m_can_config_mram_ecc_check(struct m_can_classdev *cdev, bool enable);
 #endif	/* _CAN_M_H_ */
diff --git a/drivers/net/can/m_can/m_can_platform.c b/drivers/net/can/m_can/m_can_platform.c
index b5a5bedb3116..1281214a3f43 100644
--- a/drivers/net/can/m_can/m_can_platform.c
+++ b/drivers/net/can/m_can/m_can_platform.c
@@ -67,11 +67,83 @@  static int iomap_write_fifo(struct m_can_classdev *cdev, int offset,
 	return 0;
 }
 
+static int m_can_plat_init(struct m_can_classdev *cdev)
+{
+	struct  m_can_ecc_regmap *ecc_cfg = &cdev->ecc_cfg_sys;
+	struct device_node *np = cdev->dev->of_node;
+	int ret = 0;
+
+	if (cdev->mram_cfg_flag != ECC_ENABLE) {
+		/* Initialize mcan message ram */
+		ret = m_can_init_ram(cdev);
+
+		if (ret)
+			return ret;
+
+		cdev->mram_cfg_flag = ECC_ENABLE;
+	}
+
+	if (ecc_cfg->ecc_cfg_flag != ECC_ENABLE) {
+		/* configure error code check for mram */
+		if (!ecc_cfg->syscon) {
+			ecc_cfg->syscon =
+			syscon_regmap_lookup_by_phandle_args(np,
+							     "tesla,mram-ecc-cfg"
+							     , 1,
+							     &ecc_cfg->reg);
+		}
+
+		if (IS_ERR(ecc_cfg->syscon)) {
+			dev_err(cdev->dev, "couldn't get the syscon reg!\n");
+			goto ecc_failed;
+		}
+
+		if (!ecc_cfg->reg) {
+			dev_err(cdev->dev,
+				"couldn't get the ecc init reg. offset!\n");
+			goto ecc_failed;
+		}
+
+		/* Enable error code check functionality for message ram */
+		if (m_can_config_mram_ecc_check(cdev, ECC_ENABLE))
+			goto ecc_failed;
+
+		ecc_cfg->ecc_cfg_flag = ECC_ENABLE;
+	}
+
+	return 0;
+
+ecc_failed:
+	dev_err(cdev->dev, "Message ram ecc enable config failed\n");
+
+	return 0;
+}
+
+static int m_can_plat_deinit(struct m_can_classdev *cdev)
+{
+	struct  m_can_ecc_regmap *ecc_cfg = &cdev->ecc_cfg_sys;
+
+	if (ecc_cfg->ecc_cfg_flag == ECC_ENABLE) {
+		/* Disable error code check functionality for message ram */
+		if (m_can_config_mram_ecc_check(cdev, ECC_DISABLE)) {
+			dev_err(cdev->dev,
+				"Message ram ecc disable config failed\n");
+			return 0;
+		}
+
+		ecc_cfg->ecc_cfg_flag = ECC_DISABLE;
+	}
+
+	return 0;
+}
+
 static struct m_can_ops m_can_plat_ops = {
 	.read_reg = iomap_read_reg,
 	.write_reg = iomap_write_reg,
 	.write_fifo = iomap_write_fifo,
 	.read_fifo = iomap_read_fifo,
+	.init = m_can_plat_init,
+	.deinit = m_can_plat_deinit,
 };
 
 static int m_can_plat_probe(struct platform_device *pdev)
@@ -140,10 +212,6 @@  static int m_can_plat_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, mcan_class);
 
-	ret = m_can_init_ram(mcan_class);
-	if (ret)
-		goto probe_fail;
-
 	pm_runtime_enable(mcan_class->dev);
 	ret = m_can_class_register(mcan_class);
 	if (ret)