diff options
author | Marc Kleine-Budde <mkl@pengutronix.de> | 2020-09-18 19:25:33 +0200 |
---|---|---|
committer | Marc Kleine-Budde <mkl@pengutronix.de> | 2020-09-21 10:13:20 +0200 |
commit | 55e5b97f003e85e66babb55f357627d52081a264 (patch) | |
tree | cfb3a61b07ef2d47c397e69a75fbb70ed365b5d9 /drivers/net | |
parent | 875347fe57563693b29cafe24fdee72f0faf6434 (diff) |
can: mcp25xxfd: add driver for Microchip MCP25xxFD SPI CAN
This patch adds support for the Microchip MCP25xxFD SPI CAN controller family.
Tested-by: Kurt Van Dijck <dev.kurt@vandijck-laurijssen.be>
Tested-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Link: https://lore.kernel.org/r/20200918172536.2074504-4-mkl@pengutronix.de
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c | 2907 |
2 files changed, 2908 insertions, 0 deletions
diff --git a/drivers/net/can/spi/mcp25xxfd/Makefile b/drivers/net/can/spi/mcp25xxfd/Makefile index 9dadf0070b42..4e17f592e22e 100644 --- a/drivers/net/can/spi/mcp25xxfd/Makefile +++ b/drivers/net/can/spi/mcp25xxfd/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_CAN_MCP25XXFD) += mcp25xxfd.o mcp25xxfd-objs := +mcp25xxfd-objs += mcp25xxfd-core.o mcp25xxfd-objs += mcp25xxfd-crc16.o mcp25xxfd-objs += mcp25xxfd-regmap.o diff --git a/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c new file mode 100644 index 000000000000..10e4b6d07f95 --- /dev/null +++ b/drivers/net/can/spi/mcp25xxfd/mcp25xxfd-core.c @@ -0,0 +1,2907 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mcp25xxfd - Microchip MCP25xxFD Family CAN controller driver +// +// Copyright (c) 2019, 2020 Pengutronix, +// Marc Kleine-Budde <kernel@pengutronix.de> +// +// Based on: +// +// CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface +// +// Copyright (c) 2019 Martin Sperl <kernel@martin.sperl.org> +// + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> + +#include <asm/unaligned.h> + +#include "mcp25xxfd.h" + +#define DEVICE_NAME "mcp25xxfd" + +static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp2517fd = { + .quirks = MCP25XXFD_QUIRK_MAB_NO_WARN | MCP25XXFD_QUIRK_CRC_REG | + MCP25XXFD_QUIRK_CRC_RX | MCP25XXFD_QUIRK_CRC_TX | + MCP25XXFD_QUIRK_ECC, + .model = MCP25XXFD_MODEL_MCP2517FD, +}; + +static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp2518fd = { + .quirks = MCP25XXFD_QUIRK_CRC_REG | MCP25XXFD_QUIRK_CRC_RX | + MCP25XXFD_QUIRK_CRC_TX | MCP25XXFD_QUIRK_ECC, + .model = MCP25XXFD_MODEL_MCP2518FD, +}; + +/* Autodetect model, start with CRC enabled. */ +static const struct mcp25xxfd_devtype_data mcp25xxfd_devtype_data_mcp25xxfd = { + .quirks = MCP25XXFD_QUIRK_CRC_REG | MCP25XXFD_QUIRK_CRC_RX | + MCP25XXFD_QUIRK_CRC_TX | MCP25XXFD_QUIRK_ECC, + .model = MCP25XXFD_MODEL_MCP25XXFD, +}; + +static const struct can_bittiming_const mcp25xxfd_bittiming_const = { + .name = DEVICE_NAME, + .tseg1_min = 2, + .tseg1_max = 256, + .tseg2_min = 1, + .tseg2_max = 128, + .sjw_max = 128, + .brp_min = 1, + .brp_max = 256, + .brp_inc = 1, +}; + +static const struct can_bittiming_const mcp25xxfd_data_bittiming_const = { + .name = DEVICE_NAME, + .tseg1_min = 1, + .tseg1_max = 32, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 16, + .brp_min = 1, + .brp_max = 256, + .brp_inc = 1, +}; + +static const char *__mcp25xxfd_get_model_str(enum mcp25xxfd_model model) +{ + switch (model) { + case MCP25XXFD_MODEL_MCP2517FD: + return "MCP2517FD"; break; + case MCP25XXFD_MODEL_MCP2518FD: + return "MCP2518FD"; break; + case MCP25XXFD_MODEL_MCP25XXFD: + return "MCP25xxFD"; break; + } + + return "<unknown>"; +} + +static inline const char * +mcp25xxfd_get_model_str(const struct mcp25xxfd_priv *priv) +{ + return __mcp25xxfd_get_model_str(priv->devtype_data.model); +} + +static const char *mcp25xxfd_get_mode_str(const u8 mode) +{ + switch (mode) { + case MCP25XXFD_REG_CON_MODE_MIXED: + return "Mixed (CAN FD/CAN 2.0)"; break; + case MCP25XXFD_REG_CON_MODE_SLEEP: + return "Sleep"; break; + case MCP25XXFD_REG_CON_MODE_INT_LOOPBACK: + return "Internal Loopback"; break; + case MCP25XXFD_REG_CON_MODE_LISTENONLY: + return "Listen Only"; break; + case MCP25XXFD_REG_CON_MODE_CONFIG: + return "Configuration"; break; + case MCP25XXFD_REG_CON_MODE_EXT_LOOPBACK: + return "External Loopback"; break; + case MCP25XXFD_REG_CON_MODE_CAN2_0: + return "CAN 2.0"; break; + case MCP25XXFD_REG_CON_MODE_RESTRICTED: + return "Restricted Operation"; break; + } + + return "<unknown>"; +} + +static inline int mcp25xxfd_vdd_enable(const struct mcp25xxfd_priv *priv) +{ + if (!priv->reg_vdd) + return 0; + + return regulator_enable(priv->reg_vdd); +} + +static inline int mcp25xxfd_vdd_disable(const struct mcp25xxfd_priv *priv) +{ + if (!priv->reg_vdd) + return 0; + + return regulator_disable(priv->reg_vdd); +} + +static inline int +mcp25xxfd_transceiver_enable(const struct mcp25xxfd_priv *priv) +{ + if (!priv->reg_xceiver) + return 0; + + return regulator_enable(priv->reg_xceiver); +} + +static inline int +mcp25xxfd_transceiver_disable(const struct mcp25xxfd_priv *priv) +{ + if (!priv->reg_xceiver) + return 0; + + return regulator_disable(priv->reg_xceiver); +} + +static int mcp25xxfd_clks_and_vdd_enable(const struct mcp25xxfd_priv *priv) +{ + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + err = mcp25xxfd_vdd_enable(priv); + if (err) + clk_disable_unprepare(priv->clk); + + /* Wait for oscillator stabilisation time after power up */ + usleep_range(MCP25XXFD_OSC_STAB_SLEEP_US, + 2 * MCP25XXFD_OSC_STAB_SLEEP_US); + + return err; +} + +static int mcp25xxfd_clks_and_vdd_disable(const struct mcp25xxfd_priv *priv) +{ + int err; + + err = mcp25xxfd_vdd_disable(priv); + if (err) + return err; + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static inline u8 +mcp25xxfd_cmd_prepare_write_reg(const struct mcp25xxfd_priv *priv, + union mcp25xxfd_write_reg_buf *write_reg_buf, + const u16 reg, const u32 mask, const u32 val) +{ + u8 first_byte, last_byte, len; + u8 *data; + __le32 val_le32; + + first_byte = mcp25xxfd_first_byte_set(mask); + last_byte = mcp25xxfd_last_byte_set(mask); + len = last_byte - first_byte + 1; + + data = mcp25xxfd_spi_cmd_write(priv, write_reg_buf, reg + first_byte); + val_le32 = cpu_to_le32(val >> BITS_PER_BYTE * first_byte); + memcpy(data, &val_le32, len); + + if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_REG) { + u16 crc; + + mcp25xxfd_spi_cmd_crc_set_len_in_reg(&write_reg_buf->crc.cmd, + len); + /* CRC */ + len += sizeof(write_reg_buf->crc.cmd); + crc = mcp25xxfd_crc16_compute(&write_reg_buf->crc, len); + put_unaligned_be16(crc, (void *)write_reg_buf + len); + + /* Total length */ + len += sizeof(write_reg_buf->crc.crc); + } else { + len += sizeof(write_reg_buf->nocrc.cmd); + } + + return len; +} + +static inline int +mcp25xxfd_tef_tail_get_from_chip(const struct mcp25xxfd_priv *priv, + u8 *tef_tail) +{ + u32 tef_ua; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_TEFUA, &tef_ua); + if (err) + return err; + + *tef_tail = tef_ua / sizeof(struct mcp25xxfd_hw_tef_obj); + + return 0; +} + +static inline int +mcp25xxfd_tx_tail_get_from_chip(const struct mcp25xxfd_priv *priv, + u8 *tx_tail) +{ + u32 fifo_sta; + int err; + + err = regmap_read(priv->map_reg, + MCP25XXFD_REG_FIFOSTA(MCP25XXFD_TX_FIFO), + &fifo_sta); + if (err) + return err; + + *tx_tail = FIELD_GET(MCP25XXFD_REG_FIFOSTA_FIFOCI_MASK, fifo_sta); + + return 0; +} + +static inline int +mcp25xxfd_rx_head_get_from_chip(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_rx_ring *ring, + u8 *rx_head) +{ + u32 fifo_sta; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_FIFOSTA(ring->fifo_nr), + &fifo_sta); + if (err) + return err; + + *rx_head = FIELD_GET(MCP25XXFD_REG_FIFOSTA_FIFOCI_MASK, fifo_sta); + + return 0; +} + +static inline int +mcp25xxfd_rx_tail_get_from_chip(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_rx_ring *ring, + u8 *rx_tail) +{ + u32 fifo_ua; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_FIFOUA(ring->fifo_nr), + &fifo_ua); + if (err) + return err; + + fifo_ua -= ring->base - MCP25XXFD_RAM_START; + *rx_tail = fifo_ua / ring->obj_size; + + return 0; +} + +static void +mcp25xxfd_tx_ring_init_tx_obj(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_tx_ring *ring, + struct mcp25xxfd_tx_obj *tx_obj, + const u8 rts_buf_len, + const u8 n) +{ + struct spi_transfer *xfer; + u16 addr; + + /* FIFO load */ + addr = mcp25xxfd_get_tx_obj_addr(ring, n); + if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_CRC_TX) + mcp25xxfd_spi_cmd_write_crc_set_addr(&tx_obj->buf.crc.cmd, + addr); + else + mcp25xxfd_spi_cmd_write_nocrc(&tx_obj->buf.nocrc.cmd, + addr); + + xfer = &tx_obj->xfer[0]; + xfer->tx_buf = &tx_obj->buf; + xfer->len = 0; /* actual len is assigned on the fly */ + xfer->cs_change = 1; + xfer->cs_change_delay.value = 0; + xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + /* FIFO request to send */ + xfer = &tx_obj->xfer[1]; + xfer->tx_buf = &ring->rts_buf; + xfer->len = rts_buf_len; + + /* SPI message */ + spi_message_init_with_transfers(&tx_obj->msg, tx_obj->xfer, + ARRAY_SIZE(tx_obj->xfer)); +} + +static void mcp25xxfd_ring_init(struct mcp25xxfd_priv *priv) +{ + struct mcp25xxfd_tx_ring *tx_ring; + struct mcp25xxfd_rx_ring *rx_ring, *prev_rx_ring = NULL; + struct mcp25xxfd_tx_obj *tx_obj; + u32 val; + u16 addr; + u8 len; + int i; + + /* TEF */ + priv->tef.head = 0; + priv->tef.tail = 0; + + /* TX */ + tx_ring = priv->tx; + tx_ring->head = 0; + tx_ring->tail = 0; + tx_ring->base = mcp25xxfd_get_tef_obj_addr(tx_ring->obj_num); + + /* FIFO request to send */ + addr = MCP25XXFD_REG_FIFOCON(MCP25XXFD_TX_FIFO); + val = MCP25XXFD_REG_FIFOCON_TXREQ | MCP25XXFD_REG_FIFOCON_UINC; + len = mcp25xxfd_cmd_prepare_write_reg(priv, &tx_ring->rts_buf, + addr, val, val); + + mcp25xxfd_for_each_tx_obj(tx_ring, tx_obj, i) + mcp25xxfd_tx_ring_init_tx_obj(priv, tx_ring, tx_obj, len, i); + + /* RX */ + mcp25xxfd_for_each_rx_ring(priv, rx_ring, i) { + rx_ring->head = 0; + rx_ring->tail = 0; + rx_ring->nr = i; + rx_ring->fifo_nr = MCP25XXFD_RX_FIFO(i); + + if (!prev_rx_ring) + rx_ring->base = + mcp25xxfd_get_tx_obj_addr(tx_ring, + tx_ring->obj_num); + else + rx_ring->base = prev_rx_ring->base + + prev_rx_ring->obj_size * + prev_rx_ring->obj_num; + + prev_rx_ring = rx_ring; + } +} + +static void mcp25xxfd_ring_free(struct mcp25xxfd_priv *priv) +{ + int i; + + for (i = ARRAY_SIZE(priv->rx) - 1; i > 0; i--) { + kfree(priv->rx[i]); + priv->rx[i] = NULL; + } +} + +static int mcp25xxfd_ring_alloc(struct mcp25xxfd_priv *priv) +{ + struct mcp25xxfd_tx_ring *tx_ring; + struct mcp25xxfd_rx_ring *rx_ring; + int tef_obj_size, tx_obj_size, rx_obj_size; + int tx_obj_num; + int ram_free, i; + + tef_obj_size = sizeof(struct mcp25xxfd_hw_tef_obj); + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + tx_obj_num = MCP25XXFD_TX_OBJ_NUM_CANFD; + tx_obj_size = sizeof(struct mcp25xxfd_hw_tx_obj_canfd); + rx_obj_size = sizeof(struct mcp25xxfd_hw_rx_obj_canfd); + } else { + tx_obj_num = MCP25XXFD_TX_OBJ_NUM_CAN; + tx_obj_size = sizeof(struct mcp25xxfd_hw_tx_obj_can); + rx_obj_size = sizeof(struct mcp25xxfd_hw_rx_obj_can); + } + + tx_ring = priv->tx; + tx_ring->obj_num = tx_obj_num; + tx_ring->obj_size = tx_obj_size; + + ram_free = MCP25XXFD_RAM_SIZE - tx_obj_num * + (tef_obj_size + tx_obj_size); + + for (i = 0; + i < ARRAY_SIZE(priv->rx) && ram_free >= rx_obj_size; + i++) { + int rx_obj_num; + + rx_obj_num = ram_free / rx_obj_size; + rx_obj_num = min(1 << (fls(rx_obj_num) - 1), 32); + + rx_ring = kzalloc(sizeof(*rx_ring) + rx_obj_size * rx_obj_num, + GFP_KERNEL); + if (!rx_ring) { + mcp25xxfd_ring_free(priv); + return -ENOMEM; + } + rx_ring->obj_num = rx_obj_num; + rx_ring->obj_size = rx_obj_size; + priv->rx[i] = rx_ring; + + ram_free -= rx_ring->obj_num * rx_ring->obj_size; + } + priv->rx_ring_num = i; + + netdev_dbg(priv->ndev, + "FIFO setup: TEF: %d*%d bytes = %d bytes, TX: %d*%d bytes = %d bytes\n", + tx_obj_num, tef_obj_size, tef_obj_size * tx_obj_num, + tx_obj_num, tx_obj_size, tx_obj_size * tx_obj_num); + + mcp25xxfd_for_each_rx_ring(priv, rx_ring, i) { + netdev_dbg(priv->ndev, + "FIFO setup: RX-%d: %d*%d bytes = %d bytes\n", + i, rx_ring->obj_num, rx_ring->obj_size, + rx_ring->obj_size * rx_ring->obj_num); + } + + netdev_dbg(priv->ndev, + "FIFO setup: free: %d bytes\n", + ram_free); + + return 0; +} + +static inline int +mcp25xxfd_chip_get_mode(const struct mcp25xxfd_priv *priv, u8 *mode) +{ + u32 val; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_CON, &val); + if (err) + return err; + + *mode = FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK, val); + + return 0; +} + +static int +__mcp25xxfd_chip_set_mode(const struct mcp25xxfd_priv *priv, + const u8 mode_req, bool nowait) +{ + u32 con, con_reqop; + int err; + + con_reqop = FIELD_PREP(MCP25XXFD_REG_CON_REQOP_MASK, mode_req); + err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_CON, + MCP25XXFD_REG_CON_REQOP_MASK, con_reqop); + if (err) + return err; + + if (mode_req == MCP25XXFD_REG_CON_MODE_SLEEP || nowait) + return 0; + + err = regmap_read_poll_timeout(priv->map_reg, MCP25XXFD_REG_CON, con, + FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK, + con) == mode_req, + MCP25XXFD_POLL_SLEEP_US, + MCP25XXFD_POLL_TIMEOUT_US); + if (err) { + u8 mode = FIELD_GET(MCP25XXFD_REG_CON_OPMOD_MASK, con); + + netdev_err(priv->ndev, + "Controller failed to enter mode %s Mode (%u) and stays in %s Mode (%u).\n", + mcp25xxfd_get_mode_str(mode_req), mode_req, + mcp25xxfd_get_mode_str(mode), mode); + return err; + } + + return 0; +} + +static inline int +mcp25xxfd_chip_set_mode(const struct mcp25xxfd_priv *priv, + const u8 mode_req) +{ + return __mcp25xxfd_chip_set_mode(priv, mode_req, false); +} + +static inline int +mcp25xxfd_chip_set_mode_nowait(const struct mcp25xxfd_priv *priv, + const u8 mode_req) +{ + return __mcp25xxfd_chip_set_mode(priv, mode_req, true); +} + +static inline bool mcp25xxfd_osc_invalid(u32 reg) +{ + return reg == 0x0 || reg == 0xffffffff; +} + +static int mcp25xxfd_chip_clock_enable(const struct mcp25xxfd_priv *priv) +{ + u32 osc, osc_reference, osc_mask; + int err; + + /* Set Power On Defaults for "Clock Output Divisor" and remove + * "Oscillator Disable" bit. + */ + osc = FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK, + MCP25XXFD_REG_OSC_CLKODIV_10); + osc_reference = MCP25XXFD_REG_OSC_OSCRDY; + osc_mask = MCP25XXFD_REG_OSC_OSCRDY | MCP25XXFD_REG_OSC_PLLRDY; + + /* Note: + * + * If the controller is in Sleep Mode the following write only + * removes the "Oscillator Disable" bit and powers it up. All + * other bits are unaffected. + */ + err = regmap_write(priv->map_reg, MCP25XXFD_REG_OSC, osc); + if (err) + return err; + + /* Wait for "Oscillator Ready" bit */ + err = regmap_read_poll_timeout(priv->map_reg, MCP25XXFD_REG_OSC, osc, + (osc & osc_mask) == osc_reference, + MCP25XXFD_OSC_STAB_SLEEP_US, + MCP25XXFD_OSC_STAB_TIMEOUT_US); + if (mcp25xxfd_osc_invalid(osc)) { + netdev_err(priv->ndev, + "Failed to detect %s (osc=0x%08x).\n", + mcp25xxfd_get_model_str(priv), osc); + return -ENODEV; + } else if (err == -ETIMEDOUT) { + netdev_err(priv->ndev, + "Timeout waiting for Oscillator Ready (osc=0x%08x, osc_reference=0x%08x)\n", + osc, osc_reference); + return -ETIMEDOUT; + } else if (err) { + return err; + } + + return 0; +} + +static int mcp25xxfd_chip_softreset_do(const struct mcp25xxfd_priv *priv) +{ + const __be16 cmd = mcp25xxfd_cmd_reset(); + int err; + + /* The Set Mode and SPI Reset command only seems to works if + * the controller is not in Sleep Mode. + */ + err = mcp25xxfd_chip_clock_enable(priv); + if (err) + return err; + + err = mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_CONFIG); + if (err) + return err; + + /* spi_write_then_read() works with non DMA-safe buffers */ + return spi_write_then_read(priv->spi, &cmd, sizeof(cmd), NULL, 0); +} + +static int mcp25xxfd_chip_softreset_check(const struct mcp25xxfd_priv *priv) +{ + u32 osc, osc_reference; + u8 mode; + int err; + + err = mcp25xxfd_chip_get_mode(priv, &mode); + if (err) + return err; + + if (mode != MCP25XXFD_REG_CON_MODE_CONFIG) { + netdev_info(priv->ndev, + "Controller not in Config Mode after reset, but in %s Mode (%u).\n", + mcp25xxfd_get_mode_str(mode), mode); + return -ETIMEDOUT; + } + + osc_reference = MCP25XXFD_REG_OSC_OSCRDY | + FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK, + MCP25XXFD_REG_OSC_CLKODIV_10); + + /* check reset defaults of OSC reg */ + err = regmap_read(priv->map_reg, MCP25XXFD_REG_OSC, &osc); + if (err) + return err; + + if (osc != osc_reference) { + netdev_info(priv->ndev, + "Controller failed to reset. osc=0x%08x, reference value=0x%08x\n", + osc, osc_reference); + return -ETIMEDOUT; + } + + return 0; +} + +static int mcp25xxfd_chip_softreset(const struct mcp25xxfd_priv *priv) +{ + int err, i; + + for (i = 0; i < MCP25XXFD_SOFTRESET_RETRIES_MAX; i++) { + if (i) + netdev_info(priv->ndev, + "Retrying to reset Controller.\n"); + + err = mcp25xxfd_chip_softreset_do(priv); + if (err == -ETIMEDOUT) + continue; + if (err) + return err; + + err = mcp25xxfd_chip_softreset_check(priv); + if (err == -ETIMEDOUT) + continue; + if (err) + return err; + + return 0; + } + + if (err) + return err; + + return -ETIMEDOUT; +} + +static int mcp25xxfd_chip_clock_init(const struct mcp25xxfd_priv *priv) +{ + u32 osc; + int err; + + /* Activate Low Power Mode on Oscillator Disable. This only + * works on the MCP2518FD. The MCP2517FD will go into normal + * Sleep Mode instead. + */ + osc = MCP25XXFD_REG_OSC_LPMEN | + FIELD_PREP(MCP25XXFD_REG_OSC_CLKODIV_MASK, + MCP25XXFD_REG_OSC_CLKODIV_10); + err = regmap_write(priv->map_reg, MCP25XXFD_REG_OSC, osc); + if (err) + return err; + + /* Set Time Base Counter Prescaler to 1. + * + * This means an overflow of the 32 bit Time Base Counter + * register at 40 MHz every 107 seconds. + */ + return regmap_write(priv->map_reg, MCP25XXFD_REG_TSCON, + MCP25XXFD_REG_TSCON_TBCEN); +} + +static int mcp25xxfd_set_bittiming(const struct mcp25xxfd_priv *priv) +{ + const struct can_bittiming *bt = &priv->can.bittiming; + const struct can_bittiming *dbt = &priv->can.data_bittiming; + u32 val = 0; + s8 tdco; + int err; + + /* CAN Control Register + * + * - no transmit bandwidth sharing + * - config mode + * - disable transmit queue + * - store in transmit FIFO event + * - transition to restricted operation mode on system error + * - ESI is transmitted recessive when ESI of message is high or + * CAN controller error passive + * - restricted retransmission attempts, + * use TQXCON_TXAT and FIFOCON_TXAT + * - wake-up filter bits T11FILTER + * - use CAN bus line filter for wakeup + * - protocol exception is treated as a form error + * - Do not compare data bytes + */ + val = FIELD_PREP(MCP25XXFD_REG_CON_REQOP_MASK, + MCP25XXFD_REG_CON_MODE_CONFIG) | + MCP25XXFD_REG_CON_STEF | + MCP25XXFD_REG_CON_ESIGM | + MCP25XXFD_REG_CON_RTXAT | + FIELD_PREP(MCP25XXFD_REG_CON_WFT_MASK, + MCP25XXFD_REG_CON_WFT_T11FILTER) | + MCP25XXFD_REG_CON_WAKFIL | + MCP25XXFD_REG_CON_PXEDIS; + + if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)) + val |= MCP25XXFD_REG_CON_ISOCRCEN; + + err = regmap_write(priv->map_reg, MCP25XXFD_REG_CON, val); + if (err) + return err; + + /* Nominal Bit Time */ + val = FIELD_PREP(MCP25XXFD_REG_NBTCFG_BRP_MASK, bt->brp - 1) | + FIELD_PREP(MCP25XXFD_REG_NBTCFG_TSEG1_MASK, + bt->prop_seg + bt->phase_seg1 - 1) | + FIELD_PREP(MCP25XXFD_REG_NBTCFG_TSEG2_MASK, + bt->phase_seg2 - 1) | + FIELD_PREP(MCP25XXFD_REG_NBTCFG_SJW_MASK, bt->sjw - 1); + + err = regmap_write(priv->map_reg, MCP25XXFD_REG_NBTCFG, val); + if (err) + return err; + + if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD)) + return 0; + + /* Data Bit Time */ + val = FIELD_PREP(MCP25XXFD_REG_DBTCFG_BRP_MASK, dbt->brp - 1) | + FIELD_PREP(MCP25XXFD_REG_DBTCFG_TSEG1_MASK, + dbt->prop_seg + dbt->phase_seg1 - 1) | + FIELD_PREP(MCP25XXFD_REG_DBTCFG_TSEG2_MASK, + dbt->phase_seg2 - 1) | + FIELD_PREP(MCP25XXFD_REG_DBTCFG_SJW_MASK, dbt->sjw - 1); + + err = regmap_write(priv->map_reg, MCP25XXFD_REG_DBTCFG, val); + if (err) + return err; + + /* Transmitter Delay Compensation */ + tdco = clamp_t(int, dbt->brp * (dbt->prop_seg + dbt->phase_seg1), + -64, 63); + val = FIELD_PREP(MCP25XXFD_REG_TDC_TDCMOD_MASK, + MCP25XXFD_REG_TDC_TDCMOD_AUTO) | + FIELD_PREP(MCP25XXFD_REG_TDC_TDCO_MASK, tdco); + + return regmap_write(priv->map_reg, MCP25XXFD_REG_TDC, val); +} + +static int mcp25xxfd_chip_rx_int_enable(const struct mcp25xxfd_priv *priv) +{ + u32 val; + + if (!priv->rx_int) + return 0; + + /* Configure GPIOs: + * - PIN0: GPIO Input + * - PIN1: GPIO Input/RX Interrupt + * + * PIN1 must be Input, otherwise there is a glitch on the + * rx-INT line. It happens between setting the PIN as output + * (in the first byte of the SPI transfer) and configuring the + * PIN as interrupt (in the last byte of the SPI transfer). + */ + val = MCP25XXFD_REG_IOCON_PM0 | MCP25XXFD_REG_IOCON_TRIS1 | + MCP25XXFD_REG_IOCON_TRIS0; + return regmap_write(priv->map_reg, MCP25XXFD_REG_IOCON, val); +} + +static int mcp25xxfd_chip_rx_int_disable(const struct mcp25xxfd_priv *priv) +{ + u32 val; + + if (!priv->rx_int) + return 0; + + /* Configure GPIOs: + * - PIN0: GPIO Input + * - PIN1: GPIO Input + */ + val = MCP25XXFD_REG_IOCON_PM1 | MCP25XXFD_REG_IOCON_PM0 | + MCP25XXFD_REG_IOCON_TRIS1 | MCP25XXFD_REG_IOCON_TRIS0; + return regmap_write(priv->map_reg, MCP25XXFD_REG_IOCON, val); +} + +static int +mcp25xxfd_chip_rx_fifo_init_one(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_rx_ring *ring) +{ + u32 fifo_con; + + /* Enable RXOVIE on _all_ RX FIFOs, not just the last one. + * + * FIFOs hit by a RX MAB overflow and RXOVIE enabled will + * generate a RXOVIF, use this to properly detect RX MAB + * overflows. + */ + fifo_con = FIELD_PREP(MCP25XXFD_REG_FIFOCON_FSIZE_MASK, + ring->obj_num - 1) | + MCP25XXFD_REG_FIFOCON_RXTSEN | + MCP25XXFD_REG_FIFOCON_RXOVIE | + MCP25XXFD_REG_FIFOCON_TFNRFNIE; + + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) + fifo_con |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK, + MCP25XXFD_REG_FIFOCON_PLSIZE_64); + else + fifo_con |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK, + MCP25XXFD_REG_FIFOCON_PLSIZE_8); + + return regmap_write(priv->map_reg, + MCP25XXFD_REG_FIFOCON(ring->fifo_nr), fifo_con); +} + +static int +mcp25xxfd_chip_rx_filter_init_one(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_rx_ring *ring) +{ + u32 fltcon; + + fltcon = MCP25XXFD_REG_FLTCON_FLTEN(ring->nr) | + MCP25XXFD_REG_FLTCON_FBP(ring->nr, ring->fifo_nr); + + return regmap_update_bits(priv->map_reg, + MCP25XXFD_REG_FLTCON(ring->nr >> 2), + MCP25XXFD_REG_FLTCON_FLT_MASK(ring->nr), + fltcon); +} + +static int mcp25xxfd_chip_fifo_init(const struct mcp25xxfd_priv *priv) +{ + const struct mcp25xxfd_tx_ring *tx_ring = priv->tx; + const struct mcp25xxfd_rx_ring *rx_ring; + u32 val; + int err, n; + + /* TEF */ + val = FIELD_PREP(MCP25XXFD_REG_TEFCON_FSIZE_MASK, + tx_ring->obj_num - 1) | + MCP25XXFD_REG_TEFCON_TEFTSEN | + MCP25XXFD_REG_TEFCON_TEFOVIE | + MCP25XXFD_REG_TEFCON_TEFNEIE; + + err = regmap_write(priv->map_reg, MCP25XXFD_REG_TEFCON, val); + if (err) + return err; + + /* FIFO 1 - TX */ + val = FIELD_PREP(MCP25XXFD_REG_FIFOCON_FSIZE_MASK, + tx_ring->obj_num - 1) | + MCP25XXFD_REG_FIFOCON_TXEN | + MCP25XXFD_REG_FIFOCON_TXATIE; + + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) + val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK, + MCP25XXFD_REG_FIFOCON_PLSIZE_64); + else + val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_PLSIZE_MASK, + MCP25XXFD_REG_FIFOCON_PLSIZE_8); + + if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT) + val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_TXAT_MASK, + MCP25XXFD_REG_FIFOCON_TXAT_ONE_SHOT); + else + val |= FIELD_PREP(MCP25XXFD_REG_FIFOCON_TXAT_MASK, + MCP25XXFD_REG_FIFOCON_TXAT_UNLIMITED); + + err = regmap_write(priv->map_reg, + MCP25XXFD_REG_FIFOCON(MCP25XXFD_TX_FIFO), + val); + if (err) + return err; + + /* RX FIFOs */ + mcp25xxfd_for_each_rx_ring(priv, rx_ring, n) { + err = mcp25xxfd_chip_rx_fifo_init_one(priv, rx_ring); + if (err) + return err; + + err = mcp25xxfd_chip_rx_filter_init_one(priv, rx_ring); + if (err) + return err; + } + + return 0; +} + +static int mcp25xxfd_chip_ecc_init(struct mcp25xxfd_priv *priv) +{ + struct mcp25xxfd_ecc *ecc = &priv->ecc; + void *ram; + u32 val = 0; + int err; + + ecc->ecc_stat = 0; + + if (priv->devtype_data.quirks & MCP25XXFD_QUIRK_ECC) + val = MCP25XXFD_REG_ECCCON_ECCEN; + + err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON, + MCP25XXFD_REG_ECCCON_ECCEN, val); + if (err) + return err; + + ram = kzalloc(MCP25XXFD_RAM_SIZE, GFP_KERNEL); + if (!ram) + return -ENOMEM; + + err = regmap_raw_write(priv->map_reg, MCP25XXFD_RAM_START, ram, + MCP25XXFD_RAM_SIZE); + kfree(ram); + + return err; +} + +static inline void mcp25xxfd_ecc_tefif_successful(struct mcp25xxfd_priv *priv) +{ + struct mcp25xxfd_ecc *ecc = &priv->ecc; + + ecc->ecc_stat = 0; +} + +static u8 mcp25xxfd_get_normal_mode(const struct mcp25xxfd_priv *priv) +{ + u8 mode; + + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) + mode = MCP25XXFD_REG_CON_MODE_MIXED; + else + mode = MCP25XXFD_REG_CON_MODE_CAN2_0; + + return mode; +} + +static int +__mcp25xxfd_chip_set_normal_mode(const struct mcp25xxfd_priv *priv, + bool nowait) +{ + u8 mode; + + mode = mcp25xxfd_get_normal_mode(priv); + + return __mcp25xxfd_chip_set_mode(priv, mode, nowait); +} + +static inline int +mcp25xxfd_chip_set_normal_mode(const struct mcp25xxfd_priv *priv) +{ + return __mcp25xxfd_chip_set_normal_mode(priv, false); +} + +static inline int +mcp25xxfd_chip_set_normal_mode_nowait(const struct mcp25xxfd_priv *priv) +{ + return __mcp25xxfd_chip_set_normal_mode(priv, true); +} + +static int mcp25xxfd_chip_interrupts_enable(const struct mcp25xxfd_priv *priv) +{ + u32 val; + int err; + + val = MCP25XXFD_REG_CRC_FERRIE | MCP25XXFD_REG_CRC_CRCERRIE; + err = regmap_write(priv->map_reg, MCP25XXFD_REG_CRC, val); + if (err) + return err; + + val = MCP25XXFD_REG_ECCCON_DEDIE | MCP25XXFD_REG_ECCCON_SECIE; + err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON, val, val); + if (err) + return err; + + val = MCP25XXFD_REG_INT_CERRIE | + MCP25XXFD_REG_INT_SERRIE | + MCP25XXFD_REG_INT_RXOVIE | + MCP25XXFD_REG_INT_TXATIE | + MCP25XXFD_REG_INT_SPICRCIE | + MCP25XXFD_REG_INT_ECCIE | + MCP25XXFD_REG_INT_TEFIE | + MCP25XXFD_REG_INT_MODIE | + MCP25XXFD_REG_INT_RXIE; + + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + val |= MCP25XXFD_REG_INT_IVMIE; + + return regmap_write(priv->map_reg, MCP25XXFD_REG_INT, val); +} + +static int mcp25xxfd_chip_interrupts_disable(const struct mcp25xxfd_priv *priv) +{ + int err; + u32 mask; + + err = regmap_write(priv->map_reg, MCP25XXFD_REG_INT, 0); + if (err) + return err; + + mask = MCP25XXFD_REG_ECCCON_DEDIE | MCP25XXFD_REG_ECCCON_SECIE; + err = regmap_update_bits(priv->map_reg, MCP25XXFD_REG_ECCCON, + mask, 0x0); + if (err) + return err; + + return regmap_write(priv->map_reg, MCP25XXFD_REG_CRC, 0); +} + +static int mcp25xxfd_chip_stop(struct mcp25xxfd_priv *priv, + const enum can_state state) +{ + priv->can.state = state; + + mcp25xxfd_chip_interrupts_disable(priv); + mcp25xxfd_chip_rx_int_disable(priv); + return mcp25xxfd_chip_set_mode(priv, MCP25XXFD_REG_CON_MODE_SLEEP); +} + +static int mcp25xxfd_chip_start(struct mcp25xxfd_priv *priv) +{ + int err; + + err = mcp25xxfd_chip_softreset(priv); + if (err) + goto out_chip_stop; + + err = mcp25xxfd_chip_clock_init(priv); + if (err) + goto out_chip_stop; + + err = mcp25xxfd_set_bittiming(priv); + if (err) + goto out_chip_stop; + + err = mcp25xxfd_chip_rx_int_enable(priv); + if (err) + return err; + + err = mcp25xxfd_chip_ecc_init(priv); + if (err) + goto out_chip_stop; + + mcp25xxfd_ring_init(priv); + + err = mcp25xxfd_chip_fifo_init(priv); + if (err) + goto out_chip_stop; + + priv->can.state = CAN_STATE_ERROR_ACTIVE; + + err = mcp25xxfd_chip_set_normal_mode(priv); + if (err) + goto out_chip_stop; + + return 0; + + out_chip_stop: + mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED); + + return err; +} + +static int mcp25xxfd_set_mode(struct net_device *ndev, enum can_mode mode) +{ + struct mcp25xxfd_priv *priv = netdev_priv(ndev); + int err; + + switch (mode) { + case CAN_MODE_START: + err = mcp25xxfd_chip_start(priv); + if (err) + return err; + + err = mcp25xxfd_chip_interrupts_enable(priv); + if (err) { + mcp25xxfd_chip_stop(priv, CAN_STATE_STOPPED); + return err; + } + + netif_wake_queue(ndev); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int __mcp25xxfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + const struct mcp25xxfd_priv *priv = netdev_priv(ndev); + u32 trec; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_TREC, &trec); + if (err) + return err; + + if (trec & MCP25XXFD_REG_TREC_TXBO) + bec->txerr = 256; + else + bec->txerr = FIELD_GET(MCP25XXFD_REG_TREC_TEC_MASK, trec); + bec->rxerr = FIELD_GET(MCP25XXFD_REG_TREC_REC_MASK, trec); + + return 0; +} + +static int mcp25xxfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + const struct mcp25xxfd_priv *priv = netdev_priv(ndev); + + /* Avoid waking up the controller if the interface is down */ + if (!(ndev->flags & IFF_UP)) + return 0; + + /* The controller is powered down during Bus Off, use saved + * bec values. + */ + if (priv->can.state == CAN_STATE_BUS_OFF) { + *bec = priv->bec; + return 0; + } + + return __mcp25xxfd_get_berr_counter(ndev, bec); +} + +static int mcp25xxfd_check_tef_tail(const struct mcp25xxfd_priv *priv) +{ + u8 tef_tail_chip, tef_tail; + int err; + + if (!IS_ENABLED(CONFIG_CAN_MCP25XXFD_SANITY)) + return 0; + + err = mcp25xxfd_tef_tail_get_from_chip(priv, &tef_tail_chip); + if (err) + return err; + + tef_tail = mcp25xxfd_get_tef_tail(priv); + if (tef_tail_chip != tef_tail) { + netdev_err(priv->ndev, + "TEF tail of chip (0x%02x) and ours (0x%08x) inconsistent.\n", + tef_tail_chip, tef_tail); + return -EILSEQ; + } + + return 0; +} + +static int +mcp25xxfd_check_rx_tail(const struct mcp25xxfd_priv *priv, + const struct mcp25xxfd_rx_ring *ring) +{ + u8 rx_tail_chip, rx_tail; + int err; + + if (!IS_ENABLED(CONFIG_CAN_MCP25XXFD_SANITY)) + return 0; + + err = mcp25xxfd_rx_tail_get_from_chip(priv, ring, &rx_tail_chip); + if (err) + return err; + + rx_tail = mcp25xxfd_get_rx_tail(ring); + if (rx_tail_chip != rx_tail) { + netdev_err(priv->ndev, + "RX tail of chip (%d) and ours (%d) inconsistent.\n", + rx_tail_chip, rx_tail); + return -EILSEQ; + } + + return 0; +} + +static int +mcp25xxfd_handle_tefif_recover(const struct mcp25xxfd_priv *priv, const u32 seq) +{ + const struct mcp25xxfd_tx_ring *tx_ring = priv->tx; + u32 tef_sta; + int err; + + err = regmap_read(priv->map_reg, MCP25XXFD_REG_TEFSTA, &tef_sta); + if (err) + return err; + + if (tef_sta & MCP25XXFD_REG_TEFSTA_TEFOVIF) { + netdev_err(priv->ndev, + "Transmit Event FIFO buffer overflow.\n"); + return -ENOBUFS; + } + < |