From 66e75b2758d69dce908fe98da72c345fa18a46f4 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Wed, 26 Nov 2025 11:16:02 +0100 Subject: [PATCH 01/27] can: dev: can_get_ctrlmode_str: use capitalized ctrlmode strings Unify the ctrlmode related strings to the command line options of the 'ip' tool from the iproute2 package. The capitalized strings are also shown when the detailed interface configuration is printed by 'ip'. Suggested-by: Stephane Grosjean Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-1-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index 80e1ab18de87..b392483e4499 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -92,29 +92,29 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) { switch (ctrlmode & ~(ctrlmode - 1)) { case 0: - return "none"; + return "(none)"; case CAN_CTRLMODE_LOOPBACK: - return "loopback"; + return "LOOPBACK"; case CAN_CTRLMODE_LISTENONLY: - return "listen-only"; + return "LISTEN-ONLY"; case CAN_CTRLMODE_3_SAMPLES: - return "triple-sampling"; + return "TRIPLE-SAMPLING"; case CAN_CTRLMODE_ONE_SHOT: - return "one-shot"; + return "ONE-SHOT"; case CAN_CTRLMODE_BERR_REPORTING: - return "berr-reporting"; + return "BERR-REPORTING"; case CAN_CTRLMODE_FD: - return "fd"; + return "FD"; case CAN_CTRLMODE_PRESUME_ACK: - return "presume-ack"; + return "PRESUME-ACK"; case CAN_CTRLMODE_FD_NON_ISO: - return "fd-non-iso"; + return "FD-NON-ISO"; case CAN_CTRLMODE_CC_LEN8_DLC: - return "cc-len8-dlc"; + return "CC-LEN8-DLC"; case CAN_CTRLMODE_TDC_AUTO: - return "fd-tdc-auto"; + return "TDC-AUTO"; case CAN_CTRLMODE_TDC_MANUAL: - return "fd-tdc-manual"; + return "TDC-MANUAL"; default: return ""; } From 585a4f22c4f9d85e32d42be65e67c232e82e5b3a Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:03 +0100 Subject: [PATCH 02/27] can: bittiming: apply NL_SET_ERR_MSG() to can_calc_bittiming() When CONFIG_CAN_CALC_BITTIMING is disabled, the can_calc_bittiming() functions can not be used and the user needs to provide all the bittiming parameters. Currently, can_calc_bittiming() prints an error message to the kernel log. Instead use NL_SET_ERR_MSG() to make it return the error message through the netlink interface so that the user can directly see it. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-2-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- include/linux/can/bittiming.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index d30816dd93c7..3926c78b2222 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -141,7 +141,7 @@ static inline int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, const struct can_bittiming_const *btc, struct netlink_ext_ack *extack) { - netdev_err(dev, "bit-timing calculation not available\n"); + NL_SET_ERR_MSG(extack, "bit-timing calculation not available\n"); return -EINVAL; } From d037d05c2e32792a6fa572b0aa3c92a8ac78589d Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:04 +0100 Subject: [PATCH 03/27] can: dev: can_dev_dropped_skb: drop CAN FD skbs if FD is off Currently, the CAN FD skb validation logic is based on the MTU: the interface is deemed FD capable if and only if its MTU is greater or equal to CANFD_MTU. This logic is showing its limit with the introduction of CAN XL. For example, consider the two scenarios below: 1. An interface configured with CAN FD on and CAN XL on 2. An interface configured with CAN FD off and CAN XL on In those two scenarios, the interfaces would have the same MTU: CANXL_MTU making it impossible to differentiate which one has CAN FD turned on and which one has it off. Because of the limitation, the only non-UAPI-breaking workaround is to do the check at the device level using the can_priv->ctrlmode flags. Unfortunately, the virtual interfaces (vcan, vxcan), which do not have a can_priv, are left behind. Add a check on the CAN_CTRLMODE_FD flag in can_dev_dropped_skb() and drop FD frames whenever the feature is turned off. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-3-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- include/linux/can/dev.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index bd7410b5d8a6..a7a39a6101d9 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -103,12 +103,20 @@ static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *s if (priv->ctrlmode & CAN_CTRLMODE_LISTENONLY) { netdev_info_once(dev, "interface in listen only mode, dropping skb\n"); - kfree_skb(skb); - dev->stats.tx_dropped++; - return true; + goto invalid_skb; + } + + if (!(priv->ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { + netdev_info_once(dev, "CAN FD is disabled, dropping skb\n"); + goto invalid_skb; } return can_dropped_invalid_skb(dev, skb); + +invalid_skb: + kfree_skb(skb); + dev->stats.tx_dropped++; + return true; } void can_setup(struct net_device *dev); From 60f511f443e552ef5b5cd79ec2b881f4323e19c9 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:05 +0100 Subject: [PATCH 04/27] can: netlink: add CAN_CTRLMODE_RESTRICTED ISO 11898-1:2024 adds a new restricted operation mode. This mode is added as a mandatory feature for nodes which support CAN XL and is retrofitted as optional for legacy nodes (i.e. the ones which only support Classical CAN and CAN FD). The restricted operation mode is nearly the same as the listen only mode: the node can not send data frames or remote frames and can not send dominant bits if an error occurs. The only exception is that the node shall still send the acknowledgment bit. A second niche exception is that the node may still send a data frame containing a time reference message if the node is a primary time provider, but because the time provider feature is not yet implemented in the kernel, this second exception is not relevant to us at the moment. Add the CAN_CTRLMODE_RESTRICTED control mode flag and update the can_dev_dropped_skb() helper function accordingly. Finally, bail out if both CAN_CTRLMODE_LISTENONLY and CAN_CTRLMODE_RESTRICTED are provided. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-4-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 2 ++ drivers/net/can/dev/netlink.c | 7 +++++ include/linux/can/dev.h | 50 +++++++++++++++++--------------- include/uapi/linux/can/netlink.h | 1 + 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index b392483e4499..b6980d32e5b4 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -115,6 +115,8 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "TDC-AUTO"; case CAN_CTRLMODE_TDC_MANUAL: return "TDC-MANUAL"; + case CAN_CTRLMODE_RESTRICTED: + return "RESTRICTED"; default: return ""; } diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index 6f83b87d54fc..87e731527dd7 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -188,6 +188,13 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], struct can_ctrlmode *cm = nla_data(data[IFLA_CAN_CTRLMODE]); flags = cm->flags & cm->mask; + + if ((flags & CAN_CTRLMODE_LISTENONLY) && + (flags & CAN_CTRLMODE_RESTRICTED)) { + NL_SET_ERR_MSG(extack, + "LISTEN-ONLY and RESTRICTED modes are mutually exclusive"); + return -EOPNOTSUPP; + } } err = can_validate_bittiming(data, extack, IFLA_CAN_BITTIMING); diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index a7a39a6101d9..ab11c0e9111b 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -95,30 +95,6 @@ static inline bool can_is_canxl_dev_mtu(unsigned int mtu) return (mtu >= CANXL_MIN_MTU && mtu <= CANXL_MAX_MTU); } -/* drop skb if it does not contain a valid CAN frame for sending */ -static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *skb) -{ - struct can_priv *priv = netdev_priv(dev); - - if (priv->ctrlmode & CAN_CTRLMODE_LISTENONLY) { - netdev_info_once(dev, - "interface in listen only mode, dropping skb\n"); - goto invalid_skb; - } - - if (!(priv->ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { - netdev_info_once(dev, "CAN FD is disabled, dropping skb\n"); - goto invalid_skb; - } - - return can_dropped_invalid_skb(dev, skb); - -invalid_skb: - kfree_skb(skb); - dev->stats.tx_dropped++; - return true; -} - void can_setup(struct net_device *dev); struct net_device *alloc_candev_mqs(int sizeof_priv, unsigned int echo_skb_max, @@ -154,6 +130,32 @@ void can_bus_off(struct net_device *dev); const char *can_get_state_str(const enum can_state state); const char *can_get_ctrlmode_str(u32 ctrlmode); +/* drop skb if it does not contain a valid CAN frame for sending */ +static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *skb) +{ + struct can_priv *priv = netdev_priv(dev); + u32 silent_mode = priv->ctrlmode & (CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_RESTRICTED); + + if (silent_mode) { + netdev_info_once(dev, "interface in %s mode, dropping skb\n", + can_get_ctrlmode_str(silent_mode)); + goto invalid_skb; + } + + if (!(priv->ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { + netdev_info_once(dev, "CAN FD is disabled, dropping skb\n"); + goto invalid_skb; + } + + return can_dropped_invalid_skb(dev, skb); + +invalid_skb: + kfree_skb(skb); + dev->stats.tx_dropped++; + return true; +} + void can_state_get_by_berr_counter(const struct net_device *dev, const struct can_berr_counter *bec, enum can_state *tx_state, diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index ef62f56eaaef..fafd1cce4798 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -103,6 +103,7 @@ struct can_ctrlmode { #define CAN_CTRLMODE_CC_LEN8_DLC 0x100 /* Classic CAN DLC option */ #define CAN_CTRLMODE_TDC_AUTO 0x200 /* FD transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_TDC_MANUAL 0x400 /* FD TDCV is manually set up by user */ +#define CAN_CTRLMODE_RESTRICTED 0x800 /* Restricted operation mode */ /* * CAN device statistics From e63281614747c73f25b708c75bc696c4e76f5588 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:06 +0100 Subject: [PATCH 05/27] can: netlink: add initial CAN XL support CAN XL uses bittiming parameters different from Classical CAN and CAN FD. Thus, all the data bittiming parameters, including TDC, need to be duplicated for CAN XL. Add the CAN XL netlink interface for all the features which are common with CAN FD. Any new CAN XL specific features are added later on. The first time CAN XL is activated, the MTU is set by default to CANXL_MAX_MTU. The user may then configure a custom MTU within the CANXL_MIN_MTU to CANXL_MAX_MTU range, in which case, the custom MTU value will be kept as long as CAN XL remains active. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-5-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 14 +++++- drivers/net/can/dev/netlink.c | 76 +++++++++++++++++++++++++------- include/linux/can/bittiming.h | 6 ++- include/linux/can/dev.h | 7 ++- include/uapi/linux/can/netlink.h | 7 +++ 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index b6980d32e5b4..bdec2c52c8ec 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -117,6 +117,12 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "TDC-MANUAL"; case CAN_CTRLMODE_RESTRICTED: return "RESTRICTED"; + case CAN_CTRLMODE_XL: + return "XL"; + case CAN_CTRLMODE_XL_TDC_AUTO: + return "XL-TDC-AUTO"; + case CAN_CTRLMODE_XL_TDC_MANUAL: + return "XL-TDC-MANUAL"; default: return ""; } @@ -350,7 +356,13 @@ void can_set_default_mtu(struct net_device *dev) { struct can_priv *priv = netdev_priv(dev); - if (priv->ctrlmode & CAN_CTRLMODE_FD) { + if (priv->ctrlmode & CAN_CTRLMODE_XL) { + if (can_is_canxl_dev_mtu(dev->mtu)) + return; + dev->mtu = CANXL_MTU; + dev->min_mtu = CANXL_MIN_MTU; + dev->max_mtu = CANXL_MAX_MTU; + } else if (priv->ctrlmode & CAN_CTRLMODE_FD) { dev->mtu = CANFD_MTU; dev->min_mtu = CANFD_MTU; dev->max_mtu = CANFD_MTU; diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index 87e731527dd7..fdd1fa7cf93a 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -2,7 +2,7 @@ /* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix * Copyright (C) 2006 Andrey Volkov, Varma Electronics * Copyright (C) 2008-2009 Wolfgang Grandegger - * Copyright (C) 2021 Vincent Mailhol + * Copyright (C) 2021-2025 Vincent Mailhol */ #include @@ -22,6 +22,9 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + 1] = { [IFLA_CAN_TERMINATION] = { .type = NLA_U16 }, [IFLA_CAN_TDC] = { .type = NLA_NESTED }, [IFLA_CAN_CTRLMODE_EXT] = { .type = NLA_NESTED }, + [IFLA_CAN_XL_DATA_BITTIMING] = { .len = sizeof(struct can_bittiming) }, + [IFLA_CAN_XL_DATA_BITTIMING_CONST] = { .len = sizeof(struct can_bittiming_const) }, + [IFLA_CAN_XL_TDC] = { .type = NLA_NESTED }, }; static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { @@ -70,7 +73,7 @@ static int can_validate_tdc(struct nlattr *data_tdc, return -EOPNOTSUPP; } - /* If one of the CAN_CTRLMODE_TDC_* flag is set then TDC + /* If one of the CAN_CTRLMODE_{,XL}_TDC_* flags is set then TDC * must be set and vice-versa */ if ((tdc_auto || tdc_manual) && !data_tdc) { @@ -82,8 +85,8 @@ static int can_validate_tdc(struct nlattr *data_tdc, return -EOPNOTSUPP; } - /* If providing TDC parameters, at least TDCO is needed. TDCV - * is needed if and only if CAN_CTRLMODE_TDC_MANUAL is set + /* If providing TDC parameters, at least TDCO is needed. TDCV is + * needed if and only if CAN_CTRLMODE_{,XL}_TDC_MANUAL is set */ if (data_tdc) { struct nlattr *tb_tdc[IFLA_CAN_TDC_MAX + 1]; @@ -126,10 +129,10 @@ static int can_validate_databittiming(struct nlattr *data[], bool is_on; int err; - /* Make sure that valid CAN FD configurations always consist of + /* Make sure that valid CAN FD/XL configurations always consist of * - nominal/arbitration bittiming * - data bittiming - * - control mode with CAN_CTRLMODE_FD set + * - control mode with CAN_CTRLMODE_{FD,XL} set * - TDC parameters are coherent (details in can_validate_tdc()) */ @@ -139,7 +142,10 @@ static int can_validate_databittiming(struct nlattr *data[], is_on = flags & CAN_CTRLMODE_FD; type = "FD"; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + data_tdc = data[IFLA_CAN_XL_TDC]; + tdc_flags = flags & CAN_CTRLMODE_XL_TDC_MASK; + is_on = flags & CAN_CTRLMODE_XL; + type = "XL"; } if (is_on) { @@ -206,6 +212,11 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], if (err) return err; + err = can_validate_databittiming(data, extack, + IFLA_CAN_XL_DATA_BITTIMING, flags); + if (err) + return err; + return 0; } @@ -251,18 +262,26 @@ static int can_ctrlmode_changelink(struct net_device *dev, /* If a top dependency flag is provided, reset all its dependencies */ if (cm->mask & CAN_CTRLMODE_FD) priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; + if (cm->mask & CAN_CTRLMODE_XL) + priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK); /* clear bits to be modified and copy the flag values */ priv->ctrlmode &= ~cm->mask; priv->ctrlmode |= maskedflags; - /* Wipe potential leftovers from previous CAN FD config */ + /* Wipe potential leftovers from previous CAN FD/XL config */ if (!(priv->ctrlmode & CAN_CTRLMODE_FD)) { memset(&priv->fd.data_bittiming, 0, sizeof(priv->fd.data_bittiming)); priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; memset(&priv->fd.tdc, 0, sizeof(priv->fd.tdc)); } + if (!(priv->ctrlmode & CAN_CTRLMODE_XL)) { + memset(&priv->xl.data_bittiming, 0, + sizeof(priv->fd.data_bittiming)); + priv->ctrlmode &= ~CAN_CTRLMODE_XL_TDC_MASK; + memset(&priv->xl.tdc, 0, sizeof(priv->xl.tdc)); + } can_set_default_mtu(dev); @@ -337,7 +356,10 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], dbt_params = &priv->fd; tdc_mask = CAN_CTRLMODE_FD_TDC_MASK; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + data_bittiming = data[IFLA_CAN_XL_DATA_BITTIMING]; + data_tdc = data[IFLA_CAN_XL_TDC]; + dbt_params = &priv->xl; + tdc_mask = CAN_CTRLMODE_XL_TDC_MASK; } if (!data_bittiming) @@ -388,7 +410,7 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], */ can_calc_tdco(&dbt_params->tdc, dbt_params->tdc_const, &dbt, tdc_mask, &priv->ctrlmode, priv->ctrlmode_supported); - } /* else: both CAN_CTRLMODE_TDC_{AUTO,MANUAL} are explicitly + } /* else: both CAN_CTRLMODE_{,XL}_TDC_{AUTO,MANUAL} are explicitly * turned off. TDC is disabled: do nothing */ @@ -493,6 +515,11 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], if (err) return err; + /* CAN XL */ + err = can_dbt_changelink(dev, data, false, extack); + if (err) + return err; + if (data[IFLA_CAN_TERMINATION]) { const u16 termval = nla_get_u16(data[IFLA_CAN_TERMINATION]); const unsigned int num_term = priv->termination_const_cnt; @@ -560,14 +587,14 @@ static size_t can_data_bittiming_get_size(struct data_bittiming_params *dbt_para { size_t size = 0; - if (dbt_params->data_bittiming.bitrate) /* IFLA_CAN_DATA_BITTIMING */ + if (dbt_params->data_bittiming.bitrate) /* IFLA_CAN_{,XL}_DATA_BITTIMING */ size += nla_total_size(sizeof(dbt_params->data_bittiming)); - if (dbt_params->data_bittiming_const) /* IFLA_CAN_DATA_BITTIMING_CONST */ + if (dbt_params->data_bittiming_const) /* IFLA_CAN_{,XL}_DATA_BITTIMING_CONST */ size += nla_total_size(sizeof(*dbt_params->data_bittiming_const)); - if (dbt_params->data_bitrate_const) /* IFLA_CAN_DATA_BITRATE_CONST */ + if (dbt_params->data_bitrate_const) /* IFLA_CAN_{,XL}_DATA_BITRATE_CONST */ size += nla_total_size(sizeof(*dbt_params->data_bitrate_const) * dbt_params->data_bitrate_const_cnt); - size += can_tdc_get_size(dbt_params, tdc_flags);/* IFLA_CAN_TDC */ + size += can_tdc_get_size(dbt_params, tdc_flags);/* IFLA_CAN_{,XL}_TDC */ return size; } @@ -607,6 +634,9 @@ static size_t can_get_size(const struct net_device *dev) size += can_data_bittiming_get_size(&priv->fd, priv->ctrlmode & CAN_CTRLMODE_FD_TDC_MASK); + size += can_data_bittiming_get_size(&priv->xl, + priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); + return size; } @@ -651,7 +681,9 @@ static int can_tdc_fill_info(struct sk_buff *skb, const struct net_device *dev, tdc_is_enabled = can_fd_tdc_is_enabled(priv); tdc_manual = priv->ctrlmode & CAN_CTRLMODE_TDC_MANUAL; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + dbt_params = &priv->xl; + tdc_is_enabled = can_xl_tdc_is_enabled(priv); + tdc_manual = priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MANUAL; } tdc_const = dbt_params->tdc_const; tdc = &dbt_params->tdc; @@ -773,7 +805,19 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) can_tdc_fill_info(skb, dev, IFLA_CAN_TDC) || - can_ctrlmode_ext_fill_info(skb, priv) + can_ctrlmode_ext_fill_info(skb, priv) || + + can_bittiming_fill_info(skb, IFLA_CAN_XL_DATA_BITTIMING, + &priv->xl.data_bittiming) || + + can_bittiming_const_fill_info(skb, IFLA_CAN_XL_DATA_BITTIMING_CONST, + priv->xl.data_bittiming_const) || + + can_bitrate_const_fill_info(skb, IFLA_CAN_XL_DATA_BITRATE_CONST, + priv->xl.data_bitrate_const, + priv->xl.data_bitrate_const_cnt) || + + can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) ) return -EMSGSIZE; diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index 3926c78b2222..b6cd2476ffd7 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -16,10 +16,12 @@ #define CAN_CTRLMODE_FD_TDC_MASK \ (CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_TDC_MANUAL) +#define CAN_CTRLMODE_XL_TDC_MASK \ + (CAN_CTRLMODE_XL_TDC_AUTO | CAN_CTRLMODE_XL_TDC_MANUAL) #define CAN_CTRLMODE_TDC_AUTO_MASK \ - (CAN_CTRLMODE_TDC_AUTO) + (CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_XL_TDC_AUTO) #define CAN_CTRLMODE_TDC_MANUAL_MASK \ - (CAN_CTRLMODE_TDC_MANUAL) + (CAN_CTRLMODE_TDC_MANUAL | CAN_CTRLMODE_XL_TDC_MANUAL) /* * struct can_tdc - CAN FD Transmission Delay Compensation parameters diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index ab11c0e9111b..f15879bd818d 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -47,7 +47,7 @@ struct can_priv { const struct can_bittiming_const *bittiming_const; struct can_bittiming bittiming; - struct data_bittiming_params fd; + struct data_bittiming_params fd, xl; unsigned int bitrate_const_cnt; const u32 *bitrate_const; u32 bitrate_max; @@ -85,6 +85,11 @@ static inline bool can_fd_tdc_is_enabled(const struct can_priv *priv) return !!(priv->ctrlmode & CAN_CTRLMODE_FD_TDC_MASK); } +static inline bool can_xl_tdc_is_enabled(const struct can_priv *priv) +{ + return !!(priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); +} + static inline u32 can_get_static_ctrlmode(struct can_priv *priv) { return priv->ctrlmode & ~priv->ctrlmode_supported; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index fafd1cce4798..c2c96c5978a8 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -104,6 +104,9 @@ struct can_ctrlmode { #define CAN_CTRLMODE_TDC_AUTO 0x200 /* FD transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_TDC_MANUAL 0x400 /* FD TDCV is manually set up by user */ #define CAN_CTRLMODE_RESTRICTED 0x800 /* Restricted operation mode */ +#define CAN_CTRLMODE_XL 0x1000 /* CAN XL mode */ +#define CAN_CTRLMODE_XL_TDC_AUTO 0x2000 /* XL transceiver automatically calculates TDCV */ +#define CAN_CTRLMODE_XL_TDC_MANUAL 0x4000 /* XL TDCV is manually set up by user */ /* * CAN device statistics @@ -139,6 +142,10 @@ enum { IFLA_CAN_BITRATE_MAX, IFLA_CAN_TDC, /* FD */ IFLA_CAN_CTRLMODE_EXT, + IFLA_CAN_XL_DATA_BITTIMING, + IFLA_CAN_XL_DATA_BITTIMING_CONST, + IFLA_CAN_XL_DATA_BITRATE_CONST, + IFLA_CAN_XL_TDC, /* add new constants above here */ __IFLA_CAN_MAX, From 233134af208689c2d5d40896f5740473a74e3cb2 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:07 +0100 Subject: [PATCH 06/27] can: netlink: add CAN_CTRLMODE_XL_TMS flag The Transceiver Mode Switching (TMS) indicates whether the CAN XL controller shall use the PWM or NRZ encoding during the data phase. The term "transceiver mode switching" is used in both ISO 11898-1 and CiA 612-2 (although only the latter one uses the abbreviation TMS). We adopt the same naming convention here for consistency. Add the CAN_CTRLMODE_XL_TMS flag to the list of the CAN control modes. Add can_validate_xl_flags() to check the coherency of the TMS flag. That function will be reused in upcoming changes to validate the other CAN XL flags. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-6-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 2 ++ drivers/net/can/dev/netlink.c | 48 ++++++++++++++++++++++++++++++-- include/uapi/linux/can/netlink.h | 1 + 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index bdec2c52c8ec..091f30e94c61 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -123,6 +123,8 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "XL-TDC-AUTO"; case CAN_CTRLMODE_XL_TDC_MANUAL: return "XL-TDC-MANUAL"; + case CAN_CTRLMODE_XL_TMS: + return "TMS"; default: return ""; } diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index fdd1fa7cf93a..b2c24439abba 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -181,6 +181,32 @@ static int can_validate_databittiming(struct nlattr *data[], return 0; } +static int can_validate_xl_flags(struct netlink_ext_ack *extack, + u32 masked_flags, u32 mask) +{ + if (masked_flags & CAN_CTRLMODE_XL) { + if (masked_flags & CAN_CTRLMODE_XL_TMS) { + const u32 tms_conflicts_mask = CAN_CTRLMODE_FD | + CAN_CTRLMODE_XL_TDC_MASK; + u32 tms_conflicts = masked_flags & tms_conflicts_mask; + + if (tms_conflicts) { + NL_SET_ERR_MSG_FMT(extack, + "TMS and %s are mutually exclusive", + can_get_ctrlmode_str(tms_conflicts)); + return -EOPNOTSUPP; + } + } + } else { + if (mask & CAN_CTRLMODE_XL_TMS) { + NL_SET_ERR_MSG(extack, "TMS requires CAN XL"); + return -EOPNOTSUPP; + } + } + + return 0; +} + static int can_validate(struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { @@ -201,6 +227,10 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], "LISTEN-ONLY and RESTRICTED modes are mutually exclusive"); return -EOPNOTSUPP; } + + err = can_validate_xl_flags(extack, flags, cm->mask); + if (err) + return err; } err = can_validate_bittiming(data, extack, IFLA_CAN_BITTIMING); @@ -226,7 +256,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, { struct can_priv *priv = netdev_priv(dev); struct can_ctrlmode *cm; - u32 ctrlstatic, maskedflags, notsupp, ctrlstatic_missing; + u32 ctrlstatic, maskedflags, deactivated, notsupp, ctrlstatic_missing; if (!data[IFLA_CAN_CTRLMODE]) return 0; @@ -238,6 +268,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, cm = nla_data(data[IFLA_CAN_CTRLMODE]); ctrlstatic = can_get_static_ctrlmode(priv); maskedflags = cm->flags & cm->mask; + deactivated = ~cm->flags & cm->mask; notsupp = maskedflags & ~(priv->ctrlmode_supported | ctrlstatic); ctrlstatic_missing = (maskedflags & ctrlstatic) ^ ctrlstatic; @@ -259,11 +290,21 @@ static int can_ctrlmode_changelink(struct net_device *dev, return -EOPNOTSUPP; } + /* If FD was active and is not turned off, check for XL conflicts */ + if (priv->ctrlmode & CAN_CTRLMODE_FD & ~deactivated) { + if (maskedflags & CAN_CTRLMODE_XL_TMS) { + NL_SET_ERR_MSG(extack, + "TMS can not be activated while CAN FD is on"); + return -EOPNOTSUPP; + } + } + /* If a top dependency flag is provided, reset all its dependencies */ if (cm->mask & CAN_CTRLMODE_FD) priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; if (cm->mask & CAN_CTRLMODE_XL) - priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK); + priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK | + CAN_CTRLMODE_XL_TMS); /* clear bits to be modified and copy the flag values */ priv->ctrlmode &= ~cm->mask; @@ -395,7 +436,8 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], if (data[IFLA_CAN_CTRLMODE]) { struct can_ctrlmode *cm = nla_data(data[IFLA_CAN_CTRLMODE]); - need_tdc_calc = !(cm->mask & tdc_mask); + if (fd || !(priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + need_tdc_calc = !(cm->mask & tdc_mask); } if (data_tdc) { /* TDC parameters are provided: use them */ diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index c2c96c5978a8..ebafb091d80f 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -107,6 +107,7 @@ struct can_ctrlmode { #define CAN_CTRLMODE_XL 0x1000 /* CAN XL mode */ #define CAN_CTRLMODE_XL_TDC_AUTO 0x2000 /* XL transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_XL_TDC_MANUAL 0x4000 /* XL TDCV is manually set up by user */ +#define CAN_CTRLMODE_XL_TMS 0x8000 /* Transceiver Mode Switching */ /* * CAN device statistics From 6df01533e535d21cac779ff35cc25c43304035c3 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Wed, 26 Nov 2025 11:16:08 +0100 Subject: [PATCH 07/27] can: dev: can_dev_dropped_skb: drop CC/FD frames in CANXL-only mode The error-signalling (ES) is a mandatory functionality for CAN CC and CAN FD to report CAN frame format violations by sending an error-frame signal on the bus. A so-called 'mixed-mode' is intended to have (XL-tolerant) CAN FD nodes and CAN XL nodes on one CAN segment, where the FD-controllers can talk CC/FD and the XL-controllers can talk CC/FD/XL. This mixed-mode utilizes the error-signalling for sending CC/FD/XL frames. The CANXL-only mode disables the error-signalling in the CAN XL controller. This mode does not allow CC/FD frames to be sent but additionally offers a CAN XL transceiver mode switching (TMS). Configured with CAN_CTRLMODE_FD and CAN_CTRLMODE_XL this leads to: FD=0 XL=0 CC-only mode (ES=1) FD=1 XL=0 FD/CC mixed-mode (ES=1) FD=1 XL=1 XL/FD/CC mixed-mode (ES=1) FD=0 XL=1 XL-only mode (ES=0, TMS optional) The helper function can_dev_in_xl_only_mode() determines the required value to disable error signalling in the CAN XL controller. Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-7-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- include/linux/can/dev.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index f15879bd818d..52c8be5c160e 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -135,6 +135,19 @@ void can_bus_off(struct net_device *dev); const char *can_get_state_str(const enum can_state state); const char *can_get_ctrlmode_str(u32 ctrlmode); +static inline bool can_dev_in_xl_only_mode(struct can_priv *priv) +{ + const u32 mixed_mode = CAN_CTRLMODE_FD | CAN_CTRLMODE_XL; + + /* When CAN XL is enabled but FD is disabled we are running in + * the so-called 'CANXL-only mode' where the error signalling is + * disabled. This helper function determines the required value + * to disable error signalling in the CAN XL controller. + * The so-called CC/FD/XL 'mixed mode' requires error signalling. + */ + return ((priv->ctrlmode & mixed_mode) == CAN_CTRLMODE_XL); +} + /* drop skb if it does not contain a valid CAN frame for sending */ static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *skb) { @@ -153,6 +166,12 @@ static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *s goto invalid_skb; } + if (can_dev_in_xl_only_mode(priv) && !can_is_canxl_skb(skb)) { + netdev_info_once(dev, + "Error signaling is disabled, dropping skb\n"); + goto invalid_skb; + } + return can_dropped_invalid_skb(dev, skb); invalid_skb: From f6ccc2b293ba27e9171c63e456d9cba664fa2337 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:09 +0100 Subject: [PATCH 08/27] can: bittiming: add PWM parameters In CAN XL, higher data bit rates require the CAN transceiver to switch its operation mode to use Pulse-Width Modulation (PWM) transmission mode instead of the classic dominant/recessive transmission mode. The PWM parameters are: - PWMS: pulse width modulation short phase - PWML: pulse width modulation long phase - PWMO: pulse width modulation offset CiA 612-2 specifies PWMS and PWML to be at least 1 (arguably, PWML shall be at least 2 to respect the PWMS < PWML rule). PWMO's minimum is expected to always be zero. It is added more for consistency than anything else. Add struct can_pwm_const so that the different devices can provide their minimum and maximum values. When TMS is on, the runtime PWMS, PWML and PWMO are needed (either calculated or provided by the user): add struct can_pwm to store these. TDC and PWM can not be used at the same time (TDC can only be used when TMS is off and PWM only when TMS is on). struct can_pwm is thus put together with struct can_tdc inside a union to save some space. The netlink logic will be added in an upcoming change. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-8-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- include/linux/can/bittiming.h | 41 +++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index b6cd2476ffd7..967d76689c4f 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* Copyright (c) 2020 Pengutronix, Marc Kleine-Budde - * Copyright (c) 2021 Vincent Mailhol + * Copyright (c) 2021-2025 Vincent Mailhol */ #ifndef _CAN_BITTIMING_H @@ -120,11 +120,48 @@ struct can_tdc_const { u32 tdcf_max; }; +/* + * struct can_pwm - CAN Pulse-Width Modulation (PWM) parameters + * + * @pwms: pulse width modulation short phase + * @pwml: pulse width modulation long phase + * @pwmo: pulse width modulation offset + */ +struct can_pwm { + u32 pwms; + u32 pwml; + u32 pwmo; +}; + +/* + * struct can_pwm - CAN hardware-dependent constants for Pulse-Width + * Modulation (PWM) + * + * @pwms_min: PWM short phase minimum value. Must be at least 1. + * @pwms_max: PWM short phase maximum value + * @pwml_min: PWM long phase minimum value. Must be at least 1. + * @pwml_max: PWM long phase maximum value + * @pwmo_min: PWM offset phase minimum value + * @pwmo_max: PWM offset phase maximum value + */ +struct can_pwm_const { + u32 pwms_min; + u32 pwms_max; + u32 pwml_min; + u32 pwml_max; + u32 pwmo_min; + u32 pwmo_max; +}; + struct data_bittiming_params { const struct can_bittiming_const *data_bittiming_const; struct can_bittiming data_bittiming; const struct can_tdc_const *tdc_const; - struct can_tdc tdc; + const struct can_pwm_const *pwm_const; + union { + struct can_tdc tdc; + struct can_pwm pwm; + }; const u32 *data_bitrate_const; unsigned int data_bitrate_const_cnt; int (*do_set_data_bittiming)(struct net_device *dev); From 8e2a2885a2a6217190065d1aae98fe88a670cc28 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:10 +0100 Subject: [PATCH 09/27] can: bittiming: add PWM validation Add can_validate_pwm() to validate the values pwms, pwml and pwml. Error messages are added to each of the checks to inform the user on what went wrong. Refer to those error messages to understand the validation logic. The boundary values CAN_PWM_DECODE_NS (the transceiver minimum decoding margin) and CAN_PWM_NS_MAX (the maximum PWM symbol duration) are hardcoded for the moment. Note that a transceiver capable of bitrates higher than 20 Mbps may be able to handle a CAN_PWM_DECODE_NS below 5 ns. If such transceivers become commercially available, this code could be revisited to make this parameter configurable. For now, leave it static. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-9-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/bittiming.c | 63 +++++++++++++++++++++++++++++++++ include/linux/can/bittiming.h | 22 ++++++++++++ 2 files changed, 85 insertions(+) diff --git a/drivers/net/can/dev/bittiming.c b/drivers/net/can/dev/bittiming.c index 0b93900b1dfa..8f82418230ce 100644 --- a/drivers/net/can/dev/bittiming.c +++ b/drivers/net/can/dev/bittiming.c @@ -2,6 +2,7 @@ /* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix * Copyright (C) 2006 Andrey Volkov, Varma Electronics * Copyright (C) 2008-2009 Wolfgang Grandegger + * Copyright (c) 2025 Vincent Mailhol */ #include @@ -151,3 +152,65 @@ int can_get_bittiming(const struct net_device *dev, struct can_bittiming *bt, return -EINVAL; } + +int can_validate_pwm_bittiming(const struct net_device *dev, + const struct can_pwm *pwm, + struct netlink_ext_ack *extack) +{ + const struct can_priv *priv = netdev_priv(dev); + u32 xl_bit_time_tqmin = can_bit_time_tqmin(&priv->xl.data_bittiming); + u32 nom_bit_time_tqmin = can_bit_time_tqmin(&priv->bittiming); + u32 pwms_ns = can_tqmin_to_ns(pwm->pwms, priv->clock.freq); + u32 pwml_ns = can_tqmin_to_ns(pwm->pwml, priv->clock.freq); + + if (pwms_ns + pwml_ns > CAN_PWM_NS_MAX) { + NL_SET_ERR_MSG_FMT(extack, + "The PWM symbol duration: %u ns may not exceed %u ns", + pwms_ns + pwml_ns, CAN_PWM_NS_MAX); + return -EINVAL; + } + + if (pwms_ns < CAN_PWM_DECODE_NS) { + NL_SET_ERR_MSG_FMT(extack, + "PWMS: %u ns shall be at least %u ns", + pwms_ns, CAN_PWM_DECODE_NS); + return -EINVAL; + } + + if (pwm->pwms >= pwm->pwml) { + NL_SET_ERR_MSG_FMT(extack, + "PWMS: %u tqmin shall be smaller than PWML: %u tqmin", + pwm->pwms, pwm->pwml); + return -EINVAL; + } + + if (pwml_ns - pwms_ns < 2 * CAN_PWM_DECODE_NS) { + NL_SET_ERR_MSG_FMT(extack, + "At least %u ns shall separate PWMS: %u ns from PMWL: %u ns", + 2 * CAN_PWM_DECODE_NS, pwms_ns, pwml_ns); + return -EINVAL; + } + + if (xl_bit_time_tqmin % (pwm->pwms + pwm->pwml) != 0) { + NL_SET_ERR_MSG_FMT(extack, + "PWM duration: %u tqmin does not divide XL's bit time: %u tqmin", + pwm->pwms + pwm->pwml, xl_bit_time_tqmin); + return -EINVAL; + } + + if (pwm->pwmo >= pwm->pwms + pwm->pwml) { + NL_SET_ERR_MSG_FMT(extack, + "PWMO: %u tqmin can not be greater than PWMS + PWML: %u tqmin", + pwm->pwmo, pwm->pwms + pwm->pwml); + return -EINVAL; + } + + if (nom_bit_time_tqmin % (pwm->pwms + pwm->pwml) != pwm->pwmo) { + NL_SET_ERR_MSG_FMT(extack, + "Can not assemble nominal bit time: %u tqmin out of PWMS + PMWL and PWMO", + nom_bit_time_tqmin); + return -EINVAL; + } + + return 0; +} diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index 967d76689c4f..2504fafc72e4 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -87,6 +87,11 @@ struct can_tdc { u32 tdcf; }; +/* The transceiver decoding margin corresponds to t_Decode in ISO 11898-2 */ +#define CAN_PWM_DECODE_NS 5 +/* Maximum PWM symbol duration. Corresponds to t_SymbolNom_MAX - t_Decode */ +#define CAN_PWM_NS_MAX (205 - CAN_PWM_DECODE_NS) + /* * struct can_tdc_const - CAN hardware-dependent constant for * Transmission Delay Compensation @@ -203,6 +208,10 @@ int can_get_bittiming(const struct net_device *dev, struct can_bittiming *bt, const unsigned int bitrate_const_cnt, struct netlink_ext_ack *extack); +int can_validate_pwm_bittiming(const struct net_device *dev, + const struct can_pwm *pwm, + struct netlink_ext_ack *extack); + /* * can_get_relative_tdco() - TDCO relative to the sample point * @@ -245,4 +254,17 @@ static inline unsigned int can_bit_time(const struct can_bittiming *bt) return CAN_SYNC_SEG + bt->prop_seg + bt->phase_seg1 + bt->phase_seg2; } +/* Duration of one bit in minimum time quantum */ +static inline unsigned int can_bit_time_tqmin(const struct can_bittiming *bt) +{ + return can_bit_time(bt) * bt->brp; +} + +/* Convert a duration from minimum a minimum time quantum to nano seconds */ +static inline u32 can_tqmin_to_ns(u32 tqmin, u32 clock_freq) +{ + return DIV_U64_ROUND_CLOSEST(mul_u32_u32(tqmin, NSEC_PER_SEC), + clock_freq); +} + #endif /* !_CAN_BITTIMING_H */ From 9892339cf0348730e82383d4de9d9387b9d63925 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:11 +0100 Subject: [PATCH 10/27] can: calc_bittiming: add PWM calculation Perform the PWM calculation according to CiA recommendations. Note that for databitrates greater than 5 MBPS, tqmin is less than CAN_PWM_NS_MAX (which is defined to 200 nano seconds), consequently, the result of the division: DIV_ROUND_UP(xl_ns, CAN_PWM_NS_MAX) is one and thus the for loop automatically stops on the first iteration giving a single PWM symbol per bit as expected. Because of that, there is no actual need for a separate conditional branch for when the databitrate is greater than 5 MBPS. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-10-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/calc_bittiming.c | 36 ++++++++++++++++++++++++++++ include/linux/can/bittiming.h | 10 ++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/net/can/dev/calc_bittiming.c b/drivers/net/can/dev/calc_bittiming.c index 394d6974f481..268ec6fa7c49 100644 --- a/drivers/net/can/dev/calc_bittiming.c +++ b/drivers/net/can/dev/calc_bittiming.c @@ -2,6 +2,7 @@ /* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix * Copyright (C) 2006 Andrey Volkov, Varma Electronics * Copyright (C) 2008-2009 Wolfgang Grandegger + * Copyright (C) 2021-2025 Vincent Mailhol */ #include @@ -198,3 +199,38 @@ void can_calc_tdco(struct can_tdc *tdc, const struct can_tdc_const *tdc_const, *ctrlmode |= tdc_auto; } } + +int can_calc_pwm(struct net_device *dev, struct netlink_ext_ack *extack) +{ + struct can_priv *priv = netdev_priv(dev); + const struct can_pwm_const *pwm_const = priv->xl.pwm_const; + struct can_pwm *pwm = &priv->xl.pwm; + u32 xl_tqmin = can_bit_time_tqmin(&priv->xl.data_bittiming); + u32 xl_ns = can_tqmin_to_ns(xl_tqmin, priv->clock.freq); + u32 nom_tqmin = can_bit_time_tqmin(&priv->bittiming); + int pwm_per_bit_max = xl_tqmin / (pwm_const->pwms_min + pwm_const->pwml_min); + int pwm_per_bit; + u32 pwm_tqmin; + + /* For 5 MB/s databitrate or greater, xl_ns < CAN_PWM_NS_MAX + * giving us a pwm_per_bit of 1 and the loop immediately breaks + */ + for (pwm_per_bit = DIV_ROUND_UP(xl_ns, CAN_PWM_NS_MAX); + pwm_per_bit <= pwm_per_bit_max; pwm_per_bit++) + if (xl_tqmin % pwm_per_bit == 0) + break; + + if (pwm_per_bit > pwm_per_bit_max) { + NL_SET_ERR_MSG_FMT(extack, + "Can not divide the XL data phase's bit time: %u tqmin into multiple PWM symbols", + xl_tqmin); + return -EINVAL; + } + + pwm_tqmin = xl_tqmin / pwm_per_bit; + pwm->pwms = DIV_ROUND_UP_POW2(pwm_tqmin, 4); + pwm->pwml = pwm_tqmin - pwm->pwms; + pwm->pwmo = nom_tqmin % pwm_tqmin; + + return 0; +} diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index 2504fafc72e4..726d909e87ce 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -180,6 +180,8 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, void can_calc_tdco(struct can_tdc *tdc, const struct can_tdc_const *tdc_const, const struct can_bittiming *dbt, u32 tdc_mask, u32 *ctrlmode, u32 ctrlmode_supported); + +int can_calc_pwm(struct net_device *dev, struct netlink_ext_ack *extack); #else /* !CONFIG_CAN_CALC_BITTIMING */ static inline int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, @@ -195,6 +197,14 @@ can_calc_tdco(struct can_tdc *tdc, const struct can_tdc_const *tdc_const, u32 tdc_mask, u32 *ctrlmode, u32 ctrlmode_supported) { } + +static inline int +can_calc_pwm(struct net_device *dev, struct netlink_ext_ack *extack) +{ + NL_SET_ERR_MSG(extack, + "bit-timing calculation not available: manually provide PWML and PWMS\n"); + return -EINVAL; +} #endif /* CONFIG_CAN_CALC_BITTIMING */ void can_sjw_set_default(struct can_bittiming *bt); From 46552323fa6779beb1ea558254dfd56021174c93 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:12 +0100 Subject: [PATCH 11/27] can: netlink: add PWM netlink interface When the TMS is switched on, the node uses PWM (Pulse Width Modulation) during the data phase instead of the classic NRZ (Non Return to Zero) encoding. PWM is configured by three parameters: - PWMS: Pulse Width Modulation Short phase - PWML: Pulse Width Modulation Long phase - PWMO: Pulse Width Modulation Offset time For each of these parameters, define three IFLA symbols: - IFLA_CAN_PWM_PWM*_MIN: the minimum allowed value. - IFLA_CAN_PWM_PWM*_MAX: the maximum allowed value. - IFLA_CAN_PWM_PWM*: the runtime value. This results in a total of nine IFLA symbols which are all nested in a parent IFLA_CAN_XL_PWM symbol. IFLA_CAN_PWM_PWM*_MIN and IFLA_CAN_PWM_PWM*_MAX define the range of allowed values and will match the value statically configured by the device in struct can_pwm_const. IFLA_CAN_PWM_PWM* match the runtime values stored in struct can_pwm. Those parameters may only be configured when the tms mode is on. If the PWMS, PWML and PWMO parameters are provided, check that all the needed parameters are present using can_validate_pwm(), then check their value using can_validate_pwm_bittiming(). PWMO defaults to zero if omitted. Otherwise, if CAN_CTRLMODE_XL_TMS is true but none of the PWM parameters are provided, calculate them using can_calc_pwm(). Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-11-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/netlink.c | 192 ++++++++++++++++++++++++++++++- include/uapi/linux/can/netlink.h | 25 ++++ 2 files changed, 215 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index b2c24439abba..d6b0e686fb11 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -25,6 +25,7 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + 1] = { [IFLA_CAN_XL_DATA_BITTIMING] = { .len = sizeof(struct can_bittiming) }, [IFLA_CAN_XL_DATA_BITTIMING_CONST] = { .len = sizeof(struct can_bittiming_const) }, [IFLA_CAN_XL_TDC] = { .type = NLA_NESTED }, + [IFLA_CAN_XL_PWM] = { .type = NLA_NESTED }, }; static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { @@ -39,6 +40,18 @@ static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { [IFLA_CAN_TDC_TDCF] = { .type = NLA_U32 }, }; +static const struct nla_policy can_pwm_policy[IFLA_CAN_PWM_MAX + 1] = { + [IFLA_CAN_PWM_PWMS_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMS_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMS] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO] = { .type = NLA_U32 }, +}; + static int can_validate_bittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_bittiming) @@ -119,6 +132,40 @@ static int can_validate_tdc(struct nlattr *data_tdc, return 0; } +static int can_validate_pwm(struct nlattr *data[], + struct netlink_ext_ack *extack, u32 flags) +{ + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + int err; + + if (!data[IFLA_CAN_XL_PWM]) + return 0; + + if (!(flags & CAN_CTRLMODE_XL_TMS)) { + NL_SET_ERR_MSG(extack, "PWM requires TMS"); + return -EOPNOTSUPP; + } + + err = nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, data[IFLA_CAN_XL_PWM], + can_pwm_policy, extack); + if (err) + return err; + + if (!tb_pwm[IFLA_CAN_PWM_PWMS] != !tb_pwm[IFLA_CAN_PWM_PWML]) { + NL_SET_ERR_MSG(extack, + "Provide either both PWMS and PWML, or none for automatic calculation"); + return -EOPNOTSUPP; + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO] && + (!tb_pwm[IFLA_CAN_PWM_PWMS] || !tb_pwm[IFLA_CAN_PWM_PWML])) { + NL_SET_ERR_MSG(extack, "PWMO requires both PWMS and PWML"); + return -EOPNOTSUPP; + } + + return 0; +} + static int can_validate_databittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_data_bittiming, u32 flags) @@ -247,6 +294,10 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], if (err) return err; + err = can_validate_pwm(data, extack, flags); + if (err) + return err; + return 0; } @@ -322,6 +373,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, sizeof(priv->fd.data_bittiming)); priv->ctrlmode &= ~CAN_CTRLMODE_XL_TDC_MASK; memset(&priv->xl.tdc, 0, sizeof(priv->xl.tdc)); + memset(&priv->xl.pwm, 0, sizeof(priv->xl.pwm)); } can_set_default_mtu(dev); @@ -468,6 +520,76 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], return 0; } +static int can_pwm_changelink(struct net_device *dev, + const struct nlattr *pwm_nla, + struct netlink_ext_ack *extack) +{ + struct can_priv *priv = netdev_priv(dev); + const struct can_pwm_const *pwm_const = priv->xl.pwm_const; + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + struct can_pwm pwm = { 0 }; + int err; + + if (!(priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + return 0; + + if (!pwm_const) { + NL_SET_ERR_MSG(extack, "The device does not support PWM"); + return -EOPNOTSUPP; + } + + if (!pwm_nla) + return can_calc_pwm(dev, extack); + + err = nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, pwm_nla, + can_pwm_policy, extack); + if (err) + return err; + + if (tb_pwm[IFLA_CAN_PWM_PWMS]) { + pwm.pwms = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMS]); + if (pwm.pwms < pwm_const->pwms_min || + pwm.pwms > pwm_const->pwms_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMS: %u tqmin is out of range: %u...%u", + pwm.pwms, pwm_const->pwms_min, + pwm_const->pwms_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWML]) { + pwm.pwml = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWML]); + if (pwm.pwml < pwm_const->pwml_min || + pwm.pwml > pwm_const->pwml_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWML: %u tqmin is out of range: %u...%u", + pwm.pwml, pwm_const->pwml_min, + pwm_const->pwml_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO]) { + pwm.pwmo = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMO]); + if (pwm.pwmo < pwm_const->pwmo_min || + pwm.pwmo > pwm_const->pwmo_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMO: %u tqmin is out of range: %u...%u", + pwm.pwmo, pwm_const->pwmo_min, + pwm_const->pwmo_max); + return -EINVAL; + } + } + + err = can_validate_pwm_bittiming(dev, &pwm, extack); + if (err) + return err; + + priv->xl.pwm = pwm; + return 0; +} + static int can_changelink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) @@ -559,6 +681,9 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], /* CAN XL */ err = can_dbt_changelink(dev, data, false, extack); + if (err) + return err; + err = can_pwm_changelink(dev, data[IFLA_CAN_XL_PWM], extack); if (err) return err; @@ -647,6 +772,30 @@ static size_t can_ctrlmode_ext_get_size(void) nla_total_size(sizeof(u32)); /* IFLA_CAN_CTRLMODE_SUPPORTED */ } +static size_t can_pwm_get_size(const struct can_pwm_const *pwm_const, + bool pwm_on) +{ + size_t size; + + if (!pwm_const || !pwm_on) + return 0; + + size = nla_total_size(0); /* nest IFLA_CAN_PWM */ + + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MAX */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MAX */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MAX */ + + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO */ + + return size; +} + static size_t can_get_size(const struct net_device *dev) { struct can_priv *priv = netdev_priv(dev); @@ -678,6 +827,8 @@ static size_t can_get_size(const struct net_device *dev) size += can_data_bittiming_get_size(&priv->xl, priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); + size += can_pwm_get_size(priv->xl.pwm_const, /* IFLA_CAN_XL_PWM */ + priv->ctrlmode & CAN_CTRLMODE_XL_TMS); return size; } @@ -776,6 +927,42 @@ static int can_tdc_fill_info(struct sk_buff *skb, const struct net_device *dev, return -EMSGSIZE; } +static int can_pwm_fill_info(struct sk_buff *skb, const struct can_priv *priv) +{ + const struct can_pwm_const *pwm_const = priv->xl.pwm_const; + const struct can_pwm *pwm = &priv->xl.pwm; + struct nlattr *nest; + + if (!pwm_const) + return 0; + + nest = nla_nest_start(skb, IFLA_CAN_XL_PWM); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MIN, pwm_const->pwms_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MAX, pwm_const->pwms_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MIN, pwm_const->pwml_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MAX, pwm_const->pwml_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MIN, pwm_const->pwmo_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MAX, pwm_const->pwmo_max)) + goto err_cancel; + + if (priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS, pwm->pwms) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML, pwm->pwml) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO, pwm->pwmo)) + goto err_cancel; + } + + nla_nest_end(skb, nest); + return 0; + +err_cancel: + nla_nest_cancel(skb, nest); + return -EMSGSIZE; +} + static int can_ctrlmode_ext_fill_info(struct sk_buff *skb, const struct can_priv *priv) { @@ -859,9 +1046,10 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) priv->xl.data_bitrate_const, priv->xl.data_bitrate_const_cnt) || - can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) - ) + can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) || + can_pwm_fill_info(skb, priv) + ) return -EMSGSIZE; return 0; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index ebafb091d80f..c30d16746159 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -5,6 +5,7 @@ * Definitions for the CAN netlink interface * * Copyright (c) 2009 Wolfgang Grandegger + * Copyright (c) 2021-2025 Vincent Mailhol * * This program is free software; you can redistribute it and/or modify * it under the terms of the version 2 of the GNU General Public License @@ -147,6 +148,7 @@ enum { IFLA_CAN_XL_DATA_BITTIMING_CONST, IFLA_CAN_XL_DATA_BITRATE_CONST, IFLA_CAN_XL_TDC, + IFLA_CAN_XL_PWM, /* add new constants above here */ __IFLA_CAN_MAX, @@ -188,6 +190,29 @@ enum { IFLA_CAN_CTRLMODE_MAX = __IFLA_CAN_CTRLMODE - 1 }; +/* + * CAN FD/XL Pulse-Width Modulation (PWM) + * + * Please refer to struct can_pwm_const and can_pwm in + * include/linux/can/bittiming.h for further details. + */ +enum { + IFLA_CAN_PWM_UNSPEC, + IFLA_CAN_PWM_PWMS_MIN, /* u32 */ + IFLA_CAN_PWM_PWMS_MAX, /* u32 */ + IFLA_CAN_PWM_PWML_MIN, /* u32 */ + IFLA_CAN_PWM_PWML_MAX, /* u32 */ + IFLA_CAN_PWM_PWMO_MIN, /* u32 */ + IFLA_CAN_PWM_PWMO_MAX, /* u32 */ + IFLA_CAN_PWM_PWMS, /* u32 */ + IFLA_CAN_PWM_PWML, /* u32 */ + IFLA_CAN_PWM_PWMO, /* u32 */ + + /* add new constants above here */ + __IFLA_CAN_PWM, + IFLA_CAN_PWM_MAX = __IFLA_CAN_PWM - 1 +}; + /* u16 termination range: 1..65535 Ohms */ #define CAN_TERMINATION_DISABLED 0 From 1d147cb7c51d6e994ba740709072adf270d7b878 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:13 +0100 Subject: [PATCH 12/27] can: calc_bittiming: replace misleading "nominal" by "reference" The functions can_update_sample_point() and can_calc_bittiming() are generic and meant to be used for both the nominal and the data bittiming calculation. However, those functions use misleading terminologies such as "bitrate nominal" or "sample point nominal". Replace all places where the word "nominal" appears with "reference" in order to better distinguish it from the calculated values. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-12-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/calc_bittiming.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/net/can/dev/calc_bittiming.c b/drivers/net/can/dev/calc_bittiming.c index 268ec6fa7c49..c8c166b383cd 100644 --- a/drivers/net/can/dev/calc_bittiming.c +++ b/drivers/net/can/dev/calc_bittiming.c @@ -24,7 +24,7 @@ */ static int can_update_sample_point(const struct can_bittiming_const *btc, - const unsigned int sample_point_nominal, const unsigned int tseg, + const unsigned int sample_point_reference, const unsigned int tseg, unsigned int *tseg1_ptr, unsigned int *tseg2_ptr, unsigned int *sample_point_error_ptr) { @@ -35,7 +35,7 @@ can_update_sample_point(const struct can_bittiming_const *btc, for (i = 0; i <= 1; i++) { tseg2 = tseg + CAN_SYNC_SEG - - (sample_point_nominal * (tseg + CAN_SYNC_SEG)) / + (sample_point_reference * (tseg + CAN_SYNC_SEG)) / 1000 - i; tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max); tseg1 = tseg - tseg2; @@ -46,9 +46,9 @@ can_update_sample_point(const struct can_bittiming_const *btc, sample_point = 1000 * (tseg + CAN_SYNC_SEG - tseg2) / (tseg + CAN_SYNC_SEG); - sample_point_error = abs(sample_point_nominal - sample_point); + sample_point_error = abs(sample_point_reference - sample_point); - if (sample_point <= sample_point_nominal && + if (sample_point <= sample_point_reference && sample_point_error < best_sample_point_error) { best_sample_point = sample_point; best_sample_point_error = sample_point_error; @@ -68,11 +68,11 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, { struct can_priv *priv = netdev_priv(dev); unsigned int bitrate; /* current bitrate */ - unsigned int bitrate_error; /* difference between current and nominal value */ + unsigned int bitrate_error; /* diff between calculated and reference value */ unsigned int best_bitrate_error = UINT_MAX; - unsigned int sample_point_error; /* difference between current and nominal value */ + unsigned int sample_point_error; /* diff between calculated and reference value */ unsigned int best_sample_point_error = UINT_MAX; - unsigned int sample_point_nominal; /* nominal sample point */ + unsigned int sample_point_reference; /* reference sample point */ unsigned int best_tseg = 0; /* current best value for tseg */ unsigned int best_brp = 0; /* current best value for brp */ unsigned int brp, tsegall, tseg, tseg1 = 0, tseg2 = 0; @@ -81,14 +81,14 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, /* Use CiA recommended sample points */ if (bt->sample_point) { - sample_point_nominal = bt->sample_point; + sample_point_reference = bt->sample_point; } else { if (bt->bitrate > 800 * KILO /* BPS */) - sample_point_nominal = 750; + sample_point_reference = 750; else if (bt->bitrate > 500 * KILO /* BPS */) - sample_point_nominal = 800; + sample_point_reference = 800; else - sample_point_nominal = 875; + sample_point_reference = 875; } /* tseg even = round down, odd = round up */ @@ -115,7 +115,7 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, if (bitrate_error < best_bitrate_error) best_sample_point_error = UINT_MAX; - can_update_sample_point(btc, sample_point_nominal, tseg / 2, + can_update_sample_point(btc, sample_point_reference, tseg / 2, &tseg1, &tseg2, &sample_point_error); if (sample_point_error >= best_sample_point_error) continue; @@ -146,7 +146,7 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, } /* real sample point */ - bt->sample_point = can_update_sample_point(btc, sample_point_nominal, + bt->sample_point = can_update_sample_point(btc, sample_point_reference, best_tseg, &tseg1, &tseg2, NULL); From a6ddf91a4f9718cec712785904c57b7117f61d6c Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:14 +0100 Subject: [PATCH 13/27] can: calc_bittiming: add can_calc_sample_point_nrz() CAN XL optimal sample point for PWM encoding (when TMS is on) differs from the NRZ optimal one. There is thus a need to calculate a different sample point depending whether TMS is on or off. This is a preparation change: move the sample point calculation from can_calc_bittiming() into the new can_calc_sample_point_nrz() function. In an upcoming change, a function will be added to calculate the sample point for PWM encoding. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-13-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/calc_bittiming.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/net/can/dev/calc_bittiming.c b/drivers/net/can/dev/calc_bittiming.c index c8c166b383cd..bacdf3b218d3 100644 --- a/drivers/net/can/dev/calc_bittiming.c +++ b/drivers/net/can/dev/calc_bittiming.c @@ -10,6 +10,18 @@ #define CAN_CALC_MAX_ERROR 50 /* in one-tenth of a percent */ +/* CiA recommended sample points for Non Return to Zero encoding. */ +static int can_calc_sample_point_nrz(const struct can_bittiming *bt) +{ + if (bt->bitrate > 800 * KILO /* BPS */) + return 750; + + if (bt->bitrate > 500 * KILO /* BPS */) + return 800; + + return 875; +} + /* Bit-timing calculation derived from: * * Code based on LinCAN sources and H8S2638 project @@ -79,17 +91,10 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, u64 v64; int err; - /* Use CiA recommended sample points */ - if (bt->sample_point) { + if (bt->sample_point) sample_point_reference = bt->sample_point; - } else { - if (bt->bitrate > 800 * KILO /* BPS */) - sample_point_reference = 750; - else if (bt->bitrate > 500 * KILO /* BPS */) - sample_point_reference = 800; - else - sample_point_reference = 875; - } + else + sample_point_reference = can_calc_sample_point_nrz(bt); /* tseg even = round down, odd = round up */ for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1; From f5de373ae455f1db6cf15033d660ac0046bcb0ff Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:15 +0100 Subject: [PATCH 14/27] can: calc_bittiming: add can_calc_sample_point_pwm() The optimum sample point value depends on the bit symmetry. The more asymmetric the bit is, the more the sample point would be located towards the end of the bit. On the contrary, if the transceiver only has a small asymmetry, the optimal sample point would be slightly after the centre of the bit. For NRZ encoding (used by Classical CAN, CAN FD and CAN XL with TMS off), the optimum sample points values are above 70% as implemented in can_calc_sample_point_nrz(). When TMS is on, CAN XL optimum sample points are near to 50% or 60% [1]. Add can_calc_sample_point_pwm() which returns a sample point which is suitable for PWM encoding. We crafted the formula to make it return the same values as below table (source: table 3 of [1]). Bit rate (Mbits/s) Sample point ------------------------------------- 2.0 51.3% 5.0 53.1% 8.0 55.0% 10.0 56.3% 12.3 53.8% 13.3 58.3% 14.5 54.5% 16.0 60.0% 17.7 55.6% 20.0 62.5% The calculation simply consists of setting a slightly too high sample point and then letting can_update_sample_point() correct the values. For now, it is just a formula up our sleeves which matches the empirical observations of [1]. Once CiA recommendations become available, can_calc_sample_point_pwm() should be updated accordingly. [1] CAN XL system design: Clock tolerances and edge deviations edge deviations Link: https://www.can-cia.org/fileadmin/cia/documents/publications/cnlm/december_2024/cnlm_24-4_p18_can_xl_system_design_clock_tolerances_and_edge_deviations_dr_arthur_mutter_bosch.pdf Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-14-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/calc_bittiming.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/net/can/dev/calc_bittiming.c b/drivers/net/can/dev/calc_bittiming.c index bacdf3b218d3..60a505ce69de 100644 --- a/drivers/net/can/dev/calc_bittiming.c +++ b/drivers/net/can/dev/calc_bittiming.c @@ -22,6 +22,21 @@ static int can_calc_sample_point_nrz(const struct can_bittiming *bt) return 875; } +/* Sample points for Pulse-Width Modulation encoding. */ +static int can_calc_sample_point_pwm(const struct can_bittiming *bt) +{ + if (bt->bitrate > 15 * MEGA /* BPS */) + return 625; + + if (bt->bitrate > 9 * MEGA /* BPS */) + return 600; + + if (bt->bitrate > 4 * MEGA /* BPS */) + return 560; + + return 520; +} + /* Bit-timing calculation derived from: * * Code based on LinCAN sources and H8S2638 project @@ -93,6 +108,9 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, if (bt->sample_point) sample_point_reference = bt->sample_point; + else if (btc == priv->xl.data_bittiming_const && + (priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + sample_point_reference = can_calc_sample_point_pwm(bt); else sample_point_reference = can_calc_sample_point_nrz(bt); From 816cf430e84b4628dba665491e78ce081a468fcb Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:16 +0100 Subject: [PATCH 15/27] can: add dummy_can driver During the development of CAN XL, we found the need of creating a dummy CAN XL driver in order to test the new netlink interface. While this code was initially intended to be some throwaway, it received some positive feedback. Add the dummy_can driver. This driver acts similarly to the vcan interface in the sense that it will echo back any packet it receives. The difference is that it exposes a set on bittiming parameters as a real device would and thus must be configured as if it was a real physical interface. The driver comes with a debug mode. If debug message are enabled (for example by enabling CONFIG_CAN_DEBUG_DEVICES), it will print in the kernel log all the bittiming values, similar to what a: ip --details link show can0 would do. This driver is mostly intended for debugging and testing, but some developers also may want to look at it as a simple reference implementation. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-15-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/Kconfig | 17 +++ drivers/net/can/Makefile | 1 + drivers/net/can/dummy_can.c | 285 ++++++++++++++++++++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 drivers/net/can/dummy_can.c diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index d43d56694667..e15e320db476 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -124,6 +124,23 @@ config CAN_CAN327 If this driver is built as a module, it will be called can327. +config CAN_DUMMY + tristate "Dummy CAN" + help + A dummy CAN module supporting Classical CAN, CAN FD and CAN XL. It + exposes bittiming values which can be configured through the netlink + interface. + + The module will simply echo any frame sent to it. If debug messages + are activated, it prints all the CAN bittiming information in the + kernel log. Aside from that it does nothing. + + This is convenient for testing the CAN netlink interface. Most of the + users will never need this. If unsure, say NO. + + To compile this driver as a module, choose M here: the module will be + called dummy-can. + config CAN_FLEXCAN tristate "Support for Freescale FLEXCAN based chips" depends on OF || COLDFIRE || COMPILE_TEST diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 56138d8ddfd2..d7bc10a6b8ea 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_CAN_CAN327) += can327.o obj-$(CONFIG_CAN_CC770) += cc770/ obj-$(CONFIG_CAN_C_CAN) += c_can/ obj-$(CONFIG_CAN_CTUCANFD) += ctucanfd/ +obj-$(CONFIG_CAN_DUMMY) += dummy_can.o obj-$(CONFIG_CAN_FLEXCAN) += flexcan/ obj-$(CONFIG_CAN_GRCAN) += grcan.o obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/ diff --git a/drivers/net/can/dummy_can.c b/drivers/net/can/dummy_can.c new file mode 100644 index 000000000000..41953655e3d3 --- /dev/null +++ b/drivers/net/can/dummy_can.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2025 Vincent Mailhol */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct dummy_can { + struct can_priv can; + struct net_device *dev; +}; + +static struct dummy_can *dummy_can; + +static const struct can_bittiming_const dummy_can_bittiming_const = { + .name = "dummy_can CC", + .tseg1_min = 2, + .tseg1_max = 256, + .tseg2_min = 2, + .tseg2_max = 128, + .sjw_max = 128, + .brp_min = 1, + .brp_max = 512, + .brp_inc = 1 +}; + +static const struct can_bittiming_const dummy_can_fd_databittiming_const = { + .name = "dummy_can FD", + .tseg1_min = 2, + .tseg1_max = 256, + .tseg2_min = 2, + .tseg2_max = 128, + .sjw_max = 128, + .brp_min = 1, + .brp_max = 512, + .brp_inc = 1 +}; + +static const struct can_tdc_const dummy_can_fd_tdc_const = { + .tdcv_min = 0, + .tdcv_max = 0, /* Manual mode not supported. */ + .tdco_min = 0, + .tdco_max = 127, + .tdcf_min = 0, + .tdcf_max = 127 +}; + +static const struct can_bittiming_const dummy_can_xl_databittiming_const = { + .name = "dummy_can XL", + .tseg1_min = 2, + .tseg1_max = 256, + .tseg2_min = 2, + .tseg2_max = 128, + .sjw_max = 128, + .brp_min = 1, + .brp_max = 512, + .brp_inc = 1 +}; + +static const struct can_tdc_const dummy_can_xl_tdc_const = { + .tdcv_min = 0, + .tdcv_max = 0, /* Manual mode not supported. */ + .tdco_min = 0, + .tdco_max = 127, + .tdcf_min = 0, + .tdcf_max = 127 +}; + +static const struct can_pwm_const dummy_can_pwm_const = { + .pwms_min = 1, + .pwms_max = 8, + .pwml_min = 2, + .pwml_max = 24, + .pwmo_min = 0, + .pwmo_max = 16, +}; + +static void dummy_can_print_bittiming(struct net_device *dev, + struct can_bittiming *bt) +{ + netdev_dbg(dev, "\tbitrate: %u\n", bt->bitrate); + netdev_dbg(dev, "\tsample_point: %u\n", bt->sample_point); + netdev_dbg(dev, "\ttq: %u\n", bt->tq); + netdev_dbg(dev, "\tprop_seg: %u\n", bt->prop_seg); + netdev_dbg(dev, "\tphase_seg1: %u\n", bt->phase_seg1); + netdev_dbg(dev, "\tphase_seg2: %u\n", bt->phase_seg2); + netdev_dbg(dev, "\tsjw: %u\n", bt->sjw); + netdev_dbg(dev, "\tbrp: %u\n", bt->brp); +} + +static void dummy_can_print_tdc(struct net_device *dev, struct can_tdc *tdc) +{ + netdev_dbg(dev, "\t\ttdcv: %u\n", tdc->tdcv); + netdev_dbg(dev, "\t\ttdco: %u\n", tdc->tdco); + netdev_dbg(dev, "\t\ttdcf: %u\n", tdc->tdcf); +} + +static void dummy_can_print_pwm(struct net_device *dev, struct can_pwm *pwm, + struct can_bittiming *dbt) +{ + netdev_dbg(dev, "\t\tpwms: %u\n", pwm->pwms); + netdev_dbg(dev, "\t\tpwml: %u\n", pwm->pwml); + netdev_dbg(dev, "\t\tpwmo: %u\n", pwm->pwmo); +} + +static void dummy_can_print_ctrlmode(struct net_device *dev) +{ + struct dummy_can *priv = netdev_priv(dev); + struct can_priv *can_priv = &priv->can; + unsigned long supported = can_priv->ctrlmode_supported; + u32 enabled = can_priv->ctrlmode; + + netdev_dbg(dev, "Control modes:\n"); + netdev_dbg(dev, "\tsupported: 0x%08x\n", (u32)supported); + netdev_dbg(dev, "\tenabled: 0x%08x\n", enabled); + + if (supported) { + int idx; + + netdev_dbg(dev, "\tlist:"); + for_each_set_bit(idx, &supported, BITS_PER_TYPE(u32)) + netdev_dbg(dev, "\t\t%s: %s\n", + can_get_ctrlmode_str(BIT(idx)), + enabled & BIT(idx) ? "on" : "off"); + } +} + +static void dummy_can_print_bittiming_info(struct net_device *dev) +{ + struct dummy_can *priv = netdev_priv(dev); + struct can_priv *can_priv = &priv->can; + + netdev_dbg(dev, "Clock frequency: %u\n", can_priv->clock.freq); + netdev_dbg(dev, "Maximum bitrate: %u\n", can_priv->bitrate_max); + netdev_dbg(dev, "MTU: %u\n", dev->mtu); + netdev_dbg(dev, "\n"); + + dummy_can_print_ctrlmode(dev); + netdev_dbg(dev, "\n"); + + netdev_dbg(dev, "Classical CAN nominal bittiming:\n"); + dummy_can_print_bittiming(dev, &can_priv->bittiming); + netdev_dbg(dev, "\n"); + + if (can_priv->ctrlmode & CAN_CTRLMODE_FD) { + netdev_dbg(dev, "CAN FD databittiming:\n"); + dummy_can_print_bittiming(dev, &can_priv->fd.data_bittiming); + if (can_fd_tdc_is_enabled(can_priv)) { + netdev_dbg(dev, "\tCAN FD TDC:\n"); + dummy_can_print_tdc(dev, &can_priv->fd.tdc); + } + } + netdev_dbg(dev, "\n"); + + if (can_priv->ctrlmode & CAN_CTRLMODE_XL) { + netdev_dbg(dev, "CAN XL databittiming:\n"); + dummy_can_print_bittiming(dev, &can_priv->xl.data_bittiming); + if (can_xl_tdc_is_enabled(can_priv)) { + netdev_dbg(dev, "\tCAN XL TDC:\n"); + dummy_can_print_tdc(dev, &can_priv->xl.tdc); + } + if (can_priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { + netdev_dbg(dev, "\tCAN XL PWM:\n"); + dummy_can_print_pwm(dev, &can_priv->xl.pwm, + &can_priv->xl.data_bittiming); + } + } + netdev_dbg(dev, "\n"); +} + +static int dummy_can_netdev_open(struct net_device *dev) +{ + int ret; + struct can_priv *priv = netdev_priv(dev); + + dummy_can_print_bittiming_info(dev); + netdev_dbg(dev, "error-signalling is %s\n", + str_enabled_disabled(!can_dev_in_xl_only_mode(priv))); + + ret = open_candev(dev); + if (ret) + return ret; + netif_start_queue(dev); + netdev_dbg(dev, "dummy-can is up\n"); + + return 0; +} + +static int dummy_can_netdev_close(struct net_device *dev) +{ + netif_stop_queue(dev); + close_candev(dev); + netdev_dbg(dev, "dummy-can is down\n"); + + return 0; +} + +static netdev_tx_t dummy_can_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + if (can_dev_dropped_skb(dev, skb)) + return NETDEV_TX_OK; + + can_put_echo_skb(skb, dev, 0, 0); + dev->stats.tx_packets++; + dev->stats.tx_bytes += can_get_echo_skb(dev, 0, NULL); + + return NETDEV_TX_OK; +} + +static const struct net_device_ops dummy_can_netdev_ops = { + .ndo_open = dummy_can_netdev_open, + .ndo_stop = dummy_can_netdev_close, + .ndo_start_xmit = dummy_can_start_xmit, +}; + +static const struct ethtool_ops dummy_can_ethtool_ops = { + .get_ts_info = ethtool_op_get_ts_info, +}; + +static int __init dummy_can_init(void) +{ + struct net_device *dev; + struct dummy_can *priv; + int ret; + + dev = alloc_candev(sizeof(*priv), 1); + if (!dev) + return -ENOMEM; + + dev->netdev_ops = &dummy_can_netdev_ops; + dev->ethtool_ops = &dummy_can_ethtool_ops; + priv = netdev_priv(dev); + priv->can.bittiming_const = &dummy_can_bittiming_const; + priv->can.bitrate_max = 20 * MEGA /* BPS */; + priv->can.clock.freq = 160 * MEGA /* Hz */; + priv->can.fd.data_bittiming_const = &dummy_can_fd_databittiming_const; + priv->can.fd.tdc_const = &dummy_can_fd_tdc_const; + priv->can.xl.data_bittiming_const = &dummy_can_xl_databittiming_const; + priv->can.xl.tdc_const = &dummy_can_xl_tdc_const; + priv->can.xl.pwm_const = &dummy_can_pwm_const; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_FD | CAN_CTRLMODE_TDC_AUTO | + CAN_CTRLMODE_RESTRICTED | CAN_CTRLMODE_XL | + CAN_CTRLMODE_XL_TDC_AUTO | CAN_CTRLMODE_XL_TMS; + priv->dev = dev; + + ret = register_candev(priv->dev); + if (ret) { + free_candev(priv->dev); + return ret; + } + + dummy_can = priv; + netdev_dbg(dev, "dummy-can ready\n"); + + return 0; +} + +static void __exit dummy_can_exit(void) +{ + struct net_device *dev = dummy_can->dev; + + netdev_dbg(dev, "dummy-can bye bye\n"); + unregister_candev(dev); + free_candev(dev); +} + +module_init(dummy_can_init); +module_exit(dummy_can_exit); + +MODULE_DESCRIPTION("A dummy CAN driver, mainly to test the netlink interface"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vincent Mailhol "); From 1a620a723853a0f49703c317d52dc6b9602cbaa8 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Wed, 26 Nov 2025 11:16:17 +0100 Subject: [PATCH 16/27] can: raw: instantly reject unsupported CAN frames For real CAN interfaces the CAN_CTRLMODE_FD and CAN_CTRLMODE_XL control modes indicate whether an interface can handle those CAN FD/XL frames. In the case a CAN XL interface is configured in CANXL-only mode with disabled error-signalling neither CAN CC nor CAN FD frames can be sent. The checks are performed on CAN_RAW sockets to give an instant feedback to the user when writing unsupported CAN frames to the interface. Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-16-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- net/can/raw.c | 54 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/net/can/raw.c b/net/can/raw.c index f36a83d3447c..be1ef7cf4204 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -892,20 +892,58 @@ static void raw_put_canxl_vcid(struct raw_sock *ro, struct sk_buff *skb) } } -static unsigned int raw_check_txframe(struct raw_sock *ro, struct sk_buff *skb, int mtu) +static inline bool raw_dev_cc_enabled(struct net_device *dev, + struct can_priv *priv) { - /* Classical CAN -> no checks for flags and device capabilities */ - if (can_is_can_skb(skb)) + /* The CANXL-only mode disables error-signalling on the CAN bus + * which is needed to send CAN CC/FD frames + */ + if (priv) + return !can_dev_in_xl_only_mode(priv); + + /* virtual CAN interfaces always support CAN CC */ + return true; +} + +static inline bool raw_dev_fd_enabled(struct net_device *dev, + struct can_priv *priv) +{ + /* check FD ctrlmode on real CAN interfaces */ + if (priv) + return (priv->ctrlmode & CAN_CTRLMODE_FD); + + /* check MTU for virtual CAN FD interfaces */ + return (READ_ONCE(dev->mtu) >= CANFD_MTU); +} + +static inline bool raw_dev_xl_enabled(struct net_device *dev, + struct can_priv *priv) +{ + /* check XL ctrlmode on real CAN interfaces */ + if (priv) + return (priv->ctrlmode & CAN_CTRLMODE_XL); + + /* check MTU for virtual CAN XL interfaces */ + return can_is_canxl_dev_mtu(READ_ONCE(dev->mtu)); +} + +static unsigned int raw_check_txframe(struct raw_sock *ro, struct sk_buff *skb, + struct net_device *dev) +{ + struct can_priv *priv = safe_candev_priv(dev); + + /* Classical CAN */ + if (can_is_can_skb(skb) && raw_dev_cc_enabled(dev, priv)) return CAN_MTU; - /* CAN FD -> needs to be enabled and a CAN FD or CAN XL device */ + /* CAN FD */ if (ro->fd_frames && can_is_canfd_skb(skb) && - (mtu == CANFD_MTU || can_is_canxl_dev_mtu(mtu))) + raw_dev_fd_enabled(dev, priv)) return CANFD_MTU; - /* CAN XL -> needs to be enabled and a CAN XL device */ + /* CAN XL */ if (ro->xl_frames && can_is_canxl_skb(skb) && - can_is_canxl_dev_mtu(mtu)) + raw_dev_xl_enabled(dev, priv)) return CANXL_MTU; return 0; @@ -961,7 +999,7 @@ static int raw_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) err = -EINVAL; /* check for valid CAN (CC/FD/XL) frame content */ - txmtu = raw_check_txframe(ro, skb, READ_ONCE(dev->mtu)); + txmtu = raw_check_txframe(ro, skb, dev); if (!txmtu) goto free_skb; From b360a13d44db148f6d72fe4f73356726c0663f6a Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Wed, 26 Nov 2025 11:16:18 +0100 Subject: [PATCH 17/27] can: dev: print bitrate error with two decimal digits Increase the resolution when printing the bitrate error and round-up the value to 0.01% in the case the resolution would still provide values which would lead to 0.00%. Suggested-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-17-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/calc_bittiming.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/can/dev/calc_bittiming.c b/drivers/net/can/dev/calc_bittiming.c index 60a505ce69de..cc4022241553 100644 --- a/drivers/net/can/dev/calc_bittiming.c +++ b/drivers/net/can/dev/calc_bittiming.c @@ -153,19 +153,22 @@ int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt, } if (best_bitrate_error) { - /* Error in one-tenth of a percent */ - v64 = (u64)best_bitrate_error * 1000; + /* Error in one-hundredth of a percent */ + v64 = (u64)best_bitrate_error * 10000; do_div(v64, bt->bitrate); bitrate_error = (u32)v64; + /* print at least 0.01% if the error is smaller */ + bitrate_error = max(bitrate_error, 1U); if (bitrate_error > CAN_CALC_MAX_ERROR) { NL_SET_ERR_MSG_FMT(extack, - "bitrate error: %u.%u%% too high", - bitrate_error / 10, bitrate_error % 10); + "bitrate error: %u.%02u%% too high", + bitrate_error / 100, + bitrate_error % 100); return -EINVAL; } NL_SET_ERR_MSG_FMT(extack, - "bitrate error: %u.%u%%", - bitrate_error / 10, bitrate_error % 10); + "bitrate error: %u.%02u%%", + bitrate_error / 100, bitrate_error % 100); } /* real sample point */ From 41c13eaf39932fc79aa1ac245a9b97090fe23d5e Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:26 +0000 Subject: [PATCH 18/27] can: rcar_canfd: Invert reset assert order The two resets are asserted during cleanup in the same order as they were deasserted during probe. Invert the order to restore symmetry. Signed-off-by: Geert Uytterhoeven Reviewed-by: Vincent Mailhol Reviewed-by: Biju Das Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-2-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index 49ab65274b51..cbcc971cda9a 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -2192,8 +2192,8 @@ static int rcar_canfd_probe(struct platform_device *pdev) fail_clk: clk_disable_unprepare(gpriv->clkp); fail_reset: - reset_control_assert(gpriv->rstc1); reset_control_assert(gpriv->rstc2); + reset_control_assert(gpriv->rstc1); fail_dev: return err; } @@ -2214,8 +2214,8 @@ static void rcar_canfd_remove(struct platform_device *pdev) /* Enter global sleep mode */ rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); clk_disable_unprepare(gpriv->clkp); - reset_control_assert(gpriv->rstc1); reset_control_assert(gpriv->rstc2); + reset_control_assert(gpriv->rstc1); } static int __maybe_unused rcar_canfd_suspend(struct device *dev) From 790ec4c453890f1221ea595674a1206bbee41dc4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:27 +0000 Subject: [PATCH 19/27] can: rcar_canfd: Invert global vs. channel teardown Global state is initialized and torn down before per-channel state. Invert the order to restore symmetry. Signed-off-by: Geert Uytterhoeven Reviewed-by: Vincent Mailhol Reviewed-by: Biju Das Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-3-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index cbcc971cda9a..15358eefa8fa 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -2203,14 +2203,14 @@ static void rcar_canfd_remove(struct platform_device *pdev) struct rcar_canfd_global *gpriv = platform_get_drvdata(pdev); u32 ch; - rcar_canfd_reset_controller(gpriv); - rcar_canfd_disable_global_interrupts(gpriv); - for_each_set_bit(ch, &gpriv->channels_mask, gpriv->info->max_channels) { rcar_canfd_disable_channel_interrupts(gpriv->ch[ch]); rcar_canfd_channel_remove(gpriv, ch); } + rcar_canfd_disable_global_interrupts(gpriv); + rcar_canfd_reset_controller(gpriv); + /* Enter global sleep mode */ rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); clk_disable_unprepare(gpriv->clkp); From eda3d6c8d784835cec86f42b3f8118c9eb0cc58c Mon Sep 17 00:00:00 2001 From: Biju Das Date: Mon, 24 Nov 2025 10:28:28 +0000 Subject: [PATCH 20/27] can: rcar_canfd: Use devm_clk_get_optional() for RAM clk Replace devm_clk_get_optional_enabled()->devm_clk_get_optional() as the RAM clk needs to be enabled in resume for proper operation in STR mode for RZ/G3E SoC. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20251124102837.106973-4-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index 15358eefa8fa..3f71adce6f56 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -468,6 +468,7 @@ struct rcar_canfd_global { struct platform_device *pdev; /* Respective platform device */ struct clk *clkp; /* Peripheral clock */ struct clk *can_clk; /* fCAN clock */ + struct clk *clk_ram; /* Clock RAM */ unsigned long channels_mask; /* Enabled channels mask */ bool extclk; /* CANFD or Ext clock */ bool fdmode; /* CAN FD or Classical CAN only mode */ @@ -1975,7 +1976,6 @@ static int rcar_canfd_probe(struct platform_device *pdev) u32 rule_entry = 0; bool fdmode = true; /* CAN FD only mode - default */ char name[9] = "channelX"; - struct clk *clk_ram; int i; info = of_device_get_match_data(dev); @@ -2065,10 +2065,10 @@ static int rcar_canfd_probe(struct platform_device *pdev) gpriv->extclk = gpriv->info->external_clk; } - clk_ram = devm_clk_get_optional_enabled(dev, "ram_clk"); - if (IS_ERR(clk_ram)) - return dev_err_probe(dev, PTR_ERR(clk_ram), - "cannot get enabled ram clock\n"); + gpriv->clk_ram = devm_clk_get_optional(dev, "ram_clk"); + if (IS_ERR(gpriv->clk_ram)) + return dev_err_probe(dev, PTR_ERR(gpriv->clk_ram), + "cannot get ram clock\n"); addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(addr)) { @@ -2134,10 +2134,18 @@ static int rcar_canfd_probe(struct platform_device *pdev) goto fail_reset; } + /* Enable RAM clock */ + err = clk_prepare_enable(gpriv->clk_ram); + if (err) { + dev_err(dev, "failed to enable RAM clock: %pe\n", + ERR_PTR(err)); + goto fail_clk; + } + err = rcar_canfd_reset_controller(gpriv); if (err) { dev_err(dev, "reset controller failed: %pe\n", ERR_PTR(err)); - goto fail_clk; + goto fail_ram_clk; } /* Controller in Global reset & Channel reset mode */ @@ -2189,6 +2197,8 @@ static int rcar_canfd_probe(struct platform_device *pdev) rcar_canfd_channel_remove(gpriv, ch); fail_mode: rcar_canfd_disable_global_interrupts(gpriv); +fail_ram_clk: + clk_disable_unprepare(gpriv->clk_ram); fail_clk: clk_disable_unprepare(gpriv->clkp); fail_reset: @@ -2213,6 +2223,7 @@ static void rcar_canfd_remove(struct platform_device *pdev) /* Enter global sleep mode */ rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); + clk_disable_unprepare(gpriv->clk_ram); clk_disable_unprepare(gpriv->clkp); reset_control_assert(gpriv->rstc2); reset_control_assert(gpriv->rstc1); From fa5f4ec8fff8bc587a2cbf7101303306e045c11f Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:29 +0000 Subject: [PATCH 21/27] can: rcar_canfd: Extract rcar_canfd_global_{,de}init() Extract the code to (de)initialize global state into separate functions, for future reuse. Signed-off-by: Geert Uytterhoeven Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-5-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 182 +++++++++++++++++------------- 1 file changed, 104 insertions(+), 78 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index 3f71adce6f56..f18efaf81061 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -1961,21 +1961,120 @@ static void rcar_canfd_channel_remove(struct rcar_canfd_global *gpriv, u32 ch) } } +static int rcar_canfd_global_init(struct rcar_canfd_global *gpriv) +{ + struct device *dev = &gpriv->pdev->dev; + u32 rule_entry = 0; + u32 ch, sts; + int err; + + err = reset_control_reset(gpriv->rstc1); + if (err) + return err; + + err = reset_control_reset(gpriv->rstc2); + if (err) + goto fail_reset1; + + /* Enable peripheral clock for register access */ + err = clk_prepare_enable(gpriv->clkp); + if (err) { + dev_err(dev, "failed to enable peripheral clock: %pe\n", + ERR_PTR(err)); + goto fail_reset2; + } + + /* Enable RAM clock */ + err = clk_prepare_enable(gpriv->clk_ram); + if (err) { + dev_err(dev, + "failed to enable RAM clock, error %d\n", err); + goto fail_clk; + } + + err = rcar_canfd_reset_controller(gpriv); + if (err) { + dev_err(dev, "reset controller failed: %pe\n", ERR_PTR(err)); + goto fail_ram_clk; + } + + /* Controller in Global reset & Channel reset mode */ + rcar_canfd_configure_controller(gpriv); + + /* Configure per channel attributes */ + for_each_set_bit(ch, &gpriv->channels_mask, gpriv->info->max_channels) { + /* Configure Channel's Rx fifo */ + rcar_canfd_configure_rx(gpriv, ch); + + /* Configure Channel's Tx (Common) fifo */ + rcar_canfd_configure_tx(gpriv, ch); + + /* Configure receive rules */ + rcar_canfd_configure_afl_rules(gpriv, ch, rule_entry); + rule_entry += RCANFD_CHANNEL_NUMRULES; + } + + /* Configure common interrupts */ + rcar_canfd_enable_global_interrupts(gpriv); + + /* Start Global operation mode */ + rcar_canfd_update_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GMDC_MASK, + RCANFD_GCTR_GMDC_GOPM); + + /* Verify mode change */ + err = readl_poll_timeout((gpriv->base + RCANFD_GSTS), sts, + !(sts & RCANFD_GSTS_GNOPM), 2, 500000); + if (err) { + dev_err(dev, "global operational mode failed\n"); + goto fail_mode; + } + + return 0; + +fail_mode: + rcar_canfd_disable_global_interrupts(gpriv); +fail_ram_clk: + clk_disable_unprepare(gpriv->clk_ram); +fail_clk: + clk_disable_unprepare(gpriv->clkp); +fail_reset2: + reset_control_assert(gpriv->rstc2); +fail_reset1: + reset_control_assert(gpriv->rstc1); + return err; +} + +static void rcar_canfd_global_deinit(struct rcar_canfd_global *gpriv, bool full) +{ + rcar_canfd_disable_global_interrupts(gpriv); + + if (full) { + rcar_canfd_reset_controller(gpriv); + + /* Enter global sleep mode */ + rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); + } + + clk_disable_unprepare(gpriv->clk_ram); + clk_disable_unprepare(gpriv->clkp); + reset_control_assert(gpriv->rstc2); + reset_control_assert(gpriv->rstc1); +} + static int rcar_canfd_probe(struct platform_device *pdev) { struct phy *transceivers[RCANFD_NUM_CHANNELS] = { NULL, }; const struct rcar_canfd_hw_info *info; struct device *dev = &pdev->dev; void __iomem *addr; - u32 sts, ch, fcan_freq; struct rcar_canfd_global *gpriv; struct device_node *of_child; unsigned long channels_mask = 0; int err, ch_irq, g_irq; int g_err_irq, g_recc_irq; - u32 rule_entry = 0; bool fdmode = true; /* CAN FD only mode - default */ char name[9] = "channelX"; + u32 ch, fcan_freq; int i; info = of_device_get_match_data(dev); @@ -2117,67 +2216,9 @@ static int rcar_canfd_probe(struct platform_device *pdev) } } - err = reset_control_reset(gpriv->rstc1); + err = rcar_canfd_global_init(gpriv); if (err) - goto fail_dev; - err = reset_control_reset(gpriv->rstc2); - if (err) { - reset_control_assert(gpriv->rstc1); - goto fail_dev; - } - - /* Enable peripheral clock for register access */ - err = clk_prepare_enable(gpriv->clkp); - if (err) { - dev_err(dev, "failed to enable peripheral clock: %pe\n", - ERR_PTR(err)); - goto fail_reset; - } - - /* Enable RAM clock */ - err = clk_prepare_enable(gpriv->clk_ram); - if (err) { - dev_err(dev, "failed to enable RAM clock: %pe\n", - ERR_PTR(err)); - goto fail_clk; - } - - err = rcar_canfd_reset_controller(gpriv); - if (err) { - dev_err(dev, "reset controller failed: %pe\n", ERR_PTR(err)); - goto fail_ram_clk; - } - - /* Controller in Global reset & Channel reset mode */ - rcar_canfd_configure_controller(gpriv); - - /* Configure per channel attributes */ - for_each_set_bit(ch, &gpriv->channels_mask, info->max_channels) { - /* Configure Channel's Rx fifo */ - rcar_canfd_configure_rx(gpriv, ch); - - /* Configure Channel's Tx (Common) fifo */ - rcar_canfd_configure_tx(gpriv, ch); - - /* Configure receive rules */ - rcar_canfd_configure_afl_rules(gpriv, ch, rule_entry); - rule_entry += RCANFD_CHANNEL_NUMRULES; - } - - /* Configure common interrupts */ - rcar_canfd_enable_global_interrupts(gpriv); - - /* Start Global operation mode */ - rcar_canfd_update_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GMDC_MASK, - RCANFD_GCTR_GMDC_GOPM); - - /* Verify mode change */ - err = readl_poll_timeout((gpriv->base + RCANFD_GSTS), sts, - !(sts & RCANFD_GSTS_GNOPM), 2, 500000); - if (err) { - dev_err(dev, "global operational mode failed\n"); goto fail_mode; - } for_each_set_bit(ch, &gpriv->channels_mask, info->max_channels) { err = rcar_canfd_channel_probe(gpriv, ch, fcan_freq, @@ -2196,14 +2237,7 @@ static int rcar_canfd_probe(struct platform_device *pdev) for_each_set_bit(ch, &gpriv->channels_mask, info->max_channels) rcar_canfd_channel_remove(gpriv, ch); fail_mode: - rcar_canfd_disable_global_interrupts(gpriv); -fail_ram_clk: - clk_disable_unprepare(gpriv->clk_ram); -fail_clk: - clk_disable_unprepare(gpriv->clkp); -fail_reset: - reset_control_assert(gpriv->rstc2); - reset_control_assert(gpriv->rstc1); + rcar_canfd_global_deinit(gpriv, false); fail_dev: return err; } @@ -2218,15 +2252,7 @@ static void rcar_canfd_remove(struct platform_device *pdev) rcar_canfd_channel_remove(gpriv, ch); } - rcar_canfd_disable_global_interrupts(gpriv); - rcar_canfd_reset_controller(gpriv); - - /* Enter global sleep mode */ - rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); - clk_disable_unprepare(gpriv->clk_ram); - clk_disable_unprepare(gpriv->clkp); - reset_control_assert(gpriv->rstc2); - reset_control_assert(gpriv->rstc1); + rcar_canfd_global_deinit(gpriv, true); } static int __maybe_unused rcar_canfd_suspend(struct device *dev) From ddf9bbf22b70d6b1a10e7d45de8ec3b94de7c54c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:30 +0000 Subject: [PATCH 22/27] can: rcar_canfd: Invert CAN clock and close_candev() order The CAN clock is enabled before calling open_candev(), and disabled before calling close_candev(). Invert the order of the latter, to restore symmetry. Signed-off-by: Geert Uytterhoeven Reviewed-by: Biju Das Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-6-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index f18efaf81061..9550f1e74994 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -1570,8 +1570,8 @@ static int rcar_canfd_close(struct net_device *ndev) netif_stop_queue(ndev); rcar_canfd_stop(ndev); napi_disable(&priv->napi); - clk_disable_unprepare(gpriv->can_clk); close_candev(ndev); + clk_disable_unprepare(gpriv->can_clk); phy_power_off(priv->transceiver); return 0; } From 161266c754e71d979be994967984c9fdcab74090 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:31 +0000 Subject: [PATCH 23/27] can: rcar_canfd: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the Renesas R-Car CAN-FD driver from SIMPLE_DEV_PM_OPS() to DEFINE_SIMPLE_DEV_PM_OPS() and pm_sleep_ptr(). This lets us drop the __maybe_unused annotations from its suspend and resume callbacks, and reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Geert Uytterhoeven Reviewed-by: Biju Das Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-7-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index 9550f1e74994..1d6b2f7efa8c 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -2255,18 +2255,18 @@ static void rcar_canfd_remove(struct platform_device *pdev) rcar_canfd_global_deinit(gpriv, true); } -static int __maybe_unused rcar_canfd_suspend(struct device *dev) +static int rcar_canfd_suspend(struct device *dev) { return 0; } -static int __maybe_unused rcar_canfd_resume(struct device *dev) +static int rcar_canfd_resume(struct device *dev) { return 0; } -static SIMPLE_DEV_PM_OPS(rcar_canfd_pm_ops, rcar_canfd_suspend, - rcar_canfd_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(rcar_canfd_pm_ops, rcar_canfd_suspend, + rcar_canfd_resume); static const __maybe_unused struct of_device_id rcar_canfd_of_table[] = { { .compatible = "renesas,r8a779a0-canfd", .data = &rcar_gen4_hw_info }, @@ -2283,7 +2283,7 @@ static struct platform_driver rcar_canfd_driver = { .driver = { .name = RCANFD_DRV_NAME, .of_match_table = of_match_ptr(rcar_canfd_of_table), - .pm = &rcar_canfd_pm_ops, + .pm = pm_sleep_ptr(&rcar_canfd_pm_ops), }, .probe = rcar_canfd_probe, .remove = rcar_canfd_remove, From 3a34330f6339641b7f5b76b066385f56c114490f Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Nov 2025 10:28:32 +0000 Subject: [PATCH 24/27] can: rcar_canfd: Add suspend/resume support On R-Car Gen3 using PSCI, s2ram powers down the SoC. After resume, the CAN-FD interface no longer works. Trying to bring it up again fails: # ip link set can0 up RTNETLINK answers: Connection timed out # dmesg ... channel 0 communication state failed Fix this by populating the (currently empty) suspend and resume callbacks, to stop/start the individual CAN-FD channels, and (de)initialize the CAN-FD controller. Signed-off-by: Geert Uytterhoeven Tested-by: Biju Das Signed-off-by: Biju Das Link: https://patch.msgid.link/20251124102837.106973-8-biju.das.jz@bp.renesas.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index 1d6b2f7efa8c..ce75948e6af6 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -2257,11 +2257,64 @@ static void rcar_canfd_remove(struct platform_device *pdev) static int rcar_canfd_suspend(struct device *dev) { + struct rcar_canfd_global *gpriv = dev_get_drvdata(dev); + int err; + u32 ch; + + for_each_set_bit(ch, &gpriv->channels_mask, gpriv->info->max_channels) { + struct rcar_canfd_channel *priv = gpriv->ch[ch]; + struct net_device *ndev = priv->ndev; + + if (!netif_running(ndev)) + continue; + + netif_device_detach(ndev); + + err = rcar_canfd_close(ndev); + if (err) { + netdev_err(ndev, "rcar_canfd_close() failed %pe\n", + ERR_PTR(err)); + return err; + } + + priv->can.state = CAN_STATE_SLEEPING; + } + + /* TODO Skip if wake-up (which is not yet supported) is enabled */ + rcar_canfd_global_deinit(gpriv, false); + return 0; } static int rcar_canfd_resume(struct device *dev) { + struct rcar_canfd_global *gpriv = dev_get_drvdata(dev); + int err; + u32 ch; + + err = rcar_canfd_global_init(gpriv); + if (err) { + dev_err(dev, "rcar_canfd_global_init() failed %pe\n", ERR_PTR(err)); + return err; + } + + for_each_set_bit(ch, &gpriv->channels_mask, gpriv->info->max_channels) { + struct rcar_canfd_channel *priv = gpriv->ch[ch]; + struct net_device *ndev = priv->ndev; + + if (!netif_running(ndev)) + continue; + + err = rcar_canfd_open(ndev); + if (err) { + netdev_err(ndev, "rcar_canfd_open() failed %pe\n", + ERR_PTR(err)); + return err; + } + + netif_device_attach(ndev); + } + return 0; } From 07688a882f8ee6228e63774f23dccab6977f1bdf Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Wed, 19 Nov 2025 14:50:02 +0100 Subject: [PATCH 25/27] MAINTAINERS: Add myself as m_can maintainer As I have contributed to the m_can driver over the past years, I would like to continue maintaining the driver. As Chandrasekar is currently not responsive, I will replace him as the maintainer of the driver. Signed-off-by: Markus Schneider-Pargmann Link: https://patch.msgid.link/20251119-topic-mcan-reviewer-v6-18-v2-1-f842c3094b18@baylibre.com Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index e9a8d945632b..92951d196d27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15413,7 +15413,7 @@ F: drivers/net/phy/mxl-86110.c F: drivers/net/phy/mxl-gpy.c MCAN MMIO DEVICE DRIVER -M: Chandrasekar Ramakrishnan +M: Markus Schneider-Pargmann L: linux-can@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/can/bosch,m_can.yaml From d20103d8f880b0d716ae83dba6d1d150f5790a9c Mon Sep 17 00:00:00 2001 From: Markus Schneider-Pargmann Date: Wed, 19 Nov 2025 14:50:03 +0100 Subject: [PATCH 26/27] MAINTAINERS: Simplify m_can section Simplify the section by using the whole m_can directory. This includes a few new files, e.g. Kconfig, Makefile, m_can_pci.c and tcan4x5x* which are all closely coupled to the m_can driver core. Signed-off-by: Markus Schneider-Pargmann Link: https://patch.msgid.link/20251119-topic-mcan-reviewer-v6-18-v2-2-f842c3094b18@baylibre.com Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 92951d196d27..cb7168eb0ce4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15412,14 +15412,12 @@ S: Supported F: drivers/net/phy/mxl-86110.c F: drivers/net/phy/mxl-gpy.c -MCAN MMIO DEVICE DRIVER +MCAN DEVICE DRIVER M: Markus Schneider-Pargmann L: linux-can@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/can/bosch,m_can.yaml -F: drivers/net/can/m_can/m_can.c -F: drivers/net/can/m_can/m_can.h -F: drivers/net/can/m_can/m_can_platform.c +F: drivers/net/can/m_can/ MCBA MICROCHIP CAN BUS ANALYZER TOOL DRIVER R: Yasushi SHOJI From 9aea35eb98a6560daf85a2ae9cbd482a66e4d076 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Fri, 21 Nov 2025 13:42:30 +0000 Subject: [PATCH 27/27] dt-bindings: can: mpfs: document resets The CAN cores on Polarfire SoC both have a reset. The platform firmware brings both cores out of reset, but the linux driver must use them during normal operation. The resets should have been made required, but this is one of the things that can happen when the binding is written without driver support. Fixes: c878d518d7b6 ("dt-bindings: can: mpfs: document the mpfs CAN controller") Signed-off-by: Conor Dooley Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251121-sample-footsore-743d81772efc@spud Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/microchip,mpfs-can.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/net/can/microchip,mpfs-can.yaml b/Documentation/devicetree/bindings/net/can/microchip,mpfs-can.yaml index 1219c5cb601f..519a11fbe972 100644 --- a/Documentation/devicetree/bindings/net/can/microchip,mpfs-can.yaml +++ b/Documentation/devicetree/bindings/net/can/microchip,mpfs-can.yaml @@ -32,11 +32,15 @@ properties: - description: AHB peripheral clock - description: CAN bus clock + resets: + maxItems: 1 + required: - compatible - reg - interrupts - clocks + - resets additionalProperties: false @@ -46,6 +50,7 @@ examples: compatible = "microchip,mpfs-can"; reg = <0x2010c000 0x1000>; clocks = <&clkcfg 17>, <&clkcfg 37>; + resets = <&clkcfg 17>; interrupt-parent = <&plic>; interrupts = <56>; };