diff mbox series

[v10,4/7] can: Add Nuvoton NCT6694 CANFD support

Message ID 20250423094058.1656204-5-tmyu0@nuvoton.com
State New
Headers show
Series Add Nuvoton NCT6694 MFD drivers | expand

Commit Message

Ming Yu April 23, 2025, 9:40 a.m. UTC
From: Ming Yu <tmyu0@nuvoton.com>

This driver supports Socket CANFD functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                         |   1 +
 drivers/net/can/usb/Kconfig         |  11 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 814 ++++++++++++++++++++++++++++
 4 files changed, 827 insertions(+)
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c

Comments

Marc Kleine-Budde May 3, 2025, 1:57 p.m. UTC | #1
On 23.04.2025 17:40:55, a0282524688@gmail.com wrote:
> From: Ming Yu <tmyu0@nuvoton.com>
> 
> This driver supports Socket CANFD functionality for NCT6694 MFD
> device based on USB interface.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>

The destroy functions nct6694_canfd_close() and nct6694_canfd_remove()
are not the exact inverse of their init functions. Se comments inline.

Please fix and add:

Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>

Feel free to mainline this patch as part of the series outside of the
linux-can-next tree. Better ask the netdev maintainers for their OK, too.

What about transceiver delay compensation for higher CAN-FD bitrates?
How does you device handle these?

> ---
>  MAINTAINERS                         |   1 +
>  drivers/net/can/usb/Kconfig         |  11 +
>  drivers/net/can/usb/Makefile        |   1 +
>  drivers/net/can/usb/nct6694_canfd.c | 814 ++++++++++++++++++++++++++++
>  4 files changed, 827 insertions(+)
>  create mode 100644 drivers/net/can/usb/nct6694_canfd.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 751b9108524a..ee8583edc2d2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17364,6 +17364,7 @@ S:	Supported
>  F:	drivers/gpio/gpio-nct6694.c
>  F:	drivers/i2c/busses/i2c-nct6694.c
>  F:	drivers/mfd/nct6694.c
> +F:	drivers/net/can/usb/nct6694_canfd.c
>  F:	include/linux/mfd/nct6694.h
>  
>  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
> index 9dae0c71a2e1..759e724a67cf 100644
> --- a/drivers/net/can/usb/Kconfig
> +++ b/drivers/net/can/usb/Kconfig
> @@ -133,6 +133,17 @@ config CAN_MCBA_USB
>  	  This driver supports the CAN BUS Analyzer interface
>  	  from Microchip (http://www.microchip.com/development-tools/).
>  
> +config CAN_NCT6694
> +	tristate "Nuvoton NCT6694 Socket CANfd support"
> +	depends on MFD_NCT6694
> +	select CAN_RX_OFFLOAD
> +	help
> +	  If you say yes to this option, support will be included for Nuvoton
> +	  NCT6694, a USB device to socket CANfd controller.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called nct6694_canfd.
> +
>  config CAN_PEAK_USB
>  	tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
>  	help
> diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> index 8b11088e9a59..fcafb1ac262e 100644
> --- a/drivers/net/can/usb/Makefile
> +++ b/drivers/net/can/usb/Makefile
> @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
>  obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
>  obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
>  obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
>  obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
>  obj-$(CONFIG_CAN_UCAN) += ucan.o
> diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> new file mode 100644
> index 000000000000..9cf6230ffb7d
> --- /dev/null
> +++ b/drivers/net/can/usb/nct6694_canfd.c
> @@ -0,0 +1,814 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/can/dev.h>
> +#include <linux/can/rx-offload.h>
> +#include <linux/ethtool.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +
> +#define DEVICE_NAME "nct6694-canfd"
> +
> +/* USB command module type for NCT6694 CANfd controller.
> + * This defines the module type used for communication with the NCT6694
> + * CANfd controller over the USB interface.
> + */
> +#define NCT6694_CANFD_MOD			0x05
> +
> +/* Command 00h - CAN Setting and Initialization */
> +#define NCT6694_CANFD_SETTING			0x00
> +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1	BIT(0)
> +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL2	BIT(1)
> +#define NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP	BIT(2)
> +#define NCT6694_CANFD_SETTING_CTRL1_MON		BIT(0)
> +#define NCT6694_CANFD_SETTING_CTRL1_NISO	BIT(1)
> +#define NCT6694_CANFD_SETTING_CTRL1_LBCK	BIT(2)
> +#define NCT6694_CANFD_SETTING_NBTP_NTSEG2	GENMASK(6, 0)
> +#define NCT6694_CANFD_SETTING_NBTP_NTSEG1	GENMASK(15, 8)
> +#define NCT6694_CANFD_SETTING_NBTP_NBRP		GENMASK(24, 16)
> +#define NCT6694_CANFD_SETTING_NBTP_NSJW		GENMASK(31, 25)
> +#define NCT6694_CANFD_SETTING_DBTP_DSJW		GENMASK(3, 0)
> +#define NCT6694_CANFD_SETTING_DBTP_DTSEG2	GENMASK(7, 4)
> +#define NCT6694_CANFD_SETTING_DBTP_DTSEG1	GENMASK(12, 8)
> +#define NCT6694_CANFD_SETTING_DBTP_DBRP		GENMASK(20, 16)
> +
> +/* Command 01h - CAN Information */
> +#define NCT6694_CANFD_INFORMATION		0x01
> +#define NCT6694_CANFD_INFORMATION_SEL		0x00
> +
> +/* Command 02h - CAN Event */
> +#define NCT6694_CANFD_EVENT			0x02
> +#define NCT6694_CANFD_EVENT_SEL(idx, mask)	\
> +	((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
> +
> +#define NCT6694_CANFD_EVENT_MASK		GENMASK(5, 0)
> +#define NCT6694_CANFD_EVT_TX_FIFO_EMPTY		BIT(7)	/* Read-clear */
> +#define NCT6694_CANFD_EVT_RX_DATA_LOST		BIT(5)	/* Read-clear */
> +#define NCT6694_CANFD_EVT_RX_DATA_IN		BIT(7)	/* Read-clear*/
                                                                    ^^
add a space

> +
> +/* Command 10h - CAN Deliver */
> +#define NCT6694_CANFD_DELIVER			0x10
> +#define NCT6694_CANFD_DELIVER_SEL(buf_cnt)	\
> +	((buf_cnt) & 0xFF)
> +
> +/* Command 11h - CAN Receive */
> +#define NCT6694_CANFD_RECEIVE			0x11
> +#define NCT6694_CANFD_RECEIVE_SEL(idx, buf_cnt)	\
> +	((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
> +
> +#define NCT6694_CANFD_FRAME_TAG(idx)		(0xC0 | (idx))
> +#define NCT6694_CANFD_FRAME_FLAG_EFF		BIT(0)
> +#define NCT6694_CANFD_FRAME_FLAG_RTR		BIT(1)
> +#define NCT6694_CANFD_FRAME_FLAG_FD		BIT(2)
> +#define NCT6694_CANFD_FRAME_FLAG_BRS		BIT(3)
> +#define NCT6694_CANFD_FRAME_FLAG_ERR		BIT(4)
> +
> +#define NCT6694_NAPI_WEIGHT			32
> +
> +enum nct6694_event_err {
> +	NCT6694_CANFD_EVT_ERR_NO_ERROR = 0,
> +	NCT6694_CANFD_EVT_ERR_CRC_ERROR,
> +	NCT6694_CANFD_EVT_ERR_STUFF_ERROR,
> +	NCT6694_CANFD_EVT_ERR_ACK_ERROR,
> +	NCT6694_CANFD_EVT_ERR_FORM_ERROR,
> +	NCT6694_CANFD_EVT_ERR_BIT_ERROR,
> +	NCT6694_CANFD_EVT_ERR_TIMEOUT_ERROR,
> +	NCT6694_CANFD_EVT_ERR_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> +	NCT6694_CANFD_EVT_STS_ERROR_ACTIVE = 0,
> +	NCT6694_CANFD_EVT_STS_ERROR_PASSIVE,
> +	NCT6694_CANFD_EVT_STS_BUS_OFF,
> +	NCT6694_CANFD_EVT_STS_WARNING,
> +};
> +
> +struct __packed nct6694_canfd_setting {
> +	__le32 nbr;
> +	__le32 dbr;
> +	u8 active;
> +	u8 reserved[3];
> +	__le16 ctrl1;
> +	__le16 ctrl2;
> +	__le32 nbtp;
> +	__le32 dbtp;
> +};
> +
> +struct __packed nct6694_canfd_information {
> +	u8 tx_fifo_cnt;
> +	u8 rx_fifo_cnt;
> +	u8 reserved[2];
> +	__le32 can_clk;
> +};
> +
> +struct __packed nct6694_canfd_event {
> +	u8 err;
> +	u8 status;
> +	u8 tx_evt;
> +	u8 rx_evt;
> +	u8 rec;
> +	u8 tec;
> +	u8 reserved[2];
> +};
> +
> +struct __packed nct6694_canfd_frame {
> +	u8 tag;
> +	u8 flag;
> +	u8 reserved;
> +	u8 length;
> +	__le32 id;
> +	u8 data[CANFD_MAX_DLEN];
> +};
> +
> +struct nct6694_canfd_priv {
> +	struct can_priv can;	/* must be the first member */
> +	struct can_rx_offload offload;
> +	struct net_device *ndev;
> +	struct nct6694 *nct6694;
> +	struct workqueue_struct *wq;
> +	struct work_struct tx_work;
> +	struct nct6694_canfd_frame tx;
> +	struct nct6694_canfd_frame rx;
> +	struct nct6694_canfd_event event[2];
> +	struct can_berr_counter bec;
> +};
> +
> +static inline struct nct6694_canfd_priv *rx_offload_to_priv(struct can_rx_offload *offload)
> +{
> +	return container_of(offload, struct nct6694_canfd_priv, offload);
> +}
> +
> +static const struct can_bittiming_const nct6694_canfd_bittiming_nominal_const = {
> +	.name = DEVICE_NAME,
> +	.tseg1_min = 1,
> +	.tseg1_max = 256,
> +	.tseg2_min = 1,
> +	.tseg2_max = 128,
> +	.sjw_max = 128,
> +	.brp_min = 1,
> +	.brp_max = 512,
> +	.brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_canfd_bittiming_data_const = {
> +	.name = DEVICE_NAME,
> +	.tseg1_min = 1,
> +	.tseg1_max = 32,
> +	.tseg2_min = 1,
> +	.tseg2_max = 16,
> +	.sjw_max = 16,
> +	.brp_min = 1,
> +	.brp_max = 32,
> +	.brp_inc = 1,
> +};
> +
> +static void nct6694_canfd_rx_offload(struct can_rx_offload *offload,
> +				     struct sk_buff *skb)
> +{
> +	struct nct6694_canfd_priv *priv = rx_offload_to_priv(offload);
> +	int ret;
> +
> +	ret = can_rx_offload_queue_tail(offload, skb);
> +	if (ret)
> +		priv->ndev->stats.rx_fifo_errors++;
> +}
> +
> +static void nct6694_canfd_handle_lost_msg(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +
> +	netdev_dbg(ndev, "RX FIFO overflow, message(s) lost.\n");
> +
> +	stats->rx_errors++;
> +	stats->rx_over_errors++;
> +
> +	skb = alloc_can_err_skb(ndev, &cf);
> +	if (!skb)
> +		return;
> +
> +	cf->can_id |= CAN_ERR_CRTL;
> +	cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> +
> +	nct6694_canfd_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt)
> +{
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct nct6694_canfd_frame *frame = &priv->rx;
> +	const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_RECEIVE,
> +		.sel = NCT6694_CANFD_RECEIVE_SEL(ndev->dev_port, 1),
> +		.len = cpu_to_le16(sizeof(*frame))
> +	};
> +	struct sk_buff *skb;
> +	int ret;
> +
> +	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
> +	if (ret)
> +		return;
> +
> +	if (frame->flag & NCT6694_CANFD_FRAME_FLAG_FD) {
> +		struct canfd_frame *cfd;
> +
> +		skb = alloc_canfd_skb(priv->ndev, &cfd);
> +		if (!skb) {
> +			stats->rx_dropped++;
> +			return;
> +		}
> +
> +		cfd->can_id = le32_to_cpu(frame->id);
> +		cfd->len = canfd_sanitize_len(frame->length);
> +		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
> +			cfd->can_id |= CAN_EFF_FLAG;
> +		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_BRS)
> +			cfd->flags |= CANFD_BRS;
> +		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_ERR)
> +			cfd->flags |= CANFD_ESI;
> +
> +		memcpy(cfd->data, frame->data, cfd->len);
> +	} else {
> +		struct can_frame *cf;
> +
> +		skb = alloc_can_skb(priv->ndev, &cf);
> +		if (!skb) {
> +			stats->rx_dropped++;
> +			return;
> +		}
> +
> +		cf->can_id = le32_to_cpu(frame->id);
> +		cf->len = can_cc_dlc2len(frame->length);
> +		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
> +			cf->can_id |= CAN_EFF_FLAG;
> +
> +		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_RTR)
> +			cf->can_id |= CAN_RTR_FLAG;
> +		else
> +			memcpy(cf->data, frame->data, cf->len);
> +	}
> +
> +	nct6694_canfd_rx_offload(&priv->offload, skb);
> +}
> +
> +static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
> +					  struct can_berr_counter *bec)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +
> +	*bec = priv->bec;
> +
> +	return 0;
> +}
> +
> +static void nct6694_canfd_handle_state_change(struct net_device *ndev, u8 status)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	enum can_state new_state, rx_state, tx_state;
> +	struct can_berr_counter bec;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +
> +	nct6694_canfd_get_berr_counter(ndev, &bec);
> +	can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);
> +
> +	new_state = max(tx_state, rx_state);
> +
> +	/* state hasn't changed */
> +	if (new_state == priv->can.state)
> +		return;
> +
> +	skb = alloc_can_err_skb(ndev, &cf);
> +
> +	can_change_state(ndev, cf, tx_state, rx_state);
> +
> +	if (new_state == CAN_STATE_BUS_OFF) {
> +		can_bus_off(ndev);
> +	} else if (cf) {
> +		cf->can_id |= CAN_ERR_CNT;
> +		cf->data[6] = bec.txerr;
> +		cf->data[7] = bec.rxerr;
> +	}
> +
> +	if (skb)
> +		nct6694_canfd_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_err)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +
> +	priv->can.can_stats.bus_error++;
> +
> +	skb = alloc_can_err_skb(ndev, &cf);
> +	if (cf)
> +		cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> +	switch (bus_err) {
> +	case NCT6694_CANFD_EVT_ERR_CRC_ERROR:
> +		netdev_dbg(ndev, "CRC error\n");
> +		ndev->stats.rx_errors++;
> +		if (cf)
> +			cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
> +		break;
> +
> +	case NCT6694_CANFD_EVT_ERR_STUFF_ERROR:
> +		netdev_dbg(ndev, "Stuff error\n");
> +		ndev->stats.rx_errors++;
> +		if (cf)
> +			cf->data[2] |= CAN_ERR_PROT_STUFF;
> +		break;
> +
> +	case NCT6694_CANFD_EVT_ERR_ACK_ERROR:
> +		netdev_dbg(ndev, "Ack error\n");
> +		ndev->stats.tx_errors++;
> +		if (cf) {
> +			cf->can_id |= CAN_ERR_ACK;
> +			cf->data[2] |= CAN_ERR_PROT_TX;
> +		}
> +		break;
> +
> +	case NCT6694_CANFD_EVT_ERR_FORM_ERROR:
> +		netdev_dbg(ndev, "Form error\n");
> +		ndev->stats.rx_errors++;
> +		if (cf)
> +			cf->data[2] |= CAN_ERR_PROT_FORM;
> +		break;
> +
> +	case NCT6694_CANFD_EVT_ERR_BIT_ERROR:
> +		netdev_dbg(ndev, "Bit error\n");
> +		ndev->stats.tx_errors++;
> +		if (cf)
> +			cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	if (skb)
> +		nct6694_canfd_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_canfd_handle_tx(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +
> +	stats->tx_bytes += can_rx_offload_get_echo_skb_queue_tail(&priv->offload,
> +								  0, NULL);
> +	stats->tx_packets++;
> +	netif_wake_queue(ndev);
> +}
> +
> +static irqreturn_t nct6694_canfd_irq(int irq, void *data)
> +{
> +	struct net_device *ndev = data;
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct nct6694_canfd_event *event = &priv->event[ndev->dev_port];
> +	const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_EVENT,
> +		.sel = NCT6694_CANFD_EVENT_SEL(ndev->dev_port, NCT6694_CANFD_EVENT_MASK),
> +		.len = cpu_to_le16(sizeof(priv->event))
> +	};
> +	irqreturn_t handled = IRQ_NONE;
> +	int ret;
> +
> +	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, priv->event);
> +	if (ret < 0)
> +		return handled;
> +
> +	if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_IN) {
> +		nct6694_canfd_handle_rx(ndev, event->rx_evt);
> +		handled = IRQ_HANDLED;
> +	}
> +
> +	if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_LOST) {
> +		nct6694_canfd_handle_lost_msg(ndev);
> +		handled = IRQ_HANDLED;
> +	}
> +
> +	if (event->status) {
> +		nct6694_canfd_handle_state_change(ndev, event->status);
> +		handled = IRQ_HANDLED;
> +	}
> +
> +	if (event->err != NCT6694_CANFD_EVT_ERR_NO_ERROR) {
> +		if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> +			nct6694_canfd_handle_bus_err(ndev, event->err);
> +		handled = IRQ_HANDLED;
> +	}
> +
> +	if (event->tx_evt & NCT6694_CANFD_EVT_TX_FIFO_EMPTY) {
> +		nct6694_canfd_handle_tx(ndev);
> +		handled = IRQ_HANDLED;
> +	}
> +
> +	if (handled)
> +		can_rx_offload_threaded_irq_finish(&priv->offload);
> +
> +	priv->bec.rxerr = event->rec;
> +	priv->bec.txerr = event->tec;
> +
> +	return handled;
> +}
> +
> +static void nct6694_canfd_tx_work(struct work_struct *work)
> +{
> +	struct nct6694_canfd_priv *priv = container_of(work,
> +						       struct nct6694_canfd_priv,
> +						       tx_work);
> +	struct nct6694_canfd_frame *frame = &priv->tx;
> +	struct net_device *ndev = priv->ndev;
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct sk_buff *skb = priv->can.echo_skb[0];
> +	static const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_DELIVER,
> +		.sel = NCT6694_CANFD_DELIVER_SEL(1),
> +		.len = cpu_to_le16(sizeof(*frame))
> +	};
> +	u32 txid;
> +	int err;
> +
> +	memset(frame, 0, sizeof(*frame));
> +
> +	frame->tag = NCT6694_CANFD_FRAME_TAG(ndev->dev_port);
> +
> +	if (can_is_canfd_skb(skb)) {
> +		struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
> +
> +		if (cfd->flags & CANFD_BRS)
> +			frame->flag |= NCT6694_CANFD_FRAME_FLAG_BRS;
> +
> +		if (cfd->can_id & CAN_EFF_FLAG) {
> +			txid = cfd->can_id & CAN_EFF_MASK;
> +			frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
> +		} else {
> +			txid = cfd->can_id & CAN_SFF_MASK;
> +		}
> +		frame->flag |= NCT6694_CANFD_FRAME_FLAG_FD;
> +		frame->id = cpu_to_le32(txid);
> +		frame->length = canfd_sanitize_len(cfd->len);
> +
> +		memcpy(frame->data, cfd->data, frame->length);
> +	} else {
> +		struct can_frame *cf = (struct can_frame *)skb->data;
> +
> +		if (cf->can_id & CAN_EFF_FLAG) {
> +			txid = cf->can_id & CAN_EFF_MASK;
> +			frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
> +		} else {
> +			txid = cf->can_id & CAN_SFF_MASK;
> +		}
> +
> +		if (cf->can_id & CAN_RTR_FLAG)
> +			frame->flag |= NCT6694_CANFD_FRAME_FLAG_RTR;
> +		else
> +			memcpy(frame->data, cf->data, cf->len);
> +
> +		frame->id = cpu_to_le32(txid);
> +		frame->length = cf->len;
> +	}
> +
> +	err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
> +	if (err) {
> +		can_free_echo_skb(ndev, 0, NULL);
> +		stats->tx_dropped++;
> +		stats->tx_errors++;
> +		netif_wake_queue(ndev);
> +	}
> +}
> +
> +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> +					    struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +
> +	if (can_dev_dropped_skb(ndev, skb))
> +		return NETDEV_TX_OK;
> +
> +	netif_stop_queue(ndev);
> +	can_put_echo_skb(skb, ndev, 0, 0);
> +	queue_work(priv->wq, &priv->tx_work);
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +static int nct6694_canfd_start(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> +	const struct can_bittiming *n_bt = &priv->can.bittiming;
> +	struct nct6694_canfd_setting *setting __free(kfree) = NULL;
> +	const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_SETTING,
> +		.sel = ndev->dev_port,
> +		.len = cpu_to_le16(sizeof(*setting))
> +	};
> +	int ret;
> +
> +	setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> +	if (!setting)
> +		return -ENOMEM;
> +
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
> +
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO);
> +
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK);
> +
> +	/* Disable clock divider */
> +	setting->ctrl2 = 0;
> +
> +	setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
> +					       n_bt->sjw - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP,
> +					       n_bt->brp - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2,
> +					       n_bt->phase_seg2 - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1,
> +					       n_bt->prop_seg + n_bt->phase_seg1 - 1));
> +
> +	setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
> +					       d_bt->sjw - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
> +					       d_bt->brp - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
> +					       d_bt->phase_seg2 - 1) |
> +				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
> +					       d_bt->prop_seg + d_bt->phase_seg1 - 1));
> +
> +	setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1 |
> +			  NCT6694_CANFD_SETTING_ACTIVE_CTRL2 |
> +			  NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP;
> +
> +	ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> +	if (ret)
> +		return ret;
> +
> +	priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> +	return 0;
> +}
> +
> +static void nct6694_canfd_stop(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	struct nct6694_canfd_setting *setting __free(kfree) = NULL;
> +	const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_SETTING,
> +		.sel = ndev->dev_port,
> +		.len = cpu_to_le16(sizeof(*setting))
> +	};
> +
> +	/* The NCT6694 cannot be stopped. To ensure safe operation and avoid
> +	 * interference, the control mode is set to Listen-Only mode. This
> +	 * mode allows the device to monitor bus activity without actively
> +	 * participating in communication.
> +	 */
> +	setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> +	if (!setting)
> +		return;
> +
> +	nct6694_read_msg(priv->nct6694, &cmd_hd, setting);
> +	setting->ctrl1 = cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
> +	setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1;
> +	nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> +
> +	priv->can.state = CAN_STATE_STOPPED;
> +}
> +
> +static int nct6694_canfd_close(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +

make this inverse to nct6694_canfd_open()

> +	netif_stop_queue(ndev);
> +	can_rx_offload_disable(&priv->offload);
> +	nct6694_canfd_stop(ndev);
> +	free_irq(ndev->irq, ndev);
> +	destroy_workqueue(priv->wq);
> +	close_candev(ndev);
> +	return 0;
> +}
> +
> +static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> +	int ret;
> +
> +	switch (mode) {
> +	case CAN_MODE_START:
> +		ret = nct6694_canfd_start(ndev);
> +		if (ret)
> +			return ret;
> +
> +		netif_wake_queue(ndev);
> +		break;
> +
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ret;
> +}
> +
> +static int nct6694_canfd_open(struct net_device *ndev)
> +{
> +	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +	int ret;
> +
> +	ret = open_candev(ndev);
> +	if (ret)
> +		return ret;
> +
> +	can_rx_offload_enable(&priv->offload);
> +
> +	ret = request_threaded_irq(ndev->irq, NULL,
> +				   nct6694_canfd_irq, IRQF_ONESHOT,
> +				   "nct6694_canfd", ndev);
> +	if (ret) {
> +		netdev_err(ndev, "Failed to request IRQ\n");
> +		goto close_candev;

nitpick: rename to can_rx_offload_disable

> +	}
> +
> +	priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
> +					   WQ_FREEZABLE | WQ_MEM_RECLAIM,
> +					   ndev->name);
> +	if (!priv->wq) {
> +		ret = -ENOMEM;
> +		goto free_irq;
> +	}
> +
> +	ret = nct6694_canfd_start(ndev);
> +	if (ret)
> +		goto destroy_wq;
> +
> +	netif_start_queue(ndev);
> +
> +	return 0;
> +
> +destroy_wq:
> +	destroy_workqueue(priv->wq);
> +free_irq:
> +	free_irq(ndev->irq, ndev);
> +close_candev:
> +	can_rx_offload_disable(&priv->offload);
> +	close_candev(ndev);
> +	return ret;
> +}
> +
> +static const struct net_device_ops nct6694_canfd_netdev_ops = {
> +	.ndo_open = nct6694_canfd_open,
> +	.ndo_stop = nct6694_canfd_close,
> +	.ndo_start_xmit = nct6694_canfd_start_xmit,
> +	.ndo_change_mtu = can_change_mtu,
> +};
> +
> +static const struct ethtool_ops nct6694_canfd_ethtool_ops = {
> +	.get_ts_info = ethtool_op_get_ts_info,
> +};
> +
> +static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
> +{
> +	struct nct6694_canfd_information *info __free(kfree) = NULL;
> +	static const struct nct6694_cmd_header cmd_hd = {
> +		.mod = NCT6694_CANFD_MOD,
> +		.cmd = NCT6694_CANFD_INFORMATION,
> +		.sel = NCT6694_CANFD_INFORMATION_SEL,
> +		.len = cpu_to_le16(sizeof(*info))
> +	};
> +	int ret, can_clk;
> +
> +	info = kzalloc(sizeof(*info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> +	if (ret)
> +		return ret;
> +
> +	can_clk = le32_to_cpu(info->can_clk);

return le32_to_cpu(info->can_clk);

> +
> +	return can_clk;
> +}
> +
> +static int nct6694_canfd_probe(struct platform_device *pdev)
> +{
> +	const struct mfd_cell *cell = mfd_get_cell(pdev);
> +	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> +	struct nct6694_canfd_priv *priv;
> +	struct net_device *ndev;
> +	int ret, irq, can_clk;
> +
> +	irq = irq_create_mapping(nct6694->domain,
> +				 NCT6694_IRQ_CAN0 + cell->id);
> +	if (!irq)
> +		return irq;
> +
> +	ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
> +	if (!ndev) {
> +		ret = -ENOMEM;
> +		goto dispose_irq;
> +	}
> +
> +	ndev->irq = irq;
> +	ndev->flags |= IFF_ECHO;
> +	ndev->dev_port = cell->id;
> +	ndev->netdev_ops = &nct6694_canfd_netdev_ops;
> +	ndev->ethtool_ops = &nct6694_canfd_ethtool_ops;
> +
> +	priv = netdev_priv(ndev);
> +	priv->nct6694 = nct6694;
> +	priv->ndev = ndev;
> +
> +	can_clk = nct6694_canfd_get_clock(priv);
> +	if (can_clk < 0) {
> +		ret = dev_err_probe(&pdev->dev, can_clk,
> +				    "Failed to get clock\n");
> +		goto free_candev;
> +	}
> +
> +	INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
> +
> +	priv->can.clock.freq = can_clk;
> +	priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
> +	priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
> +	priv->can.do_set_mode = nct6694_canfd_set_mode;
> +	priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
> +	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> +		CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> +		CAN_CTRLMODE_FD_NON_ISO;
> +
> +	ret = can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD);
> +	if (ret)
> +		goto free_candev;
> +
> +	ret = can_rx_offload_add_manual(ndev, &priv->offload,
> +					NCT6694_NAPI_WEIGHT);
> +	if (ret) {
> +		dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
> +		goto free_candev;
> +	}
> +
> +	platform_set_drvdata(pdev, priv);
> +	SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> +
> +	ret = register_candev(priv->ndev);
> +	if (ret)
> +		goto rx_offload_del;
> +
> +	return 0;
> +
> +rx_offload_del:
> +	can_rx_offload_del(&priv->offload);
> +free_candev:
> +	free_candev(ndev);
> +dispose_irq:
> +	irq_dispose_mapping(irq);
> +	return ret;
> +}
> +
> +static void nct6694_canfd_remove(struct platform_device *pdev)
> +{
> +	struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
> +	struct net_device *ndev = priv->ndev;
> +
> +	unregister_candev(ndev);
> +	irq_dispose_mapping(ndev->irq);
> +	can_rx_offload_del(&priv->offload);
> +	free_candev(ndev);

Make the order inverse to the nct6694_canfd_probe() function.

> +}
> +
> +static struct platform_driver nct6694_canfd_driver = {
> +	.driver = {
> +		.name	= DEVICE_NAME,
> +	},
> +	.probe		= nct6694_canfd_probe,
> +	.remove		= nct6694_canfd_remove,
> +};
> +
> +module_platform_driver(nct6694_canfd_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.34.1

regards,
Marc
kernel test robot May 7, 2025, 4:18 p.m. UTC | #2
Hi,

kernel test robot noticed the following build errors:

[auto build test ERROR on lee-mfd/for-mfd-next]
[also build test ERROR on brgl/gpio/for-next andi-shyti/i2c/i2c-host mkl-can-next/testing groeck-staging/hwmon-next abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.15-rc5 next-20250507]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/a0282524688-gmail-com/mfd-Add-core-driver-for-Nuvoton-NCT6694/20250423-174637
base:   https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link:    https://lore.kernel.org/r/20250423094058.1656204-5-tmyu0%40nuvoton.com
patch subject: [PATCH v10 4/7] can: Add Nuvoton NCT6694 CANFD support
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20250507/202505072336.mhh6H9Ma-lkp@intel.com/config)
compiler: clang version 17.0.6 (https://github.com/llvm/llvm-project 6009708b4367171ccdbf4b5905cb6a803753fe18)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250507/202505072336.mhh6H9Ma-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202505072336.mhh6H9Ma-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/net/can/usb/nct6694_canfd.c:543:30: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     543 |         setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
         |                                     ^
   include/linux/byteorder/generic.h:88:21: note: expanded from macro 'cpu_to_le32'
      88 | #define cpu_to_le32 __cpu_to_le32
         |                     ^
   1 error generated.


vim +/FIELD_PREP +543 drivers/net/can/usb/nct6694_canfd.c

   512	
   513	static int nct6694_canfd_start(struct net_device *ndev)
   514	{
   515		struct nct6694_canfd_priv *priv = netdev_priv(ndev);
   516		const struct can_bittiming *d_bt = &priv->can.data_bittiming;
   517		const struct can_bittiming *n_bt = &priv->can.bittiming;
   518		struct nct6694_canfd_setting *setting __free(kfree) = NULL;
   519		const struct nct6694_cmd_header cmd_hd = {
   520			.mod = NCT6694_CANFD_MOD,
   521			.cmd = NCT6694_CANFD_SETTING,
   522			.sel = ndev->dev_port,
   523			.len = cpu_to_le16(sizeof(*setting))
   524		};
   525		int ret;
   526	
   527		setting = kzalloc(sizeof(*setting), GFP_KERNEL);
   528		if (!setting)
   529			return -ENOMEM;
   530	
   531		if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
   532			setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
   533	
   534		if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
   535			setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO);
   536	
   537		if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
   538			setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK);
   539	
   540		/* Disable clock divider */
   541		setting->ctrl2 = 0;
   542	
 > 543		setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
   544						       n_bt->sjw - 1) |
   545					    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP,
   546						       n_bt->brp - 1) |
   547					    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2,
   548						       n_bt->phase_seg2 - 1) |
   549					    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1,
   550						       n_bt->prop_seg + n_bt->phase_seg1 - 1));
   551	
   552		setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
   553						       d_bt->sjw - 1) |
   554					    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
   555						       d_bt->brp - 1) |
   556					    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
   557						       d_bt->phase_seg2 - 1) |
   558					    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
   559						       d_bt->prop_seg + d_bt->phase_seg1 - 1));
   560	
   561		setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1 |
   562				  NCT6694_CANFD_SETTING_ACTIVE_CTRL2 |
   563				  NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP;
   564	
   565		ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
   566		if (ret)
   567			return ret;
   568	
   569		priv->can.state = CAN_STATE_ERROR_ACTIVE;
   570	
   571		return 0;
   572	}
   573
Ming Yu May 8, 2025, 3:26 a.m. UTC | #3
Dear Marc,

Thank you for reviewing.

Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年5月3日 週六 下午9:57寫道:

> > This driver supports Socket CANFD functionality for NCT6694 MFD
> > device based on USB interface.
> >
> > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
>
> The destroy functions nct6694_canfd_close() and nct6694_canfd_remove()
> are not the exact inverse of their init functions. Se comments inline.
>
> Please fix and add:
>
> Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>
>
> Feel free to mainline this patch as part of the series outside of the
> linux-can-next tree. Better ask the netdev maintainers for their OK, too.
>
> What about transceiver delay compensation for higher CAN-FD bitrates?
> How does you device handle these?
>

In the CAN CMD0's DBTP field, bit 23 is the TDC flag, I will add
support for enabling tdc, and firmware will automatically configure
tdco. Do you think this approach is appropriate?

> > ---
> >  MAINTAINERS                         |   1 +
> >  drivers/net/can/usb/Kconfig         |  11 +
> >  drivers/net/can/usb/Makefile        |   1 +
> >  drivers/net/can/usb/nct6694_canfd.c | 814 ++++++++++++++++++++++++++++
> >  4 files changed, 827 insertions(+)
> >  create mode 100644 drivers/net/can/usb/nct6694_canfd.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 751b9108524a..ee8583edc2d2 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -17364,6 +17364,7 @@ S:    Supported
> >  F:   drivers/gpio/gpio-nct6694.c
> >  F:   drivers/i2c/busses/i2c-nct6694.c
> >  F:   drivers/mfd/nct6694.c
> > +F:   drivers/net/can/usb/nct6694_canfd.c
> >  F:   include/linux/mfd/nct6694.h
> >
> >  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> > diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
> > index 9dae0c71a2e1..759e724a67cf 100644
> > --- a/drivers/net/can/usb/Kconfig
> > +++ b/drivers/net/can/usb/Kconfig
> > @@ -133,6 +133,17 @@ config CAN_MCBA_USB
> >         This driver supports the CAN BUS Analyzer interface
> >         from Microchip (http://www.microchip.com/development-tools/).
> >
> > +config CAN_NCT6694
> > +     tristate "Nuvoton NCT6694 Socket CANfd support"
> > +     depends on MFD_NCT6694
> > +     select CAN_RX_OFFLOAD
> > +     help
> > +       If you say yes to this option, support will be included for Nuvoton
> > +       NCT6694, a USB device to socket CANfd controller.
> > +
> > +       This driver can also be built as a module. If so, the module will
> > +       be called nct6694_canfd.
> > +
> >  config CAN_PEAK_USB
> >       tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
> >       help
> > diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> > index 8b11088e9a59..fcafb1ac262e 100644
> > --- a/drivers/net/can/usb/Makefile
> > +++ b/drivers/net/can/usb/Makefile
> > @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
> >  obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
> >  obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
> >  obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> > +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
> >  obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
> >  obj-$(CONFIG_CAN_UCAN) += ucan.o
> > diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> > new file mode 100644
> > index 000000000000..9cf6230ffb7d
> > --- /dev/null
> > +++ b/drivers/net/can/usb/nct6694_canfd.c
> > @@ -0,0 +1,814 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/can/dev.h>
> > +#include <linux/can/rx-offload.h>
> > +#include <linux/ethtool.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +#include <linux/module.h>
> > +#include <linux/netdevice.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define DEVICE_NAME "nct6694-canfd"
> > +
> > +/* USB command module type for NCT6694 CANfd controller.
> > + * This defines the module type used for communication with the NCT6694
> > + * CANfd controller over the USB interface.
> > + */
> > +#define NCT6694_CANFD_MOD                    0x05
> > +
> > +/* Command 00h - CAN Setting and Initialization */
> > +#define NCT6694_CANFD_SETTING                        0x00
> > +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1   BIT(0)
> > +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL2   BIT(1)
> > +#define NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP       BIT(2)
> > +#define NCT6694_CANFD_SETTING_CTRL1_MON              BIT(0)
> > +#define NCT6694_CANFD_SETTING_CTRL1_NISO     BIT(1)
> > +#define NCT6694_CANFD_SETTING_CTRL1_LBCK     BIT(2)
> > +#define NCT6694_CANFD_SETTING_NBTP_NTSEG2    GENMASK(6, 0)
> > +#define NCT6694_CANFD_SETTING_NBTP_NTSEG1    GENMASK(15, 8)
> > +#define NCT6694_CANFD_SETTING_NBTP_NBRP              GENMASK(24, 16)
> > +#define NCT6694_CANFD_SETTING_NBTP_NSJW              GENMASK(31, 25)
> > +#define NCT6694_CANFD_SETTING_DBTP_DSJW              GENMASK(3, 0)
> > +#define NCT6694_CANFD_SETTING_DBTP_DTSEG2    GENMASK(7, 4)
> > +#define NCT6694_CANFD_SETTING_DBTP_DTSEG1    GENMASK(12, 8)
> > +#define NCT6694_CANFD_SETTING_DBTP_DBRP              GENMASK(20, 16)

I will add the macro in v11: #define NCT6694_CANFD_SETTING_DBTP_TDC

> > +
> > +/* Command 01h - CAN Information */
> > +#define NCT6694_CANFD_INFORMATION            0x01
> > +#define NCT6694_CANFD_INFORMATION_SEL                0x00
> > +
> > +/* Command 02h - CAN Event */
> > +#define NCT6694_CANFD_EVENT                  0x02
> > +#define NCT6694_CANFD_EVENT_SEL(idx, mask)   \
> > +     ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
> > +
> > +#define NCT6694_CANFD_EVENT_MASK             GENMASK(5, 0)
> > +#define NCT6694_CANFD_EVT_TX_FIFO_EMPTY              BIT(7)  /* Read-clear */
> > +#define NCT6694_CANFD_EVT_RX_DATA_LOST               BIT(5)  /* Read-clear */
> > +#define NCT6694_CANFD_EVT_RX_DATA_IN         BIT(7)  /* Read-clear*/
>                                                                     ^^
> add a space
>
> > +
...
> > +static int nct6694_canfd_start(struct net_device *ndev)
> > +{
> > +     struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > +     const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > +     const struct can_bittiming *n_bt = &priv->can.bittiming;
> > +     struct nct6694_canfd_setting *setting __free(kfree) = NULL;
> > +     const struct nct6694_cmd_header cmd_hd = {
> > +             .mod = NCT6694_CANFD_MOD,
> > +             .cmd = NCT6694_CANFD_SETTING,
> > +             .sel = ndev->dev_port,
> > +             .len = cpu_to_le16(sizeof(*setting))
> > +     };
> > +     int ret;
> > +
> > +     setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > +     if (!setting)
> > +             return -ENOMEM;
> > +
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
> > +
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO);
> > +
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK);
> > +
> > +     /* Disable clock divider */
> > +     setting->ctrl2 = 0;
> > +
> > +     setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
> > +                                            n_bt->sjw - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP,
> > +                                            n_bt->brp - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2,
> > +                                            n_bt->phase_seg2 - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1,
> > +                                            n_bt->prop_seg + n_bt->phase_seg1 - 1));
> > +
> > +     setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
> > +                                            d_bt->sjw - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
> > +                                            d_bt->brp - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
> > +                                            d_bt->phase_seg2 - 1) |
> > +                                 FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
> > +                                            d_bt->prop_seg + d_bt->phase_seg1 - 1));

I'll update the code to:
     setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
                                            d_bt->sjw - 1) |
                                 FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
                                            d_bt->brp - 1) |
                                 FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
                                            d_bt->phase_seg2 - 1) |
                                FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
                                           d_bt->prop_seg +
d_bt->phase_seg1 - 1) |
                                NCT6694_CANFD_SETTING_DBTP_TDC);

> > +
> > +     setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1 |
> > +                       NCT6694_CANFD_SETTING_ACTIVE_CTRL2 |
> > +                       NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP;
> > +
> > +     ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> > +     if (ret)
> > +             return ret;
> > +
> > +     priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > +
> > +     return 0;
> > +}
> > +
> > +static void nct6694_canfd_stop(struct net_device *ndev)
> > +{
> > +     struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_canfd_setting *setting __free(kfree) = NULL;
> > +     const struct nct6694_cmd_header cmd_hd = {
> > +             .mod = NCT6694_CANFD_MOD,
> > +             .cmd = NCT6694_CANFD_SETTING,
> > +             .sel = ndev->dev_port,
> > +             .len = cpu_to_le16(sizeof(*setting))
> > +     };
> > +
> > +     /* The NCT6694 cannot be stopped. To ensure safe operation and avoid
> > +      * interference, the control mode is set to Listen-Only mode. This
> > +      * mode allows the device to monitor bus activity without actively
> > +      * participating in communication.
> > +      */
> > +     setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > +     if (!setting)
> > +             return;
> > +
> > +     nct6694_read_msg(priv->nct6694, &cmd_hd, setting);
> > +     setting->ctrl1 = cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
> > +     setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1;
> > +     nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> > +
> > +     priv->can.state = CAN_STATE_STOPPED;
> > +}
> > +
> > +static int nct6694_canfd_close(struct net_device *ndev)
> > +{
> > +     struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > +
>
> make this inverse to nct6694_canfd_open()
>

Fix it in v11.

> > +     netif_stop_queue(ndev);
> > +     can_rx_offload_disable(&priv->offload);
> > +     nct6694_canfd_stop(ndev);
> > +     free_irq(ndev->irq, ndev);
> > +     destroy_workqueue(priv->wq);
> > +     close_candev(ndev);
> > +     return 0;
> > +}
> > +
> > +static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
> > +{
> > +     int ret;
> > +
> > +     switch (mode) {
> > +     case CAN_MODE_START:
> > +             ret = nct6694_canfd_start(ndev);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             netif_wake_queue(ndev);
> > +             break;
> > +
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +
> > +     return ret;
> > +}
> > +
> > +static int nct6694_canfd_open(struct net_device *ndev)
> > +{
> > +     struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > +     int ret;
> > +
> > +     ret = open_candev(ndev);
> > +     if (ret)
> > +             return ret;
> > +
> > +     can_rx_offload_enable(&priv->offload);
> > +
> > +     ret = request_threaded_irq(ndev->irq, NULL,
> > +                                nct6694_canfd_irq, IRQF_ONESHOT,
> > +                                "nct6694_canfd", ndev);
> > +     if (ret) {
> > +             netdev_err(ndev, "Failed to request IRQ\n");
> > +             goto close_candev;
>
> nitpick: rename to can_rx_offload_disable
>

Fix it in v11.

> > +     }
> > +
> > +     priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
> > +                                        WQ_FREEZABLE | WQ_MEM_RECLAIM,
> > +                                        ndev->name);
> > +     if (!priv->wq) {
> > +             ret = -ENOMEM;
> > +             goto free_irq;
> > +     }
> > +
> > +     ret = nct6694_canfd_start(ndev);
> > +     if (ret)
> > +             goto destroy_wq;
> > +
> > +     netif_start_queue(ndev);
> > +
> > +     return 0;
> > +
> > +destroy_wq:
> > +     destroy_workqueue(priv->wq);
> > +free_irq:
> > +     free_irq(ndev->irq, ndev);
> > +close_candev:
> > +     can_rx_offload_disable(&priv->offload);
> > +     close_candev(ndev);
> > +     return ret;
> > +}
> > +
> > +static const struct net_device_ops nct6694_canfd_netdev_ops = {
> > +     .ndo_open = nct6694_canfd_open,
> > +     .ndo_stop = nct6694_canfd_close,
> > +     .ndo_start_xmit = nct6694_canfd_start_xmit,
> > +     .ndo_change_mtu = can_change_mtu,
> > +};
> > +
> > +static const struct ethtool_ops nct6694_canfd_ethtool_ops = {
> > +     .get_ts_info = ethtool_op_get_ts_info,
> > +};
> > +
> > +static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
> > +{
> > +     struct nct6694_canfd_information *info __free(kfree) = NULL;
> > +     static const struct nct6694_cmd_header cmd_hd = {
> > +             .mod = NCT6694_CANFD_MOD,
> > +             .cmd = NCT6694_CANFD_INFORMATION,
> > +             .sel = NCT6694_CANFD_INFORMATION_SEL,
> > +             .len = cpu_to_le16(sizeof(*info))
> > +     };
> > +     int ret, can_clk;
> > +
> > +     info = kzalloc(sizeof(*info), GFP_KERNEL);
> > +     if (!info)
> > +             return -ENOMEM;
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> > +     if (ret)
> > +             return ret;
> > +
> > +     can_clk = le32_to_cpu(info->can_clk);
>
> return le32_to_cpu(info->can_clk);
>

Fix it in v11.

> > +
> > +     return can_clk;
> > +}
> > +
> > +static int nct6694_canfd_probe(struct platform_device *pdev)
> > +{
> > +     const struct mfd_cell *cell = mfd_get_cell(pdev);
> > +     struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > +     struct nct6694_canfd_priv *priv;
> > +     struct net_device *ndev;
> > +     int ret, irq, can_clk;
> > +
> > +     irq = irq_create_mapping(nct6694->domain,
> > +                              NCT6694_IRQ_CAN0 + cell->id);
> > +     if (!irq)
> > +             return irq;
> > +
> > +     ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
> > +     if (!ndev) {
> > +             ret = -ENOMEM;
> > +             goto dispose_irq;
> > +     }
> > +
> > +     ndev->irq = irq;
> > +     ndev->flags |= IFF_ECHO;
> > +     ndev->dev_port = cell->id;
> > +     ndev->netdev_ops = &nct6694_canfd_netdev_ops;
> > +     ndev->ethtool_ops = &nct6694_canfd_ethtool_ops;
> > +
> > +     priv = netdev_priv(ndev);
> > +     priv->nct6694 = nct6694;
> > +     priv->ndev = ndev;
> > +
> > +     can_clk = nct6694_canfd_get_clock(priv);
> > +     if (can_clk < 0) {
> > +             ret = dev_err_probe(&pdev->dev, can_clk,
> > +                                 "Failed to get clock\n");
> > +             goto free_candev;
> > +     }
> > +
> > +     INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
> > +
> > +     priv->can.clock.freq = can_clk;
> > +     priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
> > +     priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
> > +     priv->can.do_set_mode = nct6694_canfd_set_mode;
> > +     priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
> > +     priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > +             CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > +             CAN_CTRLMODE_FD_NON_ISO;
> > +
> > +     ret = can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD);
> > +     if (ret)
> > +             goto free_candev;
> > +
> > +     ret = can_rx_offload_add_manual(ndev, &priv->offload,
> > +                                     NCT6694_NAPI_WEIGHT);
> > +     if (ret) {
> > +             dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
> > +             goto free_candev;
> > +     }
> > +
> > +     platform_set_drvdata(pdev, priv);
> > +     SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> > +
> > +     ret = register_candev(priv->ndev);
> > +     if (ret)
> > +             goto rx_offload_del;
> > +
> > +     return 0;
> > +
> > +rx_offload_del:
> > +     can_rx_offload_del(&priv->offload);
> > +free_candev:
> > +     free_candev(ndev);
> > +dispose_irq:
> > +     irq_dispose_mapping(irq);
> > +     return ret;
> > +}
> > +
> > +static void nct6694_canfd_remove(struct platform_device *pdev)
> > +{
> > +     struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
> > +     struct net_device *ndev = priv->ndev;
> > +
> > +     unregister_candev(ndev);
> > +     irq_dispose_mapping(ndev->irq);
> > +     can_rx_offload_del(&priv->offload);
> > +     free_candev(ndev);
>
> Make the order inverse to the nct6694_canfd_probe() function.
>

Fix it in v11.


Best regards,
Ming
Marc Kleine-Budde May 8, 2025, 3:08 p.m. UTC | #4
On 08.05.2025 11:26:09, Ming Yu wrote:
> > > This driver supports Socket CANFD functionality for NCT6694 MFD
> > > device based on USB interface.
> > >
> > > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> >
> > The destroy functions nct6694_canfd_close() and nct6694_canfd_remove()
> > are not the exact inverse of their init functions. Se comments inline.
> >
> > Please fix and add:
> >
> > Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>
> >
> > Feel free to mainline this patch as part of the series outside of the
> > linux-can-next tree. Better ask the netdev maintainers for their OK, too.
> >
> > What about transceiver delay compensation for higher CAN-FD bitrates?
> > How does you device handle these?
> >
> 
> In the CAN CMD0's DBTP field, bit 23 is the TDC flag, I will add
> support for enabling tdc, and firmware will automatically configure
> tdco. Do you think this approach is appropriate?

Can you configure the TDC manually via USB?

If the firmware does automatic TDCO configuration, does it take care of
not enabling TCDO if the Data-BRP is > 2?

BTW: What's the CAN clock of the device? I want to add it to the
can-utils' bitrate calculation tool.

regards,
Marc
Ming Yu May 9, 2025, 5:39 a.m. UTC | #5
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年5月8日 週四 下午11:08寫道:
> > > > This driver supports Socket CANFD functionality for NCT6694 MFD
> > > > device based on USB interface.
> > > >
> > > > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> > >
> > > The destroy functions nct6694_canfd_close() and nct6694_canfd_remove()
> > > are not the exact inverse of their init functions. Se comments inline.
> > >
> > > Please fix and add:
> > >
> > > Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>
> > >
> > > Feel free to mainline this patch as part of the series outside of the
> > > linux-can-next tree. Better ask the netdev maintainers for their OK, too.
> > >
> > > What about transceiver delay compensation for higher CAN-FD bitrates?
> > > How does you device handle these?
> > >
> >
> > In the CAN CMD0's DBTP field, bit 23 is the TDC flag, I will add
> > support for enabling tdc, and firmware will automatically configure
> > tdco. Do you think this approach is appropriate?
>
> Can you configure the TDC manually via USB?
>

Currently, it only supports enabling or disabling TDC.

> If the firmware does automatic TDCO configuration, does it take care of
> not enabling TCDO if the Data-BRP is > 2?
>

No, the firmware does not handle it. Do you think it would be
appropriate for the driver to handle the case where DBRP is > 2, for
example by disabling TDC?
(The firmware sets TDCO to (Total bit TQs / 2) when configuring DBTP.)

> BTW: What's the CAN clock of the device? I want to add it to the
> can-utils' bitrate calculation tool.
>

The CAN clock is running at 96Mhz.


Thanks,
Ming
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 751b9108524a..ee8583edc2d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17364,6 +17364,7 @@  S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
+F:	drivers/net/can/usb/nct6694_canfd.c
 F:	include/linux/mfd/nct6694.h
 
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
index 9dae0c71a2e1..759e724a67cf 100644
--- a/drivers/net/can/usb/Kconfig
+++ b/drivers/net/can/usb/Kconfig
@@ -133,6 +133,17 @@  config CAN_MCBA_USB
 	  This driver supports the CAN BUS Analyzer interface
 	  from Microchip (http://www.microchip.com/development-tools/).
 
+config CAN_NCT6694
+	tristate "Nuvoton NCT6694 Socket CANfd support"
+	depends on MFD_NCT6694
+	select CAN_RX_OFFLOAD
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB device to socket CANfd controller.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called nct6694_canfd.
+
 config CAN_PEAK_USB
 	tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
 	help
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 8b11088e9a59..fcafb1ac262e 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -11,5 +11,6 @@  obj-$(CONFIG_CAN_F81604) += f81604.o
 obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
 obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
 obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
+obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
 obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
 obj-$(CONFIG_CAN_UCAN) += ucan.o
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
new file mode 100644
index 000000000000..9cf6230ffb7d
--- /dev/null
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -0,0 +1,814 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/can/dev.h>
+#include <linux/can/rx-offload.h>
+#include <linux/ethtool.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#define DEVICE_NAME "nct6694-canfd"
+
+/* USB command module type for NCT6694 CANfd controller.
+ * This defines the module type used for communication with the NCT6694
+ * CANfd controller over the USB interface.
+ */
+#define NCT6694_CANFD_MOD			0x05
+
+/* Command 00h - CAN Setting and Initialization */
+#define NCT6694_CANFD_SETTING			0x00
+#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1	BIT(0)
+#define NCT6694_CANFD_SETTING_ACTIVE_CTRL2	BIT(1)
+#define NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP	BIT(2)
+#define NCT6694_CANFD_SETTING_CTRL1_MON		BIT(0)
+#define NCT6694_CANFD_SETTING_CTRL1_NISO	BIT(1)
+#define NCT6694_CANFD_SETTING_CTRL1_LBCK	BIT(2)
+#define NCT6694_CANFD_SETTING_NBTP_NTSEG2	GENMASK(6, 0)
+#define NCT6694_CANFD_SETTING_NBTP_NTSEG1	GENMASK(15, 8)
+#define NCT6694_CANFD_SETTING_NBTP_NBRP		GENMASK(24, 16)
+#define NCT6694_CANFD_SETTING_NBTP_NSJW		GENMASK(31, 25)
+#define NCT6694_CANFD_SETTING_DBTP_DSJW		GENMASK(3, 0)
+#define NCT6694_CANFD_SETTING_DBTP_DTSEG2	GENMASK(7, 4)
+#define NCT6694_CANFD_SETTING_DBTP_DTSEG1	GENMASK(12, 8)
+#define NCT6694_CANFD_SETTING_DBTP_DBRP		GENMASK(20, 16)
+
+/* Command 01h - CAN Information */
+#define NCT6694_CANFD_INFORMATION		0x01
+#define NCT6694_CANFD_INFORMATION_SEL		0x00
+
+/* Command 02h - CAN Event */
+#define NCT6694_CANFD_EVENT			0x02
+#define NCT6694_CANFD_EVENT_SEL(idx, mask)	\
+	((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
+
+#define NCT6694_CANFD_EVENT_MASK		GENMASK(5, 0)
+#define NCT6694_CANFD_EVT_TX_FIFO_EMPTY		BIT(7)	/* Read-clear */
+#define NCT6694_CANFD_EVT_RX_DATA_LOST		BIT(5)	/* Read-clear */
+#define NCT6694_CANFD_EVT_RX_DATA_IN		BIT(7)	/* Read-clear*/
+
+/* Command 10h - CAN Deliver */
+#define NCT6694_CANFD_DELIVER			0x10
+#define NCT6694_CANFD_DELIVER_SEL(buf_cnt)	\
+	((buf_cnt) & 0xFF)
+
+/* Command 11h - CAN Receive */
+#define NCT6694_CANFD_RECEIVE			0x11
+#define NCT6694_CANFD_RECEIVE_SEL(idx, buf_cnt)	\
+	((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
+
+#define NCT6694_CANFD_FRAME_TAG(idx)		(0xC0 | (idx))
+#define NCT6694_CANFD_FRAME_FLAG_EFF		BIT(0)
+#define NCT6694_CANFD_FRAME_FLAG_RTR		BIT(1)
+#define NCT6694_CANFD_FRAME_FLAG_FD		BIT(2)
+#define NCT6694_CANFD_FRAME_FLAG_BRS		BIT(3)
+#define NCT6694_CANFD_FRAME_FLAG_ERR		BIT(4)
+
+#define NCT6694_NAPI_WEIGHT			32
+
+enum nct6694_event_err {
+	NCT6694_CANFD_EVT_ERR_NO_ERROR = 0,
+	NCT6694_CANFD_EVT_ERR_CRC_ERROR,
+	NCT6694_CANFD_EVT_ERR_STUFF_ERROR,
+	NCT6694_CANFD_EVT_ERR_ACK_ERROR,
+	NCT6694_CANFD_EVT_ERR_FORM_ERROR,
+	NCT6694_CANFD_EVT_ERR_BIT_ERROR,
+	NCT6694_CANFD_EVT_ERR_TIMEOUT_ERROR,
+	NCT6694_CANFD_EVT_ERR_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+	NCT6694_CANFD_EVT_STS_ERROR_ACTIVE = 0,
+	NCT6694_CANFD_EVT_STS_ERROR_PASSIVE,
+	NCT6694_CANFD_EVT_STS_BUS_OFF,
+	NCT6694_CANFD_EVT_STS_WARNING,
+};
+
+struct __packed nct6694_canfd_setting {
+	__le32 nbr;
+	__le32 dbr;
+	u8 active;
+	u8 reserved[3];
+	__le16 ctrl1;
+	__le16 ctrl2;
+	__le32 nbtp;
+	__le32 dbtp;
+};
+
+struct __packed nct6694_canfd_information {
+	u8 tx_fifo_cnt;
+	u8 rx_fifo_cnt;
+	u8 reserved[2];
+	__le32 can_clk;
+};
+
+struct __packed nct6694_canfd_event {
+	u8 err;
+	u8 status;
+	u8 tx_evt;
+	u8 rx_evt;
+	u8 rec;
+	u8 tec;
+	u8 reserved[2];
+};
+
+struct __packed nct6694_canfd_frame {
+	u8 tag;
+	u8 flag;
+	u8 reserved;
+	u8 length;
+	__le32 id;
+	u8 data[CANFD_MAX_DLEN];
+};
+
+struct nct6694_canfd_priv {
+	struct can_priv can;	/* must be the first member */
+	struct can_rx_offload offload;
+	struct net_device *ndev;
+	struct nct6694 *nct6694;
+	struct workqueue_struct *wq;
+	struct work_struct tx_work;
+	struct nct6694_canfd_frame tx;
+	struct nct6694_canfd_frame rx;
+	struct nct6694_canfd_event event[2];
+	struct can_berr_counter bec;
+};
+
+static inline struct nct6694_canfd_priv *rx_offload_to_priv(struct can_rx_offload *offload)
+{
+	return container_of(offload, struct nct6694_canfd_priv, offload);
+}
+
+static const struct can_bittiming_const nct6694_canfd_bittiming_nominal_const = {
+	.name = DEVICE_NAME,
+	.tseg1_min = 1,
+	.tseg1_max = 256,
+	.tseg2_min = 1,
+	.tseg2_max = 128,
+	.sjw_max = 128,
+	.brp_min = 1,
+	.brp_max = 512,
+	.brp_inc = 1,
+};
+
+static const struct can_bittiming_const nct6694_canfd_bittiming_data_const = {
+	.name = DEVICE_NAME,
+	.tseg1_min = 1,
+	.tseg1_max = 32,
+	.tseg2_min = 1,
+	.tseg2_max = 16,
+	.sjw_max = 16,
+	.brp_min = 1,
+	.brp_max = 32,
+	.brp_inc = 1,
+};
+
+static void nct6694_canfd_rx_offload(struct can_rx_offload *offload,
+				     struct sk_buff *skb)
+{
+	struct nct6694_canfd_priv *priv = rx_offload_to_priv(offload);
+	int ret;
+
+	ret = can_rx_offload_queue_tail(offload, skb);
+	if (ret)
+		priv->ndev->stats.rx_fifo_errors++;
+}
+
+static void nct6694_canfd_handle_lost_msg(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	netdev_dbg(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+	stats->rx_errors++;
+	stats->rx_over_errors++;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+	if (!skb)
+		return;
+
+	cf->can_id |= CAN_ERR_CRTL;
+	cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+	nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt)
+{
+	struct net_device_stats *stats = &ndev->stats;
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct nct6694_canfd_frame *frame = &priv->rx;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_RECEIVE,
+		.sel = NCT6694_CANFD_RECEIVE_SEL(ndev->dev_port, 1),
+		.len = cpu_to_le16(sizeof(*frame))
+	};
+	struct sk_buff *skb;
+	int ret;
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
+	if (ret)
+		return;
+
+	if (frame->flag & NCT6694_CANFD_FRAME_FLAG_FD) {
+		struct canfd_frame *cfd;
+
+		skb = alloc_canfd_skb(priv->ndev, &cfd);
+		if (!skb) {
+			stats->rx_dropped++;
+			return;
+		}
+
+		cfd->can_id = le32_to_cpu(frame->id);
+		cfd->len = canfd_sanitize_len(frame->length);
+		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
+			cfd->can_id |= CAN_EFF_FLAG;
+		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_BRS)
+			cfd->flags |= CANFD_BRS;
+		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_ERR)
+			cfd->flags |= CANFD_ESI;
+
+		memcpy(cfd->data, frame->data, cfd->len);
+	} else {
+		struct can_frame *cf;
+
+		skb = alloc_can_skb(priv->ndev, &cf);
+		if (!skb) {
+			stats->rx_dropped++;
+			return;
+		}
+
+		cf->can_id = le32_to_cpu(frame->id);
+		cf->len = can_cc_dlc2len(frame->length);
+		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
+			cf->can_id |= CAN_EFF_FLAG;
+
+		if (frame->flag & NCT6694_CANFD_FRAME_FLAG_RTR)
+			cf->can_id |= CAN_RTR_FLAG;
+		else
+			memcpy(cf->data, frame->data, cf->len);
+	}
+
+	nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
+					  struct can_berr_counter *bec)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+	*bec = priv->bec;
+
+	return 0;
+}
+
+static void nct6694_canfd_handle_state_change(struct net_device *ndev, u8 status)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	enum can_state new_state, rx_state, tx_state;
+	struct can_berr_counter bec;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	nct6694_canfd_get_berr_counter(ndev, &bec);
+	can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);
+
+	new_state = max(tx_state, rx_state);
+
+	/* state hasn't changed */
+	if (new_state == priv->can.state)
+		return;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+
+	can_change_state(ndev, cf, tx_state, rx_state);
+
+	if (new_state == CAN_STATE_BUS_OFF) {
+		can_bus_off(ndev);
+	} else if (cf) {
+		cf->can_id |= CAN_ERR_CNT;
+		cf->data[6] = bec.txerr;
+		cf->data[7] = bec.rxerr;
+	}
+
+	if (skb)
+		nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_err)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	priv->can.can_stats.bus_error++;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+	if (cf)
+		cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+	switch (bus_err) {
+	case NCT6694_CANFD_EVT_ERR_CRC_ERROR:
+		netdev_dbg(ndev, "CRC error\n");
+		ndev->stats.rx_errors++;
+		if (cf)
+			cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+		break;
+
+	case NCT6694_CANFD_EVT_ERR_STUFF_ERROR:
+		netdev_dbg(ndev, "Stuff error\n");
+		ndev->stats.rx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_STUFF;
+		break;
+
+	case NCT6694_CANFD_EVT_ERR_ACK_ERROR:
+		netdev_dbg(ndev, "Ack error\n");
+		ndev->stats.tx_errors++;
+		if (cf) {
+			cf->can_id |= CAN_ERR_ACK;
+			cf->data[2] |= CAN_ERR_PROT_TX;
+		}
+		break;
+
+	case NCT6694_CANFD_EVT_ERR_FORM_ERROR:
+		netdev_dbg(ndev, "Form error\n");
+		ndev->stats.rx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_FORM;
+		break;
+
+	case NCT6694_CANFD_EVT_ERR_BIT_ERROR:
+		netdev_dbg(ndev, "Bit error\n");
+		ndev->stats.tx_errors++;
+		if (cf)
+			cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
+		break;
+
+	default:
+		break;
+	}
+
+	if (skb)
+		nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_tx(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+
+	stats->tx_bytes += can_rx_offload_get_echo_skb_queue_tail(&priv->offload,
+								  0, NULL);
+	stats->tx_packets++;
+	netif_wake_queue(ndev);
+}
+
+static irqreturn_t nct6694_canfd_irq(int irq, void *data)
+{
+	struct net_device *ndev = data;
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct nct6694_canfd_event *event = &priv->event[ndev->dev_port];
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_EVENT,
+		.sel = NCT6694_CANFD_EVENT_SEL(ndev->dev_port, NCT6694_CANFD_EVENT_MASK),
+		.len = cpu_to_le16(sizeof(priv->event))
+	};
+	irqreturn_t handled = IRQ_NONE;
+	int ret;
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, priv->event);
+	if (ret < 0)
+		return handled;
+
+	if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_IN) {
+		nct6694_canfd_handle_rx(ndev, event->rx_evt);
+		handled = IRQ_HANDLED;
+	}
+
+	if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_LOST) {
+		nct6694_canfd_handle_lost_msg(ndev);
+		handled = IRQ_HANDLED;
+	}
+
+	if (event->status) {
+		nct6694_canfd_handle_state_change(ndev, event->status);
+		handled = IRQ_HANDLED;
+	}
+
+	if (event->err != NCT6694_CANFD_EVT_ERR_NO_ERROR) {
+		if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+			nct6694_canfd_handle_bus_err(ndev, event->err);
+		handled = IRQ_HANDLED;
+	}
+
+	if (event->tx_evt & NCT6694_CANFD_EVT_TX_FIFO_EMPTY) {
+		nct6694_canfd_handle_tx(ndev);
+		handled = IRQ_HANDLED;
+	}
+
+	if (handled)
+		can_rx_offload_threaded_irq_finish(&priv->offload);
+
+	priv->bec.rxerr = event->rec;
+	priv->bec.txerr = event->tec;
+
+	return handled;
+}
+
+static void nct6694_canfd_tx_work(struct work_struct *work)
+{
+	struct nct6694_canfd_priv *priv = container_of(work,
+						       struct nct6694_canfd_priv,
+						       tx_work);
+	struct nct6694_canfd_frame *frame = &priv->tx;
+	struct net_device *ndev = priv->ndev;
+	struct net_device_stats *stats = &ndev->stats;
+	struct sk_buff *skb = priv->can.echo_skb[0];
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_DELIVER,
+		.sel = NCT6694_CANFD_DELIVER_SEL(1),
+		.len = cpu_to_le16(sizeof(*frame))
+	};
+	u32 txid;
+	int err;
+
+	memset(frame, 0, sizeof(*frame));
+
+	frame->tag = NCT6694_CANFD_FRAME_TAG(ndev->dev_port);
+
+	if (can_is_canfd_skb(skb)) {
+		struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
+
+		if (cfd->flags & CANFD_BRS)
+			frame->flag |= NCT6694_CANFD_FRAME_FLAG_BRS;
+
+		if (cfd->can_id & CAN_EFF_FLAG) {
+			txid = cfd->can_id & CAN_EFF_MASK;
+			frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
+		} else {
+			txid = cfd->can_id & CAN_SFF_MASK;
+		}
+		frame->flag |= NCT6694_CANFD_FRAME_FLAG_FD;
+		frame->id = cpu_to_le32(txid);
+		frame->length = canfd_sanitize_len(cfd->len);
+
+		memcpy(frame->data, cfd->data, frame->length);
+	} else {
+		struct can_frame *cf = (struct can_frame *)skb->data;
+
+		if (cf->can_id & CAN_EFF_FLAG) {
+			txid = cf->can_id & CAN_EFF_MASK;
+			frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
+		} else {
+			txid = cf->can_id & CAN_SFF_MASK;
+		}
+
+		if (cf->can_id & CAN_RTR_FLAG)
+			frame->flag |= NCT6694_CANFD_FRAME_FLAG_RTR;
+		else
+			memcpy(frame->data, cf->data, cf->len);
+
+		frame->id = cpu_to_le32(txid);
+		frame->length = cf->len;
+	}
+
+	err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
+	if (err) {
+		can_free_echo_skb(ndev, 0, NULL);
+		stats->tx_dropped++;
+		stats->tx_errors++;
+		netif_wake_queue(ndev);
+	}
+}
+
+static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
+					    struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+	if (can_dev_dropped_skb(ndev, skb))
+		return NETDEV_TX_OK;
+
+	netif_stop_queue(ndev);
+	can_put_echo_skb(skb, ndev, 0, 0);
+	queue_work(priv->wq, &priv->tx_work);
+
+	return NETDEV_TX_OK;
+}
+
+static int nct6694_canfd_start(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+	const struct can_bittiming *n_bt = &priv->can.bittiming;
+	struct nct6694_canfd_setting *setting __free(kfree) = NULL;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_SETTING,
+		.sel = ndev->dev_port,
+		.len = cpu_to_le16(sizeof(*setting))
+	};
+	int ret;
+
+	setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+	if (!setting)
+		return -ENOMEM;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK);
+
+	/* Disable clock divider */
+	setting->ctrl2 = 0;
+
+	setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
+					       n_bt->sjw - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP,
+					       n_bt->brp - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2,
+					       n_bt->phase_seg2 - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1,
+					       n_bt->prop_seg + n_bt->phase_seg1 - 1));
+
+	setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
+					       d_bt->sjw - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
+					       d_bt->brp - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
+					       d_bt->phase_seg2 - 1) |
+				    FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
+					       d_bt->prop_seg + d_bt->phase_seg1 - 1));
+
+	setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1 |
+			  NCT6694_CANFD_SETTING_ACTIVE_CTRL2 |
+			  NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP;
+
+	ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+	if (ret)
+		return ret;
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	return 0;
+}
+
+static void nct6694_canfd_stop(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	struct nct6694_canfd_setting *setting __free(kfree) = NULL;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_SETTING,
+		.sel = ndev->dev_port,
+		.len = cpu_to_le16(sizeof(*setting))
+	};
+
+	/* The NCT6694 cannot be stopped. To ensure safe operation and avoid
+	 * interference, the control mode is set to Listen-Only mode. This
+	 * mode allows the device to monitor bus activity without actively
+	 * participating in communication.
+	 */
+	setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+	if (!setting)
+		return;
+
+	nct6694_read_msg(priv->nct6694, &cmd_hd, setting);
+	setting->ctrl1 = cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
+	setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1;
+	nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+
+	priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int nct6694_canfd_close(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+	can_rx_offload_disable(&priv->offload);
+	nct6694_canfd_stop(ndev);
+	free_irq(ndev->irq, ndev);
+	destroy_workqueue(priv->wq);
+	close_candev(ndev);
+	return 0;
+}
+
+static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+	int ret;
+
+	switch (mode) {
+	case CAN_MODE_START:
+		ret = nct6694_canfd_start(ndev);
+		if (ret)
+			return ret;
+
+		netif_wake_queue(ndev);
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ret;
+}
+
+static int nct6694_canfd_open(struct net_device *ndev)
+{
+	struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+	int ret;
+
+	ret = open_candev(ndev);
+	if (ret)
+		return ret;
+
+	can_rx_offload_enable(&priv->offload);
+
+	ret = request_threaded_irq(ndev->irq, NULL,
+				   nct6694_canfd_irq, IRQF_ONESHOT,
+				   "nct6694_canfd", ndev);
+	if (ret) {
+		netdev_err(ndev, "Failed to request IRQ\n");
+		goto close_candev;
+	}
+
+	priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
+					   WQ_FREEZABLE | WQ_MEM_RECLAIM,
+					   ndev->name);
+	if (!priv->wq) {
+		ret = -ENOMEM;
+		goto free_irq;
+	}
+
+	ret = nct6694_canfd_start(ndev);
+	if (ret)
+		goto destroy_wq;
+
+	netif_start_queue(ndev);
+
+	return 0;
+
+destroy_wq:
+	destroy_workqueue(priv->wq);
+free_irq:
+	free_irq(ndev->irq, ndev);
+close_candev:
+	can_rx_offload_disable(&priv->offload);
+	close_candev(ndev);
+	return ret;
+}
+
+static const struct net_device_ops nct6694_canfd_netdev_ops = {
+	.ndo_open = nct6694_canfd_open,
+	.ndo_stop = nct6694_canfd_close,
+	.ndo_start_xmit = nct6694_canfd_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static const struct ethtool_ops nct6694_canfd_ethtool_ops = {
+	.get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
+{
+	struct nct6694_canfd_information *info __free(kfree) = NULL;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CANFD_MOD,
+		.cmd = NCT6694_CANFD_INFORMATION,
+		.sel = NCT6694_CANFD_INFORMATION_SEL,
+		.len = cpu_to_le16(sizeof(*info))
+	};
+	int ret, can_clk;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
+	if (ret)
+		return ret;
+
+	can_clk = le32_to_cpu(info->can_clk);
+
+	return can_clk;
+}
+
+static int nct6694_canfd_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_canfd_priv *priv;
+	struct net_device *ndev;
+	int ret, irq, can_clk;
+
+	irq = irq_create_mapping(nct6694->domain,
+				 NCT6694_IRQ_CAN0 + cell->id);
+	if (!irq)
+		return irq;
+
+	ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
+	if (!ndev) {
+		ret = -ENOMEM;
+		goto dispose_irq;
+	}
+
+	ndev->irq = irq;
+	ndev->flags |= IFF_ECHO;
+	ndev->dev_port = cell->id;
+	ndev->netdev_ops = &nct6694_canfd_netdev_ops;
+	ndev->ethtool_ops = &nct6694_canfd_ethtool_ops;
+
+	priv = netdev_priv(ndev);
+	priv->nct6694 = nct6694;
+	priv->ndev = ndev;
+
+	can_clk = nct6694_canfd_get_clock(priv);
+	if (can_clk < 0) {
+		ret = dev_err_probe(&pdev->dev, can_clk,
+				    "Failed to get clock\n");
+		goto free_candev;
+	}
+
+	INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
+
+	priv->can.clock.freq = can_clk;
+	priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
+	priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
+	priv->can.do_set_mode = nct6694_canfd_set_mode;
+	priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+		CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
+		CAN_CTRLMODE_FD_NON_ISO;
+
+	ret = can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD);
+	if (ret)
+		goto free_candev;
+
+	ret = can_rx_offload_add_manual(ndev, &priv->offload,
+					NCT6694_NAPI_WEIGHT);
+	if (ret) {
+		dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
+		goto free_candev;
+	}
+
+	platform_set_drvdata(pdev, priv);
+	SET_NETDEV_DEV(priv->ndev, &pdev->dev);
+
+	ret = register_candev(priv->ndev);
+	if (ret)
+		goto rx_offload_del;
+
+	return 0;
+
+rx_offload_del:
+	can_rx_offload_del(&priv->offload);
+free_candev:
+	free_candev(ndev);
+dispose_irq:
+	irq_dispose_mapping(irq);
+	return ret;
+}
+
+static void nct6694_canfd_remove(struct platform_device *pdev)
+{
+	struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
+	struct net_device *ndev = priv->ndev;
+
+	unregister_candev(ndev);
+	irq_dispose_mapping(ndev->irq);
+	can_rx_offload_del(&priv->offload);
+	free_candev(ndev);
+}
+
+static struct platform_driver nct6694_canfd_driver = {
+	.driver = {
+		.name	= DEVICE_NAME,
+	},
+	.probe		= nct6694_canfd_probe,
+	.remove		= nct6694_canfd_remove,
+};
+
+module_platform_driver(nct6694_canfd_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");