From 921ca574cd382142add8b12d0a7117f495510de5 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Sun, 6 Dec 2020 15:47:31 +0100 Subject: [PATCH 1/7] can: isotp: add SF_BROADCAST support for functional addressing When CAN_ISOTP_SF_BROADCAST is set in the CAN_ISOTP_OPTS flags the CAN_ISOTP socket is switched into functional addressing mode, where only single frame (SF) protocol data units can be send on the specified CAN interface and the given tp.tx_id after bind(). In opposite to normal and extended addressing this socket does not register a CAN-ID for reception which would be needed for a 1-to-1 ISOTP connection with a segmented bi-directional data transfer. Sending SFs on this socket is therefore a TX-only 'broadcast' operation. Signed-off-by: Oliver Hartkopp Signed-off-by: Thomas Wagner Link: https://lore.kernel.org/r/20201206144731.4609-1-socketcan@hartkopp.net Signed-off-by: Marc Kleine-Budde --- include/uapi/linux/can/isotp.h | 2 +- net/can/isotp.c | 42 +++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/include/uapi/linux/can/isotp.h b/include/uapi/linux/can/isotp.h index 7793b26aa154..c55935b64ccc 100644 --- a/include/uapi/linux/can/isotp.h +++ b/include/uapi/linux/can/isotp.h @@ -135,7 +135,7 @@ struct can_isotp_ll_options { #define CAN_ISOTP_FORCE_RXSTMIN 0x100 /* ignore CFs depending on rx stmin */ #define CAN_ISOTP_RX_EXT_ADDR 0x200 /* different rx extended addressing */ #define CAN_ISOTP_WAIT_TX_DONE 0x400 /* wait for tx completion */ - +#define CAN_ISOTP_SF_BROADCAST 0x800 /* 1-to-N functional addressing */ /* default values */ diff --git a/net/can/isotp.c b/net/can/isotp.c index d78ab13bd8be..09f781b63d66 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -865,6 +865,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) if (!size || size > MAX_MSG_LENGTH) return -EINVAL; + /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ + off = (so->tx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; + + /* does the given data fit into a single frame for SF_BROADCAST? */ + if ((so->opt.flags & CAN_ISOTP_SF_BROADCAST) && + (size > so->tx.ll_dl - SF_PCI_SZ4 - ae - off)) + return -EINVAL; + err = memcpy_from_msg(so->tx.buf, msg, size); if (err < 0) return err; @@ -891,9 +899,6 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf = (struct canfd_frame *)skb->data; skb_put(skb, so->ll.mtu); - /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ - off = (so->tx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; - /* check for single frame transmission depending on TX_DL */ if (size <= so->tx.ll_dl - SF_PCI_SZ4 - ae - off) { /* The message size generally fits into a SingleFrame - good. @@ -1016,7 +1021,7 @@ static int isotp_release(struct socket *sock) hrtimer_cancel(&so->rxtimer); /* remove current filters & unregister */ - if (so->bound) { + if (so->bound && (!(so->opt.flags & CAN_ISOTP_SF_BROADCAST))) { if (so->ifindex) { struct net_device *dev; @@ -1052,15 +1057,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) struct net_device *dev; int err = 0; int notify_enetdown = 0; + int do_rx_reg = 1; if (len < CAN_REQUIRED_SIZE(struct sockaddr_can, can_addr.tp)) return -EINVAL; - if (addr->can_addr.tp.rx_id == addr->can_addr.tp.tx_id) - return -EADDRNOTAVAIL; + /* do not register frame reception for functional addressing */ + if (so->opt.flags & CAN_ISOTP_SF_BROADCAST) + do_rx_reg = 0; - if ((addr->can_addr.tp.rx_id | addr->can_addr.tp.tx_id) & - (CAN_ERR_FLAG | CAN_RTR_FLAG)) + /* do not validate rx address for functional addressing */ + if (do_rx_reg) { + if (addr->can_addr.tp.rx_id == addr->can_addr.tp.tx_id) + return -EADDRNOTAVAIL; + + if (addr->can_addr.tp.rx_id & (CAN_ERR_FLAG | CAN_RTR_FLAG)) + return -EADDRNOTAVAIL; + } + + if (addr->can_addr.tp.tx_id & (CAN_ERR_FLAG | CAN_RTR_FLAG)) return -EADDRNOTAVAIL; if (!addr->can_ifindex) @@ -1093,13 +1108,14 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) ifindex = dev->ifindex; - can_rx_register(net, dev, addr->can_addr.tp.rx_id, - SINGLE_MASK(addr->can_addr.tp.rx_id), isotp_rcv, sk, - "isotp", sk); + if (do_rx_reg) + can_rx_register(net, dev, addr->can_addr.tp.rx_id, + SINGLE_MASK(addr->can_addr.tp.rx_id), + isotp_rcv, sk, "isotp", sk); dev_put(dev); - if (so->bound) { + if (so->bound && do_rx_reg) { /* unregister old filter */ if (so->ifindex) { dev = dev_get_by_index(net, so->ifindex); @@ -1299,7 +1315,7 @@ static int isotp_notifier(struct notifier_block *nb, unsigned long msg, case NETDEV_UNREGISTER: lock_sock(sk); /* remove current filters & unregister */ - if (so->bound) + if (so->bound && (!(so->opt.flags & CAN_ISOTP_SF_BROADCAST))) can_rx_unregister(dev_net(dev), dev, so->rxid, SINGLE_MASK(so->rxid), isotp_rcv, sk); From ecbaf5e13f45d8eebd3b772ba0032afcec1e519a Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Thu, 10 Dec 2020 09:53:21 +0100 Subject: [PATCH 2/7] can: rx-offload: can_rx_offload_offload_one(): avoid double unlikely() notation when using IS_ERR() The definition of IS_ERR() already applies the unlikely() notation when checking the error status of the passed pointer. For this reason there is no need to have the same notation outside of IS_ERR() itself. Clean up code by removing redundant notation. Signed-off-by: Antonio Quartulli Link: https://lore.kernel.org/r/20201210085321.18693-1-a@unstable.cc Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rx-offload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/rx-offload.c b/drivers/net/can/rx-offload.c index 450c5cfcb3fc..3c1912c0430b 100644 --- a/drivers/net/can/rx-offload.c +++ b/drivers/net/can/rx-offload.c @@ -157,7 +157,7 @@ can_rx_offload_offload_one(struct can_rx_offload *offload, unsigned int n) /* There was a problem reading the mailbox, propagate * error value. */ - if (unlikely(IS_ERR(skb))) { + if (IS_ERR(skb)) { offload->dev->stats.rx_dropped++; offload->dev->stats.rx_fifo_errors++; From c9f4cad6cdfe350ce2637e57f7f2aa7ff326bcc6 Mon Sep 17 00:00:00 2001 From: Sean Nyekjaer Date: Wed, 11 Dec 2019 07:32:27 +0100 Subject: [PATCH 3/7] can: m_can: m_can_config_endisable(): remove double clearing of clock stop request bit The CSR bit is already cleared when arriving here so remove this section of duplicate code. The registers set in m_can_config_endisable() is set to same exact values as before this patch. Signed-off-by: Sean Nyekjaer Acked-by: Sriram Dash Acked-by: Dan Murphy Link: https://lore.kernel.org/r/20191211063227.84259-1-sean@geanix.com Fixes: f524f829b75a ("can: m_can: Create a m_can platform framework") Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 05c978d1c53d..8f389df26afc 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -380,10 +380,6 @@ void m_can_config_endisable(struct m_can_classdev *cdev, bool enable) cccr &= ~CCCR_CSR; if (enable) { - /* Clear the Clock stop request if it was set */ - if (cccr & CCCR_CSR) - cccr &= ~CCCR_CSR; - /* enable m_can configuration */ m_can_write(cdev, M_CAN_CCCR, cccr | CCCR_INIT); udelay(5); From 227619c3ff7c71eab76848b3b2add06838f5b6e6 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 23 Oct 2020 14:58:00 +0300 Subject: [PATCH 4/7] can: m_can: move runtime PM enable/disable to m_can_platform This is a preparatory patch for upcoming PCI based M_CAN devices. The current PM implementation would cause PCI based drivers to enable PM twice, once when the PCI device is added and a second time in m_can_class_register(). This will cause 'Unbalanced pm_runtime_enable!' to be logged, and is a situation that should be avoided. Therefore, in anticipation of PCI devices, move PM enabling out from M_CAN class registration to its only user, the m_can_platform driver. Signed-off-by: Patrik Flykt Link: https://lore.kernel.org/r/20201023115800.46538-2-patrik.flykt@linux.intel.com [mkl: m_can_plat_probe(): fix error handling m_can_class_register(): simplify error handling] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 8 +------- drivers/net/can/m_can/m_can_platform.c | 9 ++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 8f389df26afc..06c136961c7c 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1826,10 +1826,9 @@ int m_can_class_register(struct m_can_classdev *m_can_dev) int ret; if (m_can_dev->pm_clock_support) { - pm_runtime_enable(m_can_dev->dev); ret = m_can_clk_start(m_can_dev); if (ret) - goto pm_runtime_fail; + return ret; } ret = m_can_dev_setup(m_can_dev); @@ -1855,11 +1854,6 @@ int m_can_class_register(struct m_can_classdev *m_can_dev) */ clk_disable: m_can_clk_stop(m_can_dev); -pm_runtime_fail: - if (ret) { - if (m_can_dev->pm_clock_support) - pm_runtime_disable(m_can_dev->dev); - } return ret; } diff --git a/drivers/net/can/m_can/m_can_platform.c b/drivers/net/can/m_can/m_can_platform.c index c45a889a1afd..36ef791da388 100644 --- a/drivers/net/can/m_can/m_can_platform.c +++ b/drivers/net/can/m_can/m_can_platform.c @@ -115,8 +115,15 @@ static int m_can_plat_probe(struct platform_device *pdev) m_can_init_ram(mcan_class); - return m_can_class_register(mcan_class); + pm_runtime_enable(mcan_class->dev); + ret = m_can_class_register(mcan_class); + if (ret) + goto out_runtime_disable; + return ret; + +out_runtime_disable: + pm_runtime_disable(mcan_class->dev); probe_fail: m_can_class_free_dev(mcan_class->net); return ret; From cab7ffc0324f053c8fb56c821cdd63dc0383270d Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Tue, 17 Nov 2020 18:08:27 +0200 Subject: [PATCH 5/7] can: m_can: add PCI glue driver for Intel Elkhart Lake Add support for M_CAN controller on Intel Elkhart Lake attached to the PCI bus. It integrates the Bosch M_CAN controller with Message RAM and the wrapper IP block with additional registers which all of them are within the same MMIO range. Currently only interrupt control register from wrapper IP is used and the MRAM configuration is expected to come from the firmware via "bosch,mram-cfg" device property and parsed by m_can.c core. Initial implementation is done by Felipe Balbi while he was working at Intel with later changes from Raymond Tan and me. Co-developed-by: Felipe Balbi (Intel) Co-developed-by: Raymond Tan Signed-off-by: Felipe Balbi (Intel) Signed-off-by: Raymond Tan Signed-off-by: Jarkko Nikula Link: https://lore.kernel.org/r/20201117160827.3636264-1-jarkko.nikula@linux.intel.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/Kconfig | 7 ++ drivers/net/can/m_can/Makefile | 1 + drivers/net/can/m_can/m_can_pci.c | 186 ++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 drivers/net/can/m_can/m_can_pci.c diff --git a/drivers/net/can/m_can/Kconfig b/drivers/net/can/m_can/Kconfig index e3eb69b76cf5..45ad1b3f0cd0 100644 --- a/drivers/net/can/m_can/Kconfig +++ b/drivers/net/can/m_can/Kconfig @@ -7,6 +7,13 @@ menuconfig CAN_M_CAN if CAN_M_CAN +config CAN_M_CAN_PCI + tristate "Generic PCI Bus based M_CAN driver" + depends on PCI + help + Say Y here if you want to support Bosch M_CAN controller connected + to the pci bus. + config CAN_M_CAN_PLATFORM tristate "Bosch M_CAN support for io-mapped devices" depends on HAS_IOMEM diff --git a/drivers/net/can/m_can/Makefile b/drivers/net/can/m_can/Makefile index 52a4a6fbe527..ef7963ff2006 100644 --- a/drivers/net/can/m_can/Makefile +++ b/drivers/net/can/m_can/Makefile @@ -4,5 +4,6 @@ # obj-$(CONFIG_CAN_M_CAN) += m_can.o +obj-$(CONFIG_CAN_M_CAN_PCI) += m_can_pci.o obj-$(CONFIG_CAN_M_CAN_PLATFORM) += m_can_platform.o obj-$(CONFIG_CAN_M_CAN_TCAN4X5X) += tcan4x5x.o diff --git a/drivers/net/can/m_can/m_can_pci.c b/drivers/net/can/m_can/m_can_pci.c new file mode 100644 index 000000000000..04010ee0407c --- /dev/null +++ b/drivers/net/can/m_can/m_can_pci.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCI Specific M_CAN Glue + * + * Copyright (C) 2018-2020 Intel Corporation + * Author: Felipe Balbi (Intel) + * Author: Jarkko Nikula + * Author: Raymond Tan + */ + +#include +#include +#include +#include +#include + +#include "m_can.h" + +#define M_CAN_PCI_MMIO_BAR 0 + +#define M_CAN_CLOCK_FREQ_EHL 100000000 +#define CTL_CSR_INT_CTL_OFFSET 0x508 + +struct m_can_pci_priv { + void __iomem *base; +}; + +static u32 iomap_read_reg(struct m_can_classdev *cdev, int reg) +{ + struct m_can_pci_priv *priv = cdev->device_data; + + return readl(priv->base + reg); +} + +static u32 iomap_read_fifo(struct m_can_classdev *cdev, int offset) +{ + struct m_can_pci_priv *priv = cdev->device_data; + + return readl(priv->base + offset); +} + +static int iomap_write_reg(struct m_can_classdev *cdev, int reg, int val) +{ + struct m_can_pci_priv *priv = cdev->device_data; + + writel(val, priv->base + reg); + + return 0; +} + +static int iomap_write_fifo(struct m_can_classdev *cdev, int offset, int val) +{ + struct m_can_pci_priv *priv = cdev->device_data; + + writel(val, priv->base + offset); + + return 0; +} + +static struct m_can_ops m_can_pci_ops = { + .read_reg = iomap_read_reg, + .write_reg = iomap_write_reg, + .write_fifo = iomap_write_fifo, + .read_fifo = iomap_read_fifo, +}; + +static int m_can_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct device *dev = &pci->dev; + struct m_can_classdev *mcan_class; + struct m_can_pci_priv *priv; + void __iomem *base; + int ret; + + ret = pcim_enable_device(pci); + if (ret) + return ret; + + pci_set_master(pci); + + ret = pcim_iomap_regions(pci, BIT(M_CAN_PCI_MMIO_BAR), pci_name(pci)); + if (ret) + return ret; + + base = pcim_iomap_table(pci)[M_CAN_PCI_MMIO_BAR]; + + if (!base) { + dev_err(dev, "failed to map BARs\n"); + return -ENOMEM; + } + + priv = devm_kzalloc(&pci->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mcan_class = m_can_class_allocate_dev(&pci->dev); + if (!mcan_class) + return -ENOMEM; + + priv->base = base; + + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) + return ret; + + mcan_class->device_data = priv; + mcan_class->dev = &pci->dev; + mcan_class->net->irq = pci_irq_vector(pci, 0); + mcan_class->pm_clock_support = 1; + mcan_class->can.clock.freq = id->driver_data; + mcan_class->ops = &m_can_pci_ops; + + pci_set_drvdata(pci, mcan_class->net); + + ret = m_can_class_register(mcan_class); + if (ret) + goto err; + + /* Enable interrupt control at CAN wrapper IP */ + writel(0x1, base + CTL_CSR_INT_CTL_OFFSET); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + + return 0; + +err: + pci_free_irq_vectors(pci); + return ret; +} + +static void m_can_pci_remove(struct pci_dev *pci) +{ + struct net_device *dev = pci_get_drvdata(pci); + struct m_can_classdev *mcan_class = netdev_priv(dev); + struct m_can_pci_priv *priv = mcan_class->device_data; + + pm_runtime_forbid(&pci->dev); + pm_runtime_get_noresume(&pci->dev); + + /* Disable interrupt control at CAN wrapper IP */ + writel(0x0, priv->base + CTL_CSR_INT_CTL_OFFSET); + + m_can_class_unregister(mcan_class); + pci_free_irq_vectors(pci); +} + +static __maybe_unused int m_can_pci_suspend(struct device *dev) +{ + return m_can_class_suspend(dev); +} + +static __maybe_unused int m_can_pci_resume(struct device *dev) +{ + return m_can_class_resume(dev); +} + +static SIMPLE_DEV_PM_OPS(m_can_pci_pm_ops, + m_can_pci_suspend, m_can_pci_resume); + +static const struct pci_device_id m_can_pci_id_table[] = { + { PCI_VDEVICE(INTEL, 0x4bc1), M_CAN_CLOCK_FREQ_EHL, }, + { PCI_VDEVICE(INTEL, 0x4bc2), M_CAN_CLOCK_FREQ_EHL, }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, m_can_pci_id_table); + +static struct pci_driver m_can_pci_driver = { + .name = "m_can_pci", + .probe = m_can_pci_probe, + .remove = m_can_pci_remove, + .id_table = m_can_pci_id_table, + .driver = { + .pm = &m_can_pci_pm_ops, + }, +}; + +module_pci_driver(m_can_pci_driver); + +MODULE_AUTHOR("Felipe Balbi (Intel)"); +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_AUTHOR("Raymond Tan "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CAN bus driver for Bosch M_CAN controller on PCI bus"); From 2c0ac9208135c75b9afec67b2819329ff41a5355 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Sat, 28 Nov 2020 10:28:55 -0300 Subject: [PATCH 6/7] can: flexcan: convert the driver to DT-only The flexcan driver runs only on DT platforms, so simplify the code by using of_device_get_match_data() to retrieve the driver data and also by removing the unused id_table. Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20201128132855.7724-1-festevam@gmail.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/flexcan.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index e85f20d18d67..038fe1036df2 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -1940,15 +1940,8 @@ static const struct of_device_id flexcan_of_match[] = { }; MODULE_DEVICE_TABLE(of, flexcan_of_match); -static const struct platform_device_id flexcan_id_table[] = { - { .name = "flexcan", .driver_data = (kernel_ulong_t)&fsl_p1010_devtype_data, }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(platform, flexcan_id_table); - static int flexcan_probe(struct platform_device *pdev) { - const struct of_device_id *of_id; const struct flexcan_devtype_data *devtype_data; struct net_device *dev; struct flexcan_priv *priv; @@ -1997,15 +1990,7 @@ static int flexcan_probe(struct platform_device *pdev) if (IS_ERR(regs)) return PTR_ERR(regs); - of_id = of_match_device(flexcan_of_match, &pdev->dev); - if (of_id) { - devtype_data = of_id->data; - } else if (platform_get_device_id(pdev)->driver_data) { - devtype_data = (struct flexcan_devtype_data *) - platform_get_device_id(pdev)->driver_data; - } else { - return -ENODEV; - } + devtype_data = of_device_get_match_data(&pdev->dev); if ((devtype_data->quirks & FLEXCAN_QUIRK_SUPPORT_FD) && !(devtype_data->quirks & FLEXCAN_QUIRK_USE_OFF_TIMESTAMP)) { @@ -2235,7 +2220,6 @@ static struct platform_driver flexcan_driver = { }, .probe = flexcan_probe, .remove = flexcan_remove, - .id_table = flexcan_id_table, }; module_platform_driver(flexcan_driver); From ee42bedc85a6e87791d5c20da6f2d150188cde54 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 1 Dec 2020 11:10:19 +0530 Subject: [PATCH 7/7] can: mcp251xfd: Add support for internal loopback mode MCP251xFD supports internal loopback mode which can be used to verify CAN functionality in the absence of a real CAN device. Link: https://lore.kernel.org/r/20201201054019.11012-1-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam [mkl: mcp251xfd_get_normal_mode(): move CAN_CTRLMODE_LOOPBACK check to front] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c index 20cbd5c446f5..77129d5f410b 100644 --- a/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c +++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-core.c @@ -965,7 +965,10 @@ static u8 mcp251xfd_get_normal_mode(const struct mcp251xfd_priv *priv) { u8 mode; - if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + mode = MCP251XFD_REG_CON_MODE_INT_LOOPBACK; + else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) mode = MCP251XFD_REG_CON_MODE_LISTENONLY; else if (priv->can.ctrlmode & CAN_CTRLMODE_FD) mode = MCP251XFD_REG_CON_MODE_MIXED; @@ -2881,9 +2884,9 @@ static int mcp251xfd_probe(struct spi_device *spi) priv->can.do_get_berr_counter = mcp251xfd_get_berr_counter; priv->can.bittiming_const = &mcp251xfd_bittiming_const; priv->can.data_bittiming_const = &mcp251xfd_data_bittiming_const; - priv->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | - CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD | - CAN_CTRLMODE_FD_NON_ISO; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING | + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO; priv->ndev = ndev; priv->spi = spi; priv->rx_int = rx_int;