Message ID | 20250423094058.1656204-5-tmyu0@nuvoton.com |
---|---|
State | New |
Headers | show |
Series | Add Nuvoton NCT6694 MFD drivers | expand |
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
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
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
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
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 --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");