@@ -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);
@@ -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_ */
@@ -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)