]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
can: mcp251xfd: add workaround for errata 5
authorGregor Herburger <gregor.herburger@ew.tq-group.com>
Wed, 1 Oct 2025 09:10:03 +0000 (14:40 +0530)
committerMarc Kleine-Budde <mkl@pengutronix.de>
Wed, 12 Nov 2025 18:30:07 +0000 (19:30 +0100)
According to Errata DS80000789E 5 writing IOCON register using one SPI
write command clears LAT0/LAT1.

Errata Fix/Work Around suggests to write registers with single byte write
instructions. However, it seems that every write to the second byte
causes the overwrite of LAT0/LAT1.

Never write byte 2 of IOCON register to avoid clearing of LAT0/LAT1.

Signed-off-by: Gregor Herburger <gregor.herburger@ew.tq-group.com>
Tested-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
Link: https://patch.msgid.link/20251001091006.4003841-4-viken.dadhaniya@oss.qualcomm.com
[mkl: add missing MCP251XFD_REG_IOCON_GPIO_MASK]
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c
drivers/net/can/spi/mcp251xfd/mcp251xfd.h

index e61cbd209955b8b7cd9b68eced1342cfb9b54097..70d5ff0ae7acb6ab5baf319e99e259b4b92bd5bd 100644 (file)
@@ -13,9 +13,9 @@
 static const struct regmap_config mcp251xfd_regmap_crc;
 
 static int
-mcp251xfd_regmap_nocrc_gather_write(void *context,
-                                   const void *reg, size_t reg_len,
-                                   const void *val, size_t val_len)
+_mcp251xfd_regmap_nocrc_gather_write(void *context,
+                                    const void *reg, size_t reg_len,
+                                    const void *val, size_t val_len)
 {
        struct spi_device *spi = context;
        struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
@@ -39,6 +39,45 @@ mcp251xfd_regmap_nocrc_gather_write(void *context,
        return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
 }
 
+static int
+mcp251xfd_regmap_nocrc_gather_write(void *context,
+                                   const void *reg_p, size_t reg_len,
+                                   const void *val, size_t val_len)
+{
+       const u16 byte_exclude = MCP251XFD_REG_IOCON +
+                                mcp251xfd_first_byte_set(MCP251XFD_REG_IOCON_GPIO_MASK);
+       u16 reg = be16_to_cpu(*(__be16 *)reg_p) & MCP251XFD_SPI_ADDRESS_MASK;
+       int ret;
+
+       /* Never write to bits 16..23 of IOCON register to avoid clearing of LAT0/LAT1
+        *
+        * According to MCP2518FD Errata DS80000789E 5 writing IOCON register using one
+        * SPI write command clears LAT0/LAT1.
+        *
+        * Errata Fix/Work Around suggests to write registers with single byte
+        * write instructions. However, it seems that the byte at 0xe06(IOCON[23:16])
+        * is for read-only access and writing to it causes the clearing of LAT0/LAT1.
+        */
+       if (reg <= byte_exclude && reg + val_len > byte_exclude) {
+               size_t len = byte_exclude - reg;
+
+               /* Write up to 0xe05 */
+               ret = _mcp251xfd_regmap_nocrc_gather_write(context, reg_p, reg_len, val, len);
+               if (ret)
+                       return ret;
+
+               /* Write from 0xe07 on */
+               reg += len + 1;
+               reg = (__force unsigned short)cpu_to_be16(MCP251XFD_SPI_INSTRUCTION_WRITE | reg);
+               return _mcp251xfd_regmap_nocrc_gather_write(context, &reg, reg_len,
+                                                           val + len + 1,
+                                                           val_len - len - 1);
+       }
+
+       return _mcp251xfd_regmap_nocrc_gather_write(context, reg_p, reg_len,
+                                                 val, val_len);
+}
+
 static int
 mcp251xfd_regmap_nocrc_write(void *context, const void *data, size_t count)
 {
@@ -197,9 +236,9 @@ mcp251xfd_regmap_nocrc_read(void *context,
 }
 
 static int
-mcp251xfd_regmap_crc_gather_write(void *context,
-                                 const void *reg_p, size_t reg_len,
-                                 const void *val, size_t val_len)
+_mcp251xfd_regmap_crc_gather_write(void *context,
+                                  const void *reg_p, size_t reg_len,
+                                  const void *val, size_t val_len)
 {
        struct spi_device *spi = context;
        struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
@@ -230,6 +269,44 @@ mcp251xfd_regmap_crc_gather_write(void *context,
        return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
 }
 
+static int
+mcp251xfd_regmap_crc_gather_write(void *context,
+                                 const void *reg_p, size_t reg_len,
+                                 const void *val, size_t val_len)
+{
+       const u16 byte_exclude = MCP251XFD_REG_IOCON +
+                                mcp251xfd_first_byte_set(MCP251XFD_REG_IOCON_GPIO_MASK);
+       u16 reg = *(u16 *)reg_p;
+       int ret;
+
+       /* Never write to bits 16..23 of IOCON register to avoid clearing of LAT0/LAT1
+        *
+        * According to MCP2518FD Errata DS80000789E 5 writing IOCON register using one
+        * SPI write command clears LAT0/LAT1.
+        *
+        * Errata Fix/Work Around suggests to write registers with single byte
+        * write instructions. However, it seems that the byte at 0xe06(IOCON[23:16])
+        * is for read-only access and writing to it causes the clearing of LAT0/LAT1.
+        */
+       if (reg <= byte_exclude  && reg + val_len > byte_exclude) {
+               size_t len = byte_exclude - reg;
+
+               /* Write up to 0xe05 */
+               ret = _mcp251xfd_regmap_crc_gather_write(context, &reg, reg_len, val, len);
+               if (ret)
+                       return ret;
+
+               /* Write from 0xe07 on */
+               reg += len + 1;
+               return _mcp251xfd_regmap_crc_gather_write(context, &reg, reg_len,
+                                                         val + len + 1,
+                                                         val_len - len - 1);
+       }
+
+       return _mcp251xfd_regmap_crc_gather_write(context, reg_p, reg_len,
+                                                 val, val_len);
+}
+
 static int
 mcp251xfd_regmap_crc_write(void *context,
                           const void *data, size_t count)
index dcbbd2b2fae8273844bd1843209ecd5a2ed3a43c..e63034fd59471ab730b13680e2ea21c44f358008 100644 (file)
 #define MCP251XFD_REG_IOCON_PM0 BIT(24)
 #define MCP251XFD_REG_IOCON_GPIO1 BIT(17)
 #define MCP251XFD_REG_IOCON_GPIO0 BIT(16)
+#define MCP251XFD_REG_IOCON_GPIO_MASK GENMASK(17, 16)
 #define MCP251XFD_REG_IOCON_LAT1 BIT(9)
 #define MCP251XFD_REG_IOCON_LAT0 BIT(8)
 #define MCP251XFD_REG_IOCON_XSTBYEN BIT(6)