summaryrefslogtreecommitdiff
path: root/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c')
-rw-r--r--drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c392
1 files changed, 392 insertions, 0 deletions
diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c
new file mode 100644
index 000000000000..81a4ff49ebc6
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_cmd.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2019 Martin Sperl <kernel@martin.sperl.org>
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+#include "mcp25xxfd_cmd.h"
+#include "mcp25xxfd_crc.h"
+#include "mcp25xxfd_priv.h"
+
+/* module parameter */
+static bool use_spi_crc;
+module_param(use_spi_crc, bool, 0664);
+MODULE_PARM_DESC(use_spi_crc, "Use SPI CRC instruction\n");
+
+/* SPI helper */
+
+/* wrapper arround spi_sync, that sets speed_hz */
+static int mcp25xxfd_cmd_sync_transfer(struct spi_device *spi,
+ struct spi_transfer *xfer,
+ unsigned int xfers)
+{
+ struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+ int i;
+
+ for (i = 0; i < xfers; i++)
+ xfer[i].speed_hz = priv->spi_use_speed_hz;
+
+ return spi_sync_transfer(spi, xfer, xfers);
+}
+
+/* simple spi_write wrapper with speed_hz
+ * WARINING: tx_buf needs to be on heap!
+ */
+static int mcp25xxfd_cmd_sync_write(struct spi_device *spi,
+ const void *tx_buf,
+ unsigned len)
+{
+ struct spi_transfer xfer = {
+ .tx_buf = tx_buf,
+ .len = len,
+ };
+
+ return mcp25xxfd_cmd_sync_transfer(spi, &xfer, 1);
+}
+
+/* alloc buffer */
+static int mcp25xxfd_cmd_alloc_buf(struct spi_device *spi,
+ size_t len,
+ u8 **tx, u8 **rx)
+{
+ struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+ /* allocate from heap in case the size is to big
+ * or the preallocated buffer is already used (i.e locked)
+ */
+ if (len > sizeof(priv->spi_tx) ||
+ !mutex_trylock(&priv->spi_rxtx_lock)) {
+ /* allocate tx+rx in one allocation if rx is requested */
+ *tx = kzalloc(rx ? 2 * len : len, GFP_KERNEL);
+ if (!*tx)
+ return -ENOMEM;
+ if (rx)
+ *rx = *tx + len;
+ } else {
+ /* use the preallocated buffers instead */
+ *tx = priv->spi_tx;
+ memset(priv->spi_tx, 0, sizeof(priv->spi_tx));
+ if (rx) {
+ *rx = priv->spi_rx;
+ memset(priv->spi_rx, 0, sizeof(priv->spi_rx));
+ }
+ }
+
+ return 0;
+}
+
+static void mcp25xxfd_cmd_release_buf(struct spi_device *spi, u8 *tx, u8 *rx)
+{
+ struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+ if (tx == priv->spi_tx)
+ mutex_unlock(&priv->spi_rxtx_lock);
+ else
+ kfree(tx);
+}
+
+/* an optimization of spi_write_then_read that merges the transfers
+ * this also makes sure that the data is ALWAYS on heap
+ */
+static int mcp25xxfd_cmd_write_then_read(struct spi_device *spi,
+ const void *tx_buf,
+ unsigned int tx_len,
+ void *rx_buf,
+ unsigned int rx_len,
+ void *crc_buf)
+{
+ int crc_len = crc_buf ? 2 : 0;
+ struct spi_transfer xfer[2];
+ u8 *spi_tx, *spi_rx;
+ int xfers;
+ int ret;
+
+ /* get pointer to buffers */
+ ret = mcp25xxfd_cmd_alloc_buf(spi, tx_len + rx_len + crc_len,
+ &spi_tx, &spi_rx);
+ if (ret)
+ return ret;
+
+ /* clear the xfers */
+ memset(xfer, 0, sizeof(xfer));
+
+ /* special handling for half-duplex */
+ if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
+ xfers = 2;
+ xfer[0].tx_buf = spi_tx;
+ xfer[0].len = tx_len;
+ /* the offset for rx_buf needs to get aligned */
+ xfer[1].rx_buf = spi_rx + tx_len;
+ xfer[1].len = rx_len + crc_len;
+ } else {
+ xfers = 1;
+ xfer[0].len = tx_len + rx_len + crc_len;
+ xfer[0].tx_buf = spi_tx;
+ xfer[0].rx_buf = spi_rx;
+ }
+
+ /* copy data - especially to avoid buffers from stack */
+ memcpy(spi_tx, tx_buf, tx_len);
+
+ /* do the transfer */
+ ret = mcp25xxfd_cmd_sync_transfer(spi, xfer, xfers);
+ if (ret)
+ goto out;
+
+ /* copy result back */
+ memcpy(rx_buf, xfer[0].rx_buf + tx_len, rx_len);
+ if (crc_buf)
+ memcpy(crc_buf, xfer[0].rx_buf + tx_len + rx_len, crc_len);
+
+out:
+ mcp25xxfd_cmd_release_buf(spi, spi_tx, spi_rx);
+
+ return ret;
+}
+
+static int mcp25xxfd_cmd_write_then_write(struct spi_device *spi,
+ const void *tx_buf,
+ unsigned int tx_len,
+ const void *tx2_buf,
+ unsigned int tx2_len)
+{
+ struct spi_transfer xfer;
+ u8 *spi_tx;
+ int ret;
+
+ /* get pointer to buffers */
+ ret = mcp25xxfd_cmd_alloc_buf(spi, tx_len + tx2_len, &spi_tx, NULL);
+ if (ret)
+ return ret;
+
+ /* setup xfer */
+ memset(&xfer, 0, sizeof(xfer));
+ xfer.len = tx_len + tx2_len;
+ xfer.tx_buf = spi_tx;
+
+ /* copy data to correct location in buffer */
+ memcpy(spi_tx, tx_buf, tx_len);
+ memcpy(spi_tx + tx_len, tx2_buf, tx2_len);
+
+ /* run the transfer */
+ ret = mcp25xxfd_cmd_sync_transfer(spi, &xfer, 1);
+
+ mcp25xxfd_cmd_release_buf(spi, spi_tx, NULL);
+
+ return ret;
+}
+
+/* mcp25xxfd spi command/protocol helper */
+
+/* read multiple bytes, transform some registers */
+int mcp25xxfd_cmd_readn(struct spi_device *spi, u32 reg,
+ void *data, int n)
+{
+ u8 cmd[2];
+
+ mcp25xxfd_cmd_calc(MCP25XXFD_INSTRUCTION_READ, reg, cmd);
+
+ return mcp25xxfd_cmd_write_then_read(spi, cmd, ARRAY_SIZE(cmd), data, n, NULL);
+}
+
+static u16 _mcp25xxfd_cmd_compute_crc(u8 *cmd, u8 *data, int n)
+{
+ u16 crc = 0xffff;
+
+ crc = mcp25xxfd_crc(crc, cmd, 3);
+ crc = mcp25xxfd_crc(crc, data, n);
+
+ return crc;
+}
+
+static int _mcp25xxfd_cmd_readn_crc(struct spi_device *spi, u32 reg,
+ void *data, int n)
+{
+ u8 cmd[3], crcd[2];
+ u16 crcc, crcr;
+ int ret;
+
+ /* prepare command */
+ mcp25xxfd_cmd_calc(MCP25XXFD_INSTRUCTION_READ_CRC, reg, cmd);
+ /* count depends on word (=RAM) or byte access (Registers) */
+ if (reg < MCP25XXFD_SRAM_ADDR(0) ||
+ reg >= MCP25XXFD_SRAM_ADDR(MCP25XXFD_SRAM_SIZE))
+ cmd[2] = n;
+ else
+ cmd[2] = n / 4;
+
+ /* now read for real */
+ ret = mcp25xxfd_cmd_write_then_read(spi, &cmd, 3, data, n, crcd);
+ if (ret)
+ return ret;
+
+ /* the received crc */
+ crcr = (crcd[0] << 8) + crcd[1];
+
+ /* compute the crc */
+ crcc = _mcp25xxfd_cmd_compute_crc(cmd, data, n);
+
+ /* if it matches, then return */
+ if (crcc == crcr)
+ return 0;
+
+ /* here possibly handle crc variants with a single bit7 flips */
+
+ /* return with error and rate limited */
+ dev_err_ratelimited(&spi->dev,
+ "CRC read error: computed: %04x received: %04x - data: %*ph %*ph%s\n",
+ crcc, crcr, 3, cmd, min_t(int, 64, n), data,
+ (n > 64) ? "..." : "");
+ return -EILSEQ;
+}
+
+static int mcp25xxfd_cmd_readn_crc(struct spi_device *spi, u32 reg,
+ void *data, int n)
+{
+#ifdef CONFIG_CAN_MCP25XXFD_DEBUG_FS
+ struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+#endif
+ int ret;
+
+ for (; n > 0; n -= 254, reg += 254, data += 254) {
+#ifdef CONFIG_CAN_MCP25XXFD_DEBUG_FS
+ priv->stats.spi_crc_read++;
+ if (n > 254)
+ priv->stats.spi_crc_read_split++;
+#endif
+ ret = _mcp25xxfd_cmd_readn_crc(spi, reg, data, n);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* read a register, but we are only interrested in a few bytes */
+int mcp25xxfd_cmd_read_mask(struct spi_device *spi, u32 reg,
+ u32 *data, u32 mask)
+{
+ int first_byte, last_byte, len_byte;
+ int ret;
+
+ /* check that at least one bit is set */
+ if (!mask)
+ return -EINVAL;
+
+ /* calculate first and last byte used */
+ first_byte = mcp25xxfd_cmd_first_byte(mask);
+ last_byte = mcp25xxfd_cmd_last_byte(mask);
+ len_byte = last_byte - first_byte + 1;
+
+ mcp25xxfd_cmd_convert_from_cpu(data, 1);
+
+ /* do a partial read */
+ ret = mcp25xxfd_cmd_readn(spi, reg + first_byte,
+ ((void *)data + first_byte), len_byte);
+ if (ret)
+ return ret;
+
+ mcp25xxfd_cmd_convert_to_cpu(data, 1);
+
+ return 0;
+}
+
+int mcp25xxfd_cmd_writen(struct spi_device *spi, u32 reg,
+ void *data, int n)
+{
+ u8 cmd[2];
+
+ mcp25xxfd_cmd_calc(MCP25XXFD_INSTRUCTION_WRITE, reg, cmd);
+
+ return mcp25xxfd_cmd_write_then_write(spi, &cmd, 2, data, n);
+}
+
+/* read a register, but we are only interrested in a few bytes */
+int mcp25xxfd_cmd_write_mask(struct spi_device *spi, u32 reg,
+ u32 data, u32 mask)
+{
+ int first_byte, last_byte, len_byte;
+ u8 cmd[2];
+
+ /* check that at least one bit is set */
+ if (!mask)
+ return -EINVAL;
+
+ /* calculate first and last byte used */
+ first_byte = mcp25xxfd_cmd_first_byte(mask);
+ last_byte = mcp25xxfd_cmd_last_byte(mask);
+ len_byte = last_byte - first_byte + 1;
+
+ /* prepare buffer */
+ mcp25xxfd_cmd_calc(MCP25XXFD_INSTRUCTION_WRITE,
+ reg + first_byte, cmd);
+
+ mcp25xxfd_cmd_convert_from_cpu(&data, 1);
+
+ return mcp25xxfd_cmd_write_then_write(spi,
+ cmd, sizeof(cmd),
+ ((void *)&data + first_byte),
+ len_byte);
+}
+
+int mcp25xxfd_cmd_write_regs(struct spi_device *spi, u32 reg,
+ u32 *data, u32 bytes)
+{
+ int ret;
+
+ /* first transpose to controller format */
+ mcp25xxfd_cmd_convert_from_cpu(data, bytes / sizeof(bytes));
+
+ /* now write it */
+ ret = mcp25xxfd_cmd_writen(spi, reg, data, bytes);
+
+ /* and convert it back to cpu format even if it fails */
+ mcp25xxfd_cmd_convert_to_cpu(data, bytes / sizeof(bytes));
+
+ return ret;
+}
+
+int mcp25xxfd_cmd_read_regs(struct spi_device *spi, u32 reg,
+ u32 *data, u32 bytes)
+{
+ int ret;
+
+ /* read it using crc */
+ if ((use_spi_crc) || (reg & MCP25XXFD_ADDRESS_WITH_CRC))
+ ret = mcp25xxfd_cmd_readn_crc(spi,
+ reg & MCP25XXFD_ADDRESS_MASK,
+ data, bytes);
+ else
+ ret = mcp25xxfd_cmd_readn(spi, reg, data, bytes);
+
+ /* and convert it to cpu format */
+ mcp25xxfd_cmd_convert_to_cpu((u32 *)data, bytes / sizeof(bytes));
+
+ return ret;
+}
+
+int mcp25xxfd_cmd_reset(struct spi_device *spi)
+{
+ u8 *cmd;
+ int ret;
+
+ /* allocate 2 bytes on heap, as we use sync_write */
+ cmd = kzalloc(2, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ mcp25xxfd_cmd_calc(MCP25XXFD_INSTRUCTION_RESET, 0, cmd);
+
+ /* write the reset command */
+ ret = mcp25xxfd_cmd_sync_write(spi, cmd, 2);
+
+ kfree(cmd);
+
+ return ret;
+}