mbox series

[v10,0/7] Add Nuvoton NCT6694 MFD drivers

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

Message

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

This patch series introduces support for Nuvoton NCT6694, a peripheral
expander based on USB interface. It models the chip as an MFD driver
(1/7), GPIO driver(2/7), I2C Adapter driver(3/7), CANfd driver(4/7),
WDT driver(5/7), HWMON driver(6/7), and RTC driver(7/7).

The MFD driver implements USB device functionality to issue
custom-define USB bulk pipe packets for NCT6694. Each child device can
use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
a command. They can also request interrupt that will be called when the
USB device receives its interrupt pipe.

The following introduces the custom-define USB transactions:
	nct6694_read_msg - Send bulk-out pipe to write request packet
			   Receive bulk-in pipe to read response packet
			   Receive bulk-in pipe to read data packet

	nct6694_write_msg - Send bulk-out pipe to write request packet
			    Send bulk-out pipe to write data packet
			    Receive bulk-in pipe to read response packet
			    Receive bulk-in pipe to read data packet

Changes since version 9:
- Add devm_add_action_or_reset() to dispose irq mapping
- Add KernelDoc to exported functions in nct6694.c

Changes since version 8:
- Modify the signed-off-by with my work address
- Rename all MFD cell names to "nct6694-xxx"
- Add irq_dispose_mapping() in the error handling path and in the remove
  function
- Fix some comments in nct6694.c and in nct6694.h
- Add module parameters to configure I2C's baudrate in i2c-nct6694.c
- Rename all function names nct6694_can_xxx to nct6694_canfd_xxx in
  nct6694_canfd.c
- Fix nct6694_canfd_handle_state_change() in nct6694_canfd.c
- Fix nct6694_canfd_start() to configure NBTP and DBTP in nct6694_canfd.c
- Add can_set_static_ctrlmode() in nct6694_canfd.c

Changes since version 7:
- Add error handling for devm_mutex_init()
- Modify the name of the child devices CAN1 and CAN2 to CAN0 and CAN1.
- Fix multiline comments to net-dev style in nct6694_canfd.c

Changes since version 6:
- Fix nct6694_can_handle_state_change() in nct6694_canfd.c
- Fix warnings in nct6694_canfd.c
- Move the nct6694_can_priv's bec to the end in nct6694_canfd.c
- Fix warning in nct6694_wdt.c
- Fix temp_hyst's data type to signed variable in nct6694-hwmon.c

Changes since version 5:
- Modify the module name and the driver name consistently
- Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC()
- Drop unnecessary macros in nct6694.c
- Update private data and drop mutex in nct6694_canfd.c
- Fix nct6694_can_handle_state_change() in nct6694_canfd.c

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function
- Move the nct6694_canfd.c to drivers/net/can/usb/
- Fix the missing rx offload function in nct6694_canfd.c
- Fix warngings in nct6694-hwmon.c

Changes since version 3:
- Modify array buffer to structure for each drivers
- Fix defines and comments for each drivers
- Add header <linux/bits.h> and use BIT macro in nct6694.c and
  gpio-nct6694.c
- Modify mutex_init() to devm_mutex_init()
- Add rx-offload helper in nct6694_canfd.c
- Drop watchdog_init_timeout() in nct6694_wdt.c
- Modify the division method to DIV_ROUND_CLOSEST() in nct6694-hwmon.c
- Drop private mutex and use rtc core lock in rtc-nct6694.c
- Modify device_set_wakeup_capable() to device_init_wakeup() in
  rtc-nct6694.c

Changes since version 2:
- Add MODULE_ALIAS() for each child driver
- Modify gpio line names be a local variable in gpio-nct6694.c
- Drop unnecessary platform_get_drvdata() in gpio-nct6694.c
- Rename each command in nct6694_canfd.c
- Modify each function name consistently in nct6694_canfd.c
- Modify the pretimeout validation procedure in nct6694_wdt.c
- Fix warnings in nct6694-hwmon.c

Changes since version 1:
- Implement IRQ domain to handle IRQ demux in nct6694.c
- Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API in nct6694.c
- Add each driver's command structure
- Fix USB functions in nct6694.c
- Fix platform driver registration in each child driver
- Sort each driver's header files alphabetically
- Drop unnecessary header in gpio-nct6694.c
- Add gpio line names in gpio-nct6694.c
- Fix errors and warnings in nct6694_canfd.c
- Fix TX-flow control in nct6694_canfd.c
- Fix warnings in nct6694_wdt.c
- Drop unnecessary logs in nct6694_wdt.c
- Modify start() function to setup device in nct6694_wdt.c
- Add voltage sensors functionality in nct6694-hwmon.c
- Add temperature sensors functionality in nct6694-hwmon.c
- Fix overwrite error return values in nct6694-hwmon.c
- Add write value limitation for each write() function in nct6694-hwmon.c
- Drop unnecessary logs in rtc-nct6694.c
- Fix overwrite error return values in rtc-nct6694.c
- Modify to use dev_err_probe API in rtc-nct6694.c


Ming Yu (7):
  mfd: Add core driver for Nuvoton NCT6694
  gpio: Add Nuvoton NCT6694 GPIO support
  i2c: Add Nuvoton NCT6694 I2C support
  can: Add Nuvoton NCT6694 CANFD support
  watchdog: Add Nuvoton NCT6694 WDT support
  hwmon: Add Nuvoton NCT6694 HWMON support
  rtc: Add Nuvoton NCT6694 RTC support

 MAINTAINERS                         |  12 +
 drivers/gpio/Kconfig                |  12 +
 drivers/gpio/Makefile               |   1 +
 drivers/gpio/gpio-nct6694.c         | 480 ++++++++++++++
 drivers/hwmon/Kconfig               |  10 +
 drivers/hwmon/Makefile              |   1 +
 drivers/hwmon/nct6694-hwmon.c       | 949 ++++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig          |  10 +
 drivers/i2c/busses/Makefile         |   1 +
 drivers/i2c/busses/i2c-nct6694.c    | 184 ++++++
 drivers/mfd/Kconfig                 |  15 +
 drivers/mfd/Makefile                |   2 +
 drivers/mfd/nct6694.c               | 387 ++++++++++++
 drivers/net/can/usb/Kconfig         |  11 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 814 ++++++++++++++++++++++++
 drivers/rtc/Kconfig                 |  10 +
 drivers/rtc/Makefile                |   1 +
 drivers/rtc/rtc-nct6694.c           | 298 +++++++++
 drivers/watchdog/Kconfig            |  11 +
 drivers/watchdog/Makefile           |   1 +
 drivers/watchdog/nct6694_wdt.c      | 298 +++++++++
 include/linux/mfd/nct6694.h         | 101 +++
 23 files changed, 3610 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c
 create mode 100644 drivers/hwmon/nct6694-hwmon.c
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c
 create mode 100644 drivers/rtc/rtc-nct6694.c
 create mode 100644 drivers/watchdog/nct6694_wdt.c
 create mode 100644 include/linux/mfd/nct6694.h

Comments

Lee Jones May 1, 2025, 12:22 p.m. UTC | #1
On Wed, 23 Apr 2025, a0282524688@gmail.com wrote:

> From: Ming Yu <tmyu0@nuvoton.com>
> 
> The Nuvoton NCT6694 provides an USB interface to the host to
> access its features.
> 
> Sub-devices can use the USB functions nct6694_read_msg() and
> nct6694_write_msg() to issue a command. They can also request
> interrupt that will be called when the USB device receives its
> interrupt pipe.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---

v10 and no change log?  Please add a change log.

>  MAINTAINERS                 |   6 +
>  drivers/mfd/Kconfig         |  15 ++
>  drivers/mfd/Makefile        |   2 +
>  drivers/mfd/nct6694.c       | 387 ++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/nct6694.h | 101 ++++++++++
>  5 files changed, 511 insertions(+)
>  create mode 100644 drivers/mfd/nct6694.c
>  create mode 100644 include/linux/mfd/nct6694.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fa1e04e87d1d..b2dfcc063f88 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17358,6 +17358,12 @@ F:	drivers/nubus/
>  F:	include/linux/nubus.h
>  F:	include/uapi/linux/nubus.h
>  
> +NUVOTON NCT6694 MFD DRIVER
> +M:	Ming Yu <tmyu0@nuvoton.com>
> +S:	Supported
> +F:	drivers/mfd/nct6694.c
> +F:	include/linux/mfd/nct6694.h
> +
>  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
>  M:	Antonino Daplas <adaplas@gmail.com>
>  L:	linux-fbdev@vger.kernel.org
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 22b936310039..cd4d826a7fcb 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1058,6 +1058,21 @@ config MFD_MENF21BMC
>  	  This driver can also be built as a module. If so the module
>  	  will be called menf21bmc.
>  
> +config MFD_NCT6694
> +	tristate "Nuvoton NCT6694 support"
> +	select MFD_CORE
> +	depends on USB
> +	help
> +	  This enables support for the Nuvoton USB device NCT6694, which shares
> +	  peripherals.
> +	  The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> +	  6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> +	  PWM, and RTC.
> +	  This driver provides core APIs to access the NCT6694 hardware
> +	  monitoring and control features.
> +	  Additional drivers must be enabled to utilize the specific
> +	  functionalities of the device.
> +
>  config MFD_OCELOT
>  	tristate "Microsemi Ocelot External Control Support"
>  	depends on SPI_MASTER
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 948cbdf42a18..471dc1f183b8 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
>  obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
>  obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
>  
> +obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
> +
>  obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
>  
>  ocelot-soc-objs			:= ocelot-core.o ocelot-spi.o
> diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> new file mode 100644
> index 000000000000..2480ca56f350
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,387 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + *
> + * Nuvoton NCT6694 core driver using USB interface to provide
> + * access to the NCT6694 hardware monitoring and control features.
> + *
> + * The NCT6694 is an integrated controller that provides GPIO, I2C,
> + * CAN, WDT, HWMON and RTC management.
> + *

Superfluous blank line.

> + */
> +
> +#include <linux/bits.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.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/slab.h>
> +#include <linux/usb.h>
> +
> +static const struct mfd_cell nct6694_devs[] = {
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),

These are all identical.

I thought you were going to use PLATFORM_DEVID_AUTO?  In fact, you are
already using PLATFORM_DEVID_AUTO since you are calling
mfd_add_hotplug_devices().  So you don't need this IDs.

MFD_CELL_NAME() should do.

> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> +
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_NAME("nct6694-hwmon"),
> +	MFD_CELL_NAME("nct6694-rtc"),
> +};

[...]

> diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> new file mode 100644
> index 000000000000..7a02e5b14bbb
> --- /dev/null
> +++ b/include/linux/mfd/nct6694.h
> @@ -0,0 +1,101 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + *
> + * Nuvoton NCT6694 USB transaction and data structure.
> + *

Remove this line please.

> + */
> +
> +#ifndef __MFD_NCT6694_H
> +#define __MFD_NCT6694_H
> +
> +#define NCT6694_VENDOR_ID	0x0416
> +#define NCT6694_PRODUCT_ID	0x200B
> +#define NCT6694_INT_IN_EP	0x81
> +#define NCT6694_BULK_IN_EP	0x02
> +#define NCT6694_BULK_OUT_EP	0x03
> +
> +#define NCT6694_HCTRL_SET	0x40
> +#define NCT6694_HCTRL_GET	0x80
> +
> +#define NCT6694_URB_TIMEOUT	1000
> +
> +enum nct6694_irq_id {
> +	NCT6694_IRQ_GPIO0 = 0,
> +	NCT6694_IRQ_GPIO1,
> +	NCT6694_IRQ_GPIO2,
> +	NCT6694_IRQ_GPIO3,
> +	NCT6694_IRQ_GPIO4,
> +	NCT6694_IRQ_GPIO5,
> +	NCT6694_IRQ_GPIO6,
> +	NCT6694_IRQ_GPIO7,
> +	NCT6694_IRQ_GPIO8,
> +	NCT6694_IRQ_GPIO9,
> +	NCT6694_IRQ_GPIOA,
> +	NCT6694_IRQ_GPIOB,
> +	NCT6694_IRQ_GPIOC,
> +	NCT6694_IRQ_GPIOD,
> +	NCT6694_IRQ_GPIOE,
> +	NCT6694_IRQ_GPIOF,
> +	NCT6694_IRQ_CAN0,
> +	NCT6694_IRQ_CAN1,
> +	NCT6694_IRQ_RTC,
> +	NCT6694_NR_IRQS,
> +};
> +
> +enum nct6694_response_err_status {
> +	NCT6694_NO_ERROR = 0,
> +	NCT6694_FORMAT_ERROR,
> +	NCT6694_RESERVED1,
> +	NCT6694_RESERVED2,
> +	NCT6694_NOT_SUPPORT_ERROR,
> +	NCT6694_NO_RESPONSE_ERROR,
> +	NCT6694_TIMEOUT_ERROR,
> +	NCT6694_PENDING,
> +};
> +
> +struct __packed nct6694_cmd_header {
> +	u8 rsv1;
> +	u8 mod;
> +	union __packed {
> +		__le16 offset;
> +		struct __packed {
> +			u8 cmd;
> +			u8 sel;
> +		};
> +	};
> +	u8 hctrl;
> +	u8 rsv2;
> +	__le16 len;
> +};
> +
> +struct __packed nct6694_response_header {
> +	u8 sequence_id;
> +	u8 sts;
> +	u8 reserved[4];
> +	__le16 len;
> +};
> +
> +union __packed nct6694_usb_msg {
> +	struct nct6694_cmd_header cmd_header;
> +	struct nct6694_response_header response_header;
> +};
> +
> +struct nct6694 {
> +	struct device *dev;
> +	struct irq_domain *domain;
> +	/* Mutex to protect access to the device */

Place these single line comments on the end of the line you're commenting.

Actually, considering the nomenclature here, they're probably not
required at all.

> +	struct mutex access_lock;
> +	/* Mutex to protect access to the IRQ */
> +	struct mutex irq_lock;
> +	struct urb *int_in_urb;
> +	struct usb_device *udev;
> +	union nct6694_usb_msg *usb_msg;
> +	unsigned char *int_buffer;
> +	unsigned int irq_enable;
> +};
> +
> +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> +
> +#endif
> -- 
> 2.34.1
>
Ming Yu May 2, 2025, 2:02 a.m. UTC | #2
Dear Lee,

Thank you for your review. I have a few questions and would appreciate
your advice.

Lee Jones <lee@kernel.org> 於 2025年5月1日 週四 下午8:22寫道:
>
> On Wed, 23 Apr 2025, a0282524688@gmail.com wrote:
>
> > From: Ming Yu <tmyu0@nuvoton.com>
> >
> > The Nuvoton NCT6694 provides an USB interface to the host to
> > access its features.
> >
> > Sub-devices can use the USB functions nct6694_read_msg() and
> > nct6694_write_msg() to issue a command. They can also request
> > interrupt that will be called when the USB device receives its
> > interrupt pipe.
> >
> > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> > ---
>
> v10 and no change log?  Please add a change log.
>

The change log is currently available at
https://patchwork.ozlabs.org/project/rtc-linux/cover/20250423094058.1656204-1-tmyu0@nuvoton.com/
I will move the relevant entries into each individual patch in the
next revision.

> >  MAINTAINERS                 |   6 +
> >  drivers/mfd/Kconfig         |  15 ++
> >  drivers/mfd/Makefile        |   2 +
> >  drivers/mfd/nct6694.c       | 387 ++++++++++++++++++++++++++++++++++++
> >  include/linux/mfd/nct6694.h | 101 ++++++++++
> >  5 files changed, 511 insertions(+)
> >  create mode 100644 drivers/mfd/nct6694.c
> >  create mode 100644 include/linux/mfd/nct6694.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index fa1e04e87d1d..b2dfcc063f88 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -17358,6 +17358,12 @@ F:   drivers/nubus/
> >  F:   include/linux/nubus.h
> >  F:   include/uapi/linux/nubus.h
> >
> > +NUVOTON NCT6694 MFD DRIVER
> > +M:   Ming Yu <tmyu0@nuvoton.com>
> > +S:   Supported
> > +F:   drivers/mfd/nct6694.c
> > +F:   include/linux/mfd/nct6694.h
> > +
> >  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> >  M:   Antonino Daplas <adaplas@gmail.com>
> >  L:   linux-fbdev@vger.kernel.org
> > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > index 22b936310039..cd4d826a7fcb 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -1058,6 +1058,21 @@ config MFD_MENF21BMC
> >         This driver can also be built as a module. If so the module
> >         will be called menf21bmc.
> >
> > +config MFD_NCT6694
> > +     tristate "Nuvoton NCT6694 support"
> > +     select MFD_CORE
> > +     depends on USB
> > +     help
> > +       This enables support for the Nuvoton USB device NCT6694, which shares
> > +       peripherals.
> > +       The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> > +       6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> > +       PWM, and RTC.
> > +       This driver provides core APIs to access the NCT6694 hardware
> > +       monitoring and control features.
> > +       Additional drivers must be enabled to utilize the specific
> > +       functionalities of the device.
> > +
> >  config MFD_OCELOT
> >       tristate "Microsemi Ocelot External Control Support"
> >       depends on SPI_MASTER
> > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> > index 948cbdf42a18..471dc1f183b8 100644
> > --- a/drivers/mfd/Makefile
> > +++ b/drivers/mfd/Makefile
> > @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
> >  obj-$(CONFIG_MFD_MC13XXX_SPI)        += mc13xxx-spi.o
> >  obj-$(CONFIG_MFD_MC13XXX_I2C)        += mc13xxx-i2c.o
> >
> > +obj-$(CONFIG_MFD_NCT6694)    += nct6694.o
> > +
> >  obj-$(CONFIG_MFD_CORE)               += mfd-core.o
> >
> >  ocelot-soc-objs                      := ocelot-core.o ocelot-spi.o
> > diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> > new file mode 100644
> > index 000000000000..2480ca56f350
> > --- /dev/null
> > +++ b/drivers/mfd/nct6694.c
> > @@ -0,0 +1,387 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + *
> > + * Nuvoton NCT6694 core driver using USB interface to provide
> > + * access to the NCT6694 hardware monitoring and control features.
> > + *
> > + * The NCT6694 is an integrated controller that provides GPIO, I2C,
> > + * CAN, WDT, HWMON and RTC management.
> > + *
>
> Superfluous blank line.
>

Remove it in v11.

> > + */
> > +
> > +#include <linux/bits.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/irq.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/slab.h>
> > +#include <linux/usb.h>
> > +
> > +static const struct mfd_cell nct6694_devs[] = {
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
>
> These are all identical.
>
> I thought you were going to use PLATFORM_DEVID_AUTO?  In fact, you are
> already using PLATFORM_DEVID_AUTO since you are calling
> mfd_add_hotplug_devices().  So you don't need this IDs.
>
> MFD_CELL_NAME() should do.
>

Yes, it uses PLATFORM_DEVID_AUTO, but in my implementation, the
sub-devices use cell->id instead of platform_device->id, so it doesn't
affect the current behavior.
However, if you think there's a better approach or that this should be
changed for consistency or correctness, I'm happy to update it, please
let me know your recommendation.

When using MFD_CELL_NAME(), the platform_device->id for the GPIO
devices is assigned values from 1 to 16, and for the I2C devices from
1 to 6, but I need the ID offset to start from 0 instead.

> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > +
> > +     MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
> > +
> > +     MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
> > +
> > +     MFD_CELL_NAME("nct6694-hwmon"),
> > +     MFD_CELL_NAME("nct6694-rtc"),
> > +};
>
> [...]
>
> > diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> > new file mode 100644
> > index 000000000000..7a02e5b14bbb
> > --- /dev/null
> > +++ b/include/linux/mfd/nct6694.h
> > @@ -0,0 +1,101 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + *
> > + * Nuvoton NCT6694 USB transaction and data structure.
> > + *
>
> Remove this line please.
>

Remove it in v11.

> > + */
> > +
> > +#ifndef __MFD_NCT6694_H
> > +#define __MFD_NCT6694_H
> > +
> > +#define NCT6694_VENDOR_ID    0x0416
> > +#define NCT6694_PRODUCT_ID   0x200B
> > +#define NCT6694_INT_IN_EP    0x81
> > +#define NCT6694_BULK_IN_EP   0x02
> > +#define NCT6694_BULK_OUT_EP  0x03
> > +
> > +#define NCT6694_HCTRL_SET    0x40
> > +#define NCT6694_HCTRL_GET    0x80
> > +
> > +#define NCT6694_URB_TIMEOUT  1000
> > +
> > +enum nct6694_irq_id {
> > +     NCT6694_IRQ_GPIO0 = 0,
> > +     NCT6694_IRQ_GPIO1,
> > +     NCT6694_IRQ_GPIO2,
> > +     NCT6694_IRQ_GPIO3,
> > +     NCT6694_IRQ_GPIO4,
> > +     NCT6694_IRQ_GPIO5,
> > +     NCT6694_IRQ_GPIO6,
> > +     NCT6694_IRQ_GPIO7,
> > +     NCT6694_IRQ_GPIO8,
> > +     NCT6694_IRQ_GPIO9,
> > +     NCT6694_IRQ_GPIOA,
> > +     NCT6694_IRQ_GPIOB,
> > +     NCT6694_IRQ_GPIOC,
> > +     NCT6694_IRQ_GPIOD,
> > +     NCT6694_IRQ_GPIOE,
> > +     NCT6694_IRQ_GPIOF,
> > +     NCT6694_IRQ_CAN0,
> > +     NCT6694_IRQ_CAN1,
> > +     NCT6694_IRQ_RTC,
> > +     NCT6694_NR_IRQS,
> > +};
> > +
> > +enum nct6694_response_err_status {
> > +     NCT6694_NO_ERROR = 0,
> > +     NCT6694_FORMAT_ERROR,
> > +     NCT6694_RESERVED1,
> > +     NCT6694_RESERVED2,
> > +     NCT6694_NOT_SUPPORT_ERROR,
> > +     NCT6694_NO_RESPONSE_ERROR,
> > +     NCT6694_TIMEOUT_ERROR,
> > +     NCT6694_PENDING,
> > +};
> > +
> > +struct __packed nct6694_cmd_header {
> > +     u8 rsv1;
> > +     u8 mod;
> > +     union __packed {
> > +             __le16 offset;
> > +             struct __packed {
> > +                     u8 cmd;
> > +                     u8 sel;
> > +             };
> > +     };
> > +     u8 hctrl;
> > +     u8 rsv2;
> > +     __le16 len;
> > +};
> > +
> > +struct __packed nct6694_response_header {
> > +     u8 sequence_id;
> > +     u8 sts;
> > +     u8 reserved[4];
> > +     __le16 len;
> > +};
> > +
> > +union __packed nct6694_usb_msg {
> > +     struct nct6694_cmd_header cmd_header;
> > +     struct nct6694_response_header response_header;
> > +};
> > +
> > +struct nct6694 {
> > +     struct device *dev;
> > +     struct irq_domain *domain;
> > +     /* Mutex to protect access to the device */
>
> Place these single line comments on the end of the line you're commenting.
>
> Actually, considering the nomenclature here, they're probably not
> required at all.
>

Remove these in v11.

> > +     struct mutex access_lock;
> > +     /* Mutex to protect access to the IRQ */
> > +     struct mutex irq_lock;
> > +     struct urb *int_in_urb;
> > +     struct usb_device *udev;
> > +     union nct6694_usb_msg *usb_msg;
> > +     unsigned char *int_buffer;
> > +     unsigned int irq_enable;
> > +};
> > +
> > +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> > +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> > +
> > +#endif
> > --
> > 2.34.1
> >
>


Best regards,
Ming
Lee Jones May 2, 2025, 8:07 a.m. UTC | #3
On Fri, 02 May 2025, Ming Yu wrote:

> Dear Lee,
> 
> Thank you for your review. I have a few questions and would appreciate
> your advice.
> 
> Lee Jones <lee@kernel.org> 於 2025年5月1日 週四 下午8:22寫道:
> >
> > On Wed, 23 Apr 2025, a0282524688@gmail.com wrote:
> >
> > > From: Ming Yu <tmyu0@nuvoton.com>
> > >
> > > The Nuvoton NCT6694 provides an USB interface to the host to
> > > access its features.
> > >
> > > Sub-devices can use the USB functions nct6694_read_msg() and
> > > nct6694_write_msg() to issue a command. They can also request
> > > interrupt that will be called when the USB device receives its
> > > interrupt pipe.
> > >
> > > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> > > ---
> >
> > v10 and no change log?  Please add a change log.
> >
> 
> The change log is currently available at
> https://patchwork.ozlabs.org/project/rtc-linux/cover/20250423094058.1656204-1-tmyu0@nuvoton.com/
> I will move the relevant entries into each individual patch in the
> next revision.
> 
> > >  MAINTAINERS                 |   6 +
> > >  drivers/mfd/Kconfig         |  15 ++
> > >  drivers/mfd/Makefile        |   2 +
> > >  drivers/mfd/nct6694.c       | 387 ++++++++++++++++++++++++++++++++++++
> > >  include/linux/mfd/nct6694.h | 101 ++++++++++
> > >  5 files changed, 511 insertions(+)
> > >  create mode 100644 drivers/mfd/nct6694.c
> > >  create mode 100644 include/linux/mfd/nct6694.h
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index fa1e04e87d1d..b2dfcc063f88 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -17358,6 +17358,12 @@ F:   drivers/nubus/
> > >  F:   include/linux/nubus.h
> > >  F:   include/uapi/linux/nubus.h
> > >
> > > +NUVOTON NCT6694 MFD DRIVER
> > > +M:   Ming Yu <tmyu0@nuvoton.com>
> > > +S:   Supported
> > > +F:   drivers/mfd/nct6694.c
> > > +F:   include/linux/mfd/nct6694.h
> > > +
> > >  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> > >  M:   Antonino Daplas <adaplas@gmail.com>
> > >  L:   linux-fbdev@vger.kernel.org
> > > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > > index 22b936310039..cd4d826a7fcb 100644
> > > --- a/drivers/mfd/Kconfig
> > > +++ b/drivers/mfd/Kconfig
> > > @@ -1058,6 +1058,21 @@ config MFD_MENF21BMC
> > >         This driver can also be built as a module. If so the module
> > >         will be called menf21bmc.
> > >
> > > +config MFD_NCT6694
> > > +     tristate "Nuvoton NCT6694 support"
> > > +     select MFD_CORE
> > > +     depends on USB
> > > +     help
> > > +       This enables support for the Nuvoton USB device NCT6694, which shares
> > > +       peripherals.
> > > +       The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> > > +       6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> > > +       PWM, and RTC.
> > > +       This driver provides core APIs to access the NCT6694 hardware
> > > +       monitoring and control features.
> > > +       Additional drivers must be enabled to utilize the specific
> > > +       functionalities of the device.
> > > +
> > >  config MFD_OCELOT
> > >       tristate "Microsemi Ocelot External Control Support"
> > >       depends on SPI_MASTER
> > > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> > > index 948cbdf42a18..471dc1f183b8 100644
> > > --- a/drivers/mfd/Makefile
> > > +++ b/drivers/mfd/Makefile
> > > @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
> > >  obj-$(CONFIG_MFD_MC13XXX_SPI)        += mc13xxx-spi.o
> > >  obj-$(CONFIG_MFD_MC13XXX_I2C)        += mc13xxx-i2c.o
> > >
> > > +obj-$(CONFIG_MFD_NCT6694)    += nct6694.o
> > > +
> > >  obj-$(CONFIG_MFD_CORE)               += mfd-core.o
> > >
> > >  ocelot-soc-objs                      := ocelot-core.o ocelot-spi.o
> > > diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> > > new file mode 100644
> > > index 000000000000..2480ca56f350
> > > --- /dev/null
> > > +++ b/drivers/mfd/nct6694.c
> > > @@ -0,0 +1,387 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > > + *
> > > + * Nuvoton NCT6694 core driver using USB interface to provide
> > > + * access to the NCT6694 hardware monitoring and control features.
> > > + *
> > > + * The NCT6694 is an integrated controller that provides GPIO, I2C,
> > > + * CAN, WDT, HWMON and RTC management.
> > > + *
> >
> > Superfluous blank line.
> >
> 
> Remove it in v11.
> 
> > > + */
> > > +
> > > +#include <linux/bits.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/irq.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/slab.h>
> > > +#include <linux/usb.h>
> > > +
> > > +static const struct mfd_cell nct6694_devs[] = {
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> >
> > These are all identical.
> >
> > I thought you were going to use PLATFORM_DEVID_AUTO?  In fact, you are
> > already using PLATFORM_DEVID_AUTO since you are calling
> > mfd_add_hotplug_devices().  So you don't need this IDs.
> >
> > MFD_CELL_NAME() should do.
> >
> 
> Yes, it uses PLATFORM_DEVID_AUTO, but in my implementation, the
> sub-devices use cell->id instead of platform_device->id, so it doesn't
> affect the current behavior.
> However, if you think there's a better approach or that this should be
> changed for consistency or correctness, I'm happy to update it, please
> let me know your recommendation.
> 
> When using MFD_CELL_NAME(), the platform_device->id for the GPIO
> devices is assigned values from 1 to 16, and for the I2C devices from
> 1 to 6, but I need the ID offset to start from 0 instead.

Oh no, don't do that.  mfd_cell isn't supposed to be used outside of MFD.

Just use the platform_device id-- if you really need to start from 0.

As an aside, I'm surprised numbering starts from 1.
Ming Yu May 2, 2025, 9:04 a.m. UTC | #4
Lee Jones <lee@kernel.org> 於 2025年5月2日 週五 下午4:08寫道:
>
...
> > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > >
> > > These are all identical.
> > >
> > > I thought you were going to use PLATFORM_DEVID_AUTO?  In fact, you are
> > > already using PLATFORM_DEVID_AUTO since you are calling
> > > mfd_add_hotplug_devices().  So you don't need this IDs.
> > >
> > > MFD_CELL_NAME() should do.
> > >
> >
> > Yes, it uses PLATFORM_DEVID_AUTO, but in my implementation, the
> > sub-devices use cell->id instead of platform_device->id, so it doesn't
> > affect the current behavior.
> > However, if you think there's a better approach or that this should be
> > changed for consistency or correctness, I'm happy to update it, please
> > let me know your recommendation.
> >
> > When using MFD_CELL_NAME(), the platform_device->id for the GPIO
> > devices is assigned values from 1 to 16, and for the I2C devices from
> > 1 to 6, but I need the ID offset to start from 0 instead.
>
> Oh no, don't do that.  mfd_cell isn't supposed to be used outside of MFD.
>
> Just use the platform_device id-- if you really need to start from 0.
>
> As an aside, I'm surprised numbering starts from 1.
>

OK, I will use platform_device->id instead. However, I'm still unsure
why the ID starts from1.

Additionally, I noticed that when calling mfd_add_devices()
separately, the IDs are also assigned consecutively (e.g., GPIO: 1~16,
I2C: 17~22, ...).

Do you have any recommendations on how I should implement this?


Thanks,
Ming
Marc Kleine-Budde May 3, 2025, 1:57 p.m. UTC | #5
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