From e332873dc7e21282420cde9bee73e0ffaeb5cb31 Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:18 +0200 Subject: [PATCH 1/6] dt-bindings: can: tcan4x5x: Add tcan4552 and tcan4553 variants These two new chips do not have state or wake pins. Signed-off-by: Markus Schneider-Pargmann Acked-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/all/20230728141923.162477-2-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/tcan4x5x.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/net/can/tcan4x5x.txt b/Documentation/devicetree/bindings/net/can/tcan4x5x.txt index e3501bfa22e90..170e23f0610db 100644 --- a/Documentation/devicetree/bindings/net/can/tcan4x5x.txt +++ b/Documentation/devicetree/bindings/net/can/tcan4x5x.txt @@ -4,7 +4,10 @@ Texas Instruments TCAN4x5x CAN Controller This file provides device node information for the TCAN4x5x interface contains. Required properties: - - compatible: "ti,tcan4x5x" + - compatible: + "ti,tcan4552", "ti,tcan4x5x" + "ti,tcan4553", "ti,tcan4x5x" or + "ti,tcan4x5x" - reg: 0 - #address-cells: 1 - #size-cells: 0 @@ -21,8 +24,10 @@ Optional properties: - reset-gpios: Hardwired output GPIO. If not defined then software reset. - device-state-gpios: Input GPIO that indicates if the device is in - a sleep state or if the device is active. - - device-wake-gpios: Wake up GPIO to wake up the TCAN device. + a sleep state or if the device is active. Not + available with tcan4552/4553. + - device-wake-gpios: Wake up GPIO to wake up the TCAN device. Not + available with tcan4552/4553. Example: tcan4x5x: tcan4x5x@0 { From fbe534f7bf213d485b0ed5362b24a41bf3e18803 Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:19 +0200 Subject: [PATCH 2/6] can: tcan4x5x: Remove reserved register 0x814 from writable table The mentioned register is not writable. It is reserved and should not be written. Fixes: 39dbb21b6a29 ("can: tcan4x5x: Specify separate read/write ranges") Signed-off-by: Markus Schneider-Pargmann Reviewed-by: Michal Kubiak Link: https://lore.kernel.org/all/20230728141923.162477-3-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/tcan4x5x-regmap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/can/m_can/tcan4x5x-regmap.c b/drivers/net/can/m_can/tcan4x5x-regmap.c index 2b218ce04e9f2..fafa6daa67e69 100644 --- a/drivers/net/can/m_can/tcan4x5x-regmap.c +++ b/drivers/net/can/m_can/tcan4x5x-regmap.c @@ -95,7 +95,6 @@ static const struct regmap_range tcan4x5x_reg_table_wr_range[] = { regmap_reg_range(0x000c, 0x0010), /* Device configuration registers and Interrupt Flags*/ regmap_reg_range(0x0800, 0x080c), - regmap_reg_range(0x0814, 0x0814), regmap_reg_range(0x0820, 0x0820), regmap_reg_range(0x0830, 0x0830), /* M_CAN */ From c1b17ea7dd7ca7a199d85e42fd3f8e3d46816b73 Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:20 +0200 Subject: [PATCH 3/6] can: tcan4x5x: Check size of mram configuration To reduce debugging effort in case the mram is misconfigured, add this size check of the DT configuration. Currently if the mram configuration doesn't fit into the available MRAM it just overwrites other areas of the MRAM. Signed-off-by: Markus Schneider-Pargmann Reviewed-by: Michal Kubiak Link: https://lore.kernel.org/all/20230728141923.162477-4-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 16 ++++++++++++++++ drivers/net/can/m_can/m_can.h | 1 + drivers/net/can/m_can/tcan4x5x-core.c | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index d204703521d28..16ecc11c7f62a 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1913,6 +1913,22 @@ static int register_m_can_dev(struct net_device *dev) return register_candev(dev); } +int m_can_check_mram_cfg(struct m_can_classdev *cdev, u32 mram_max_size) +{ + u32 total_size; + + total_size = cdev->mcfg[MRAM_TXB].off - cdev->mcfg[MRAM_SIDF].off + + cdev->mcfg[MRAM_TXB].num * TXB_ELEMENT_SIZE; + if (total_size > mram_max_size) { + dev_err(cdev->dev, "Total size of mram config(%u) exceeds mram(%u)\n", + total_size, mram_max_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(m_can_check_mram_cfg); + static void m_can_of_parse_mram(struct m_can_classdev *cdev, const u32 *mram_config_vals) { diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h index c543928c756fc..520e14277dff5 100644 --- a/drivers/net/can/m_can/m_can.h +++ b/drivers/net/can/m_can/m_can.h @@ -103,6 +103,7 @@ int m_can_class_register(struct m_can_classdev *cdev); void m_can_class_unregister(struct m_can_classdev *cdev); int m_can_class_get_clocks(struct m_can_classdev *cdev); int m_can_init_ram(struct m_can_classdev *priv); +int m_can_check_mram_cfg(struct m_can_classdev *cdev, u32 mram_max_size); int m_can_class_suspend(struct device *dev); int m_can_class_resume(struct device *dev); diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index 2342aa011647c..e706518176e4d 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -80,6 +80,7 @@ TCAN4X5X_MCAN_IR_RF1F) #define TCAN4X5X_MRAM_START 0x8000 +#define TCAN4X5X_MRAM_SIZE 0x800 #define TCAN4X5X_MCAN_OFFSET 0x1000 #define TCAN4X5X_CLEAR_ALL_INT 0xffffffff @@ -307,6 +308,10 @@ static int tcan4x5x_can_probe(struct spi_device *spi) if (!mcan_class) return -ENOMEM; + ret = m_can_check_mram_cfg(mcan_class, TCAN4X5X_MRAM_SIZE); + if (ret) + goto out_m_can_class_free_dev; + priv = cdev_to_priv(mcan_class); priv->power = devm_regulator_get_optional(&spi->dev, "vsup"); From 0d6f3b25ac2fa13fddc6106239f7fa7795c8e808 Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:21 +0200 Subject: [PATCH 4/6] can: tcan4x5x: Rename ID registers to match datasheet The datasheet calls these registers ID1 and ID2. Rename these to avoid confusion. Signed-off-by: Markus Schneider-Pargmann Reviewed-by: Michal Kubiak Link: https://lore.kernel.org/all/20230728141923.162477-5-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/tcan4x5x-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index e706518176e4d..fb9375fa20ec0 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -6,8 +6,8 @@ #define TCAN4X5X_EXT_CLK_DEF 40000000 -#define TCAN4X5X_DEV_ID0 0x00 -#define TCAN4X5X_DEV_ID1 0x04 +#define TCAN4X5X_DEV_ID1 0x00 +#define TCAN4X5X_DEV_ID2 0x04 #define TCAN4X5X_REV 0x08 #define TCAN4X5X_STATUS 0x0C #define TCAN4X5X_ERROR_STATUS_MASK 0x10 From 142c6dc6d9d7c27157e41985519464d5985e3d95 Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:22 +0200 Subject: [PATCH 5/6] can: tcan4x5x: Add support for tcan4552/4553 tcan4552 and tcan4553 do not have wake or state pins, so they are currently not compatible with the generic driver. The generic driver uses tcan4x5x_disable_state() and tcan4x5x_disable_wake() if the gpios are not defined. These functions use register bits that are not available in tcan4552/4553. This patch adds support by introducing version information to reflect if the chip has wake and state pins. Also the version is now checked. Signed-off-by: Markus Schneider-Pargmann Link: https://lore.kernel.org/all/20230728141923.162477-6-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/tcan4x5x-core.c | 104 ++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 14 deletions(-) diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index fb9375fa20ec0..2d329b4e4f52c 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -7,6 +7,7 @@ #define TCAN4X5X_EXT_CLK_DEF 40000000 #define TCAN4X5X_DEV_ID1 0x00 +#define TCAN4X5X_DEV_ID1_TCAN 0x4e414354 /* ASCII TCAN */ #define TCAN4X5X_DEV_ID2 0x04 #define TCAN4X5X_REV 0x08 #define TCAN4X5X_STATUS 0x0C @@ -103,6 +104,37 @@ #define TCAN4X5X_WD_3_S_TIMER BIT(29) #define TCAN4X5X_WD_6_S_TIMER (BIT(28) | BIT(29)) +struct tcan4x5x_version_info { + const char *name; + u32 id2_register; + + bool has_wake_pin; + bool has_state_pin; +}; + +enum { + TCAN4552 = 0, + TCAN4553, + TCAN4X5X, +}; + +static const struct tcan4x5x_version_info tcan4x5x_versions[] = { + [TCAN4552] = { + .name = "4552", + .id2_register = 0x32353534, + }, + [TCAN4553] = { + .name = "4553", + .id2_register = 0x32353534, + }, + /* generic version with no id2_register at the end */ + [TCAN4X5X] = { + .name = "generic", + .has_wake_pin = true, + .has_state_pin = true, + }, +}; + static inline struct tcan4x5x_priv *cdev_to_priv(struct m_can_classdev *cdev) { return container_of(cdev, struct tcan4x5x_priv, cdev); @@ -254,18 +286,53 @@ static int tcan4x5x_disable_state(struct m_can_classdev *cdev) TCAN4X5X_DISABLE_INH_MSK, 0x01); } -static int tcan4x5x_get_gpios(struct m_can_classdev *cdev) +static const struct tcan4x5x_version_info +*tcan4x5x_find_version(struct tcan4x5x_priv *priv) +{ + u32 val; + int ret; + + ret = regmap_read(priv->regmap, TCAN4X5X_DEV_ID1, &val); + if (ret) + return ERR_PTR(ret); + + if (val != TCAN4X5X_DEV_ID1_TCAN) { + dev_err(&priv->spi->dev, "Not a tcan device %x\n", val); + return ERR_PTR(-ENODEV); + } + + ret = regmap_read(priv->regmap, TCAN4X5X_DEV_ID2, &val); + if (ret) + return ERR_PTR(ret); + + for (int i = 0; i != ARRAY_SIZE(tcan4x5x_versions); ++i) { + const struct tcan4x5x_version_info *vinfo = &tcan4x5x_versions[i]; + + if (!vinfo->id2_register || val == vinfo->id2_register) { + dev_info(&priv->spi->dev, "Detected TCAN device version %s\n", + vinfo->name); + return vinfo; + } + } + + return &tcan4x5x_versions[TCAN4X5X]; +} + +static int tcan4x5x_get_gpios(struct m_can_classdev *cdev, + const struct tcan4x5x_version_info *version_info) { struct tcan4x5x_priv *tcan4x5x = cdev_to_priv(cdev); int ret; - tcan4x5x->device_wake_gpio = devm_gpiod_get(cdev->dev, "device-wake", - GPIOD_OUT_HIGH); - if (IS_ERR(tcan4x5x->device_wake_gpio)) { - if (PTR_ERR(tcan4x5x->device_wake_gpio) == -EPROBE_DEFER) - return -EPROBE_DEFER; + if (version_info->has_wake_pin) { + tcan4x5x->device_wake_gpio = devm_gpiod_get(cdev->dev, "device-wake", + GPIOD_OUT_HIGH); + if (IS_ERR(tcan4x5x->device_wake_gpio)) { + if (PTR_ERR(tcan4x5x->device_wake_gpio) == -EPROBE_DEFER) + return -EPROBE_DEFER; - tcan4x5x_disable_wake(cdev); + tcan4x5x_disable_wake(cdev); + } } tcan4x5x->reset_gpio = devm_gpiod_get_optional(cdev->dev, "reset", @@ -277,12 +344,14 @@ static int tcan4x5x_get_gpios(struct m_can_classdev *cdev) if (ret) return ret; - tcan4x5x->device_state_gpio = devm_gpiod_get_optional(cdev->dev, - "device-state", - GPIOD_IN); - if (IS_ERR(tcan4x5x->device_state_gpio)) { - tcan4x5x->device_state_gpio = NULL; - tcan4x5x_disable_state(cdev); + if (version_info->has_state_pin) { + tcan4x5x->device_state_gpio = devm_gpiod_get_optional(cdev->dev, + "device-state", + GPIOD_IN); + if (IS_ERR(tcan4x5x->device_state_gpio)) { + tcan4x5x->device_state_gpio = NULL; + tcan4x5x_disable_state(cdev); + } } return 0; @@ -299,6 +368,7 @@ static struct m_can_ops tcan4x5x_ops = { static int tcan4x5x_can_probe(struct spi_device *spi) { + const struct tcan4x5x_version_info *version_info; struct tcan4x5x_priv *priv; struct m_can_classdev *mcan_class; int freq, ret; @@ -361,7 +431,13 @@ static int tcan4x5x_can_probe(struct spi_device *spi) if (ret) goto out_m_can_class_free_dev; - ret = tcan4x5x_get_gpios(mcan_class); + version_info = tcan4x5x_find_version(priv); + if (IS_ERR(version_info)) { + ret = PTR_ERR(version_info); + goto out_power; + } + + ret = tcan4x5x_get_gpios(mcan_class, version_info); if (ret) goto out_power; From 35e7aaab3e0058dba3eaf082c3f1ccee96c20b1c Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Fri, 28 Jul 2023 16:19:23 +0200 Subject: [PATCH 6/6] can: tcan4x5x: Add error messages in probe To be able to understand issues during probe easier, add error messages if something fails. Signed-off-by: Markus Schneider-Pargmann Link: https://lore.kernel.org/all/20230728141923.162477-7-msp@baylibre.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/tcan4x5x-core.c | 29 +++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index 2d329b4e4f52c..8a4143809d337 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -402,6 +402,8 @@ static int tcan4x5x_can_probe(struct spi_device *spi) /* Sanity check */ if (freq < 20000000 || freq > TCAN4X5X_EXT_CLK_DEF) { + dev_err(&spi->dev, "Clock frequency is out of supported range %d\n", + freq); ret = -ERANGE; goto out_m_can_class_free_dev; } @@ -420,16 +422,23 @@ static int tcan4x5x_can_probe(struct spi_device *spi) /* Configure the SPI bus */ spi->bits_per_word = 8; ret = spi_setup(spi); - if (ret) + if (ret) { + dev_err(&spi->dev, "SPI setup failed %pe\n", ERR_PTR(ret)); goto out_m_can_class_free_dev; + } ret = tcan4x5x_regmap_init(priv); - if (ret) + if (ret) { + dev_err(&spi->dev, "regmap init failed %pe\n", ERR_PTR(ret)); goto out_m_can_class_free_dev; + } ret = tcan4x5x_power_enable(priv->power, 1); - if (ret) + if (ret) { + dev_err(&spi->dev, "Enabling regulator failed %pe\n", + ERR_PTR(ret)); goto out_m_can_class_free_dev; + } version_info = tcan4x5x_find_version(priv); if (IS_ERR(version_info)) { @@ -438,16 +447,24 @@ static int tcan4x5x_can_probe(struct spi_device *spi) } ret = tcan4x5x_get_gpios(mcan_class, version_info); - if (ret) + if (ret) { + dev_err(&spi->dev, "Getting gpios failed %pe\n", ERR_PTR(ret)); goto out_power; + } ret = tcan4x5x_init(mcan_class); - if (ret) + if (ret) { + dev_err(&spi->dev, "tcan initialization failed %pe\n", + ERR_PTR(ret)); goto out_power; + } ret = m_can_class_register(mcan_class); - if (ret) + if (ret) { + dev_err(&spi->dev, "Failed registering m_can device %pe\n", + ERR_PTR(ret)); goto out_power; + } netdev_info(mcan_class->net, "TCAN4X5X successfully initialized.\n"); return 0;