netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers
@ 2024-12-10 10:45 Ming Yu
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
                   ` (6 more replies)
  0 siblings, 7 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

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 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 CAN support
  watchdog: Add Nuvoton NCT6694 WDT support
  hwmon: Add Nuvoton NCT6694 HWMON support
  rtc: Add Nuvoton NCT6694 RTC support

 MAINTAINERS                      |  13 +
 drivers/gpio/Kconfig             |  12 +
 drivers/gpio/Makefile            |   1 +
 drivers/gpio/gpio-nct6694.c      | 438 +++++++++++++++
 drivers/hwmon/Kconfig            |  10 +
 drivers/hwmon/Makefile           |   1 +
 drivers/hwmon/nct6694-hwmon.c    | 768 ++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig       |  10 +
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-nct6694.c | 153 +++++
 drivers/mfd/Kconfig              |  10 +
 drivers/mfd/Makefile             |   2 +
 drivers/mfd/nct6694.c            | 382 +++++++++++++
 drivers/net/can/Kconfig          |  10 +
 drivers/net/can/Makefile         |   1 +
 drivers/net/can/nct6694_canfd.c  | 920 +++++++++++++++++++++++++++++++
 drivers/rtc/Kconfig              |  10 +
 drivers/rtc/Makefile             |   1 +
 drivers/rtc/rtc-nct6694.c        | 264 +++++++++
 drivers/watchdog/Kconfig         |  11 +
 drivers/watchdog/Makefile        |   1 +
 drivers/watchdog/nct6694_wdt.c   | 277 ++++++++++
 include/linux/mfd/nct6694.h      | 139 +++++
 23 files changed, 3435 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/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

-- 
2.34.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-10 10:57   ` Mateusz Polchlopek
                     ` (3 more replies)
  2024-12-10 10:45 ` [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
                   ` (5 subsequent siblings)
  6 siblings, 4 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

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 implements USB device functionality and shares the
chip's peripherals as a child device.

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.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                 |   7 +
 drivers/mfd/Kconfig         |  10 +
 drivers/mfd/Makefile        |   2 +
 drivers/mfd/nct6694.c       | 382 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/nct6694.h | 139 +++++++++++++
 5 files changed, 540 insertions(+)
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 include/linux/mfd/nct6694.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 67d2159406c2..9c9a94ff8f98 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16541,6 +16541,13 @@ F:	drivers/nubus/
 F:	include/linux/nubus.h
 F:	include/uapi/linux/nubus.h
 
+NUVOTON NCT6694 MFD DRIVER
+M:	Ming Yu <tmyu0@nuvoton.com>
+L:	linux-kernel@vger.kernel.org
+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 03c1e4e3eea4..07ccc32f9985 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -558,6 +558,16 @@ config MFD_MX25_TSADC
 	  i.MX25 processors. They consist of a conversion queue for general
 	  purpose ADC and a queue for Touchscreens.
 
+config MFD_NCT6694
+	tristate "Nuvoton NCT6694 support"
+	select MFD_CORE
+	depends on USB
+	help
+	  This adds support for Nuvoton USB device NCT6694 sharing peripherals
+	  This includes the USB devcie driver and core APIs.
+	  Additional drivers must be enabled in order to use the functionality
+	  of the device.
+
 config MFD_HI6421_PMIC
 	tristate "HiSilicon Hi6421 PMU/Codec IC"
 	depends on OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e057d6d6faef..9d0365ba6a26 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -117,6 +117,8 @@ obj-$(CONFIG_TWL6040_CORE)	+= twl6040.o
 
 obj-$(CONFIG_MFD_MX25_TSADC)	+= fsl-imx25-tsadc.o
 
+obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
+
 obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
 obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
 obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
new file mode 100644
index 000000000000..071333d13eae
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 MFD driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#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>
+
+#define MFD_DEV_SIMPLE(_name)				\
+{							\
+	.name = NCT6694_DEV_##_name,			\
+}							\
+
+#define MFD_DEV_WITH_ID(_name, _id)			\
+{							\
+	.name = NCT6694_DEV_##_name,			\
+	.id = _id,					\
+}
+
+/* MFD device resources */
+static const struct mfd_cell nct6694_dev[] = {
+	MFD_DEV_WITH_ID(GPIO, 0x0),
+	MFD_DEV_WITH_ID(GPIO, 0x1),
+	MFD_DEV_WITH_ID(GPIO, 0x2),
+	MFD_DEV_WITH_ID(GPIO, 0x3),
+	MFD_DEV_WITH_ID(GPIO, 0x4),
+	MFD_DEV_WITH_ID(GPIO, 0x5),
+	MFD_DEV_WITH_ID(GPIO, 0x6),
+	MFD_DEV_WITH_ID(GPIO, 0x7),
+	MFD_DEV_WITH_ID(GPIO, 0x8),
+	MFD_DEV_WITH_ID(GPIO, 0x9),
+	MFD_DEV_WITH_ID(GPIO, 0xA),
+	MFD_DEV_WITH_ID(GPIO, 0xB),
+	MFD_DEV_WITH_ID(GPIO, 0xC),
+	MFD_DEV_WITH_ID(GPIO, 0xD),
+	MFD_DEV_WITH_ID(GPIO, 0xE),
+	MFD_DEV_WITH_ID(GPIO, 0xF),
+
+	MFD_DEV_WITH_ID(I2C, 0x0),
+	MFD_DEV_WITH_ID(I2C, 0x1),
+	MFD_DEV_WITH_ID(I2C, 0x2),
+	MFD_DEV_WITH_ID(I2C, 0x3),
+	MFD_DEV_WITH_ID(I2C, 0x4),
+	MFD_DEV_WITH_ID(I2C, 0x5),
+
+	MFD_DEV_WITH_ID(CAN, 0x0),
+	MFD_DEV_WITH_ID(CAN, 0x1),
+
+	MFD_DEV_WITH_ID(WDT, 0x0),
+	MFD_DEV_WITH_ID(WDT, 0x1),
+
+	MFD_DEV_SIMPLE(HWMON),
+	MFD_DEV_SIMPLE(RTC),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694,
+					 unsigned char err_status)
+{
+	struct device *dev = &nct6694->udev->dev;
+
+	switch (err_status) {
+	case NCT6694_NO_ERROR:
+		return err_status;
+	case NCT6694_NOT_SUPPORT_ERROR:
+		dev_dbg(dev, "%s: Command is not support!\n", __func__);
+		break;
+	case NCT6694_NO_RESPONSE_ERROR:
+		dev_dbg(dev, "%s: Command is no response!\n", __func__);
+		break;
+	case NCT6694_TIMEOUT_ERROR:
+		dev_dbg(dev, "%s: Command is timeout!\n", __func__);
+		break;
+	case NCT6694_PENDING:
+		dev_dbg(dev, "%s: Command is pending!\n", __func__);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return -EIO;
+}
+
+int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+		     u16 length, void *buf)
+{
+	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
+	struct nct6694_response_header *response_header = nct6694->response_header;
+	struct usb_device *udev = nct6694->udev;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	/* Send command packet to USB device */
+	cmd_header->mod = mod;
+	cmd_header->cmd = offset & 0xFF;
+	cmd_header->sel = (offset >> 8) & 0xFF;
+	cmd_header->hctrl = NCT6694_HCTRL_GET;
+	cmd_header->len = length;
+
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+			   cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+			   response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+			   buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
+	if (ret)
+		return ret;
+
+	return nct6694_response_err_handling(nct6694, response_header->sts);
+}
+EXPORT_SYMBOL(nct6694_read_msg);
+
+int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+		      u16 length, void *buf)
+{
+	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
+	struct nct6694_response_header *response_header = nct6694->response_header;
+	struct usb_device *udev = nct6694->udev;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	/* Send command packet to USB device  */
+	cmd_header->mod = mod;
+	cmd_header->cmd = offset & 0xFF;
+	cmd_header->sel = (offset >> 8) & 0xFF;
+	cmd_header->hctrl = NCT6694_HCTRL_SET;
+	cmd_header->len = length;
+
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+			   cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+			   buf, length, &tx_len, nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+			   response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+			   buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
+	if (ret)
+		return ret;
+
+	return nct6694_response_err_handling(nct6694, response_header->sts);
+}
+EXPORT_SYMBOL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+	struct nct6694 *nct6694 = urb->context;
+	struct device *dev = &nct6694->udev->dev;
+	unsigned int *int_status = urb->transfer_buffer;
+	int ret;
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:
+		goto resubmit;
+	}
+
+	while (*int_status) {
+		int irq = __ffs(*int_status);
+
+		if (*int_status & (1 << irq))
+			generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+
+		*int_status &= ~(1 << irq);
+	}
+
+resubmit:
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret)
+		dev_dbg(dev, "%s: Failed to resubmit urb, status %d",
+			__func__, ret);
+}
+
+static void nct6694_irq_lock(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_sync_unlock(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+	mutex_unlock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+	unsigned int irq = data->hwirq;
+
+	nct6694->irq_enable |= (1 << irq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+	unsigned int irq = data->hwirq;
+
+	nct6694->irq_enable &= ~(1 << irq);
+}
+
+static struct irq_chip nct6694_irq_chip = {
+	.name = "nct6694-irq",
+	.flags = IRQCHIP_SKIP_SET_WAKE,
+	.irq_bus_lock = nct6694_irq_lock,
+	.irq_bus_sync_unlock = nct6694_irq_sync_unlock,
+	.irq_enable = nct6694_irq_enable,
+	.irq_disable = nct6694_irq_disable,
+};
+
+static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq,
+				  irq_hw_number_t hw)
+{
+	struct nct6694 *nct6694 = d->host_data;
+
+	irq_set_chip_data(irq, nct6694);
+	irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+	return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+	irq_set_chip_and_handler(irq, NULL, NULL);
+	irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+	.map	= nct6694_irq_domain_map,
+	.unmap	= nct6694_irq_domain_unmap,
+};
+
+static int nct6694_usb_probe(struct usb_interface *iface,
+			     const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct device *dev = &udev->dev;
+	struct usb_host_interface *interface;
+	struct usb_endpoint_descriptor *int_endpoint;
+	struct nct6694 *nct6694;
+	struct nct6694_cmd_header *cmd_header;
+	struct nct6694_response_header *response_header;
+	int pipe, maxp;
+	int ret;
+
+	interface = iface->cur_altsetting;
+
+	int_endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(int_endpoint))
+		return -ENODEV;
+
+	nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+	if (!nct6694)
+		return -ENOMEM;
+
+	pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
+	maxp = usb_maxpacket(udev, pipe);
+
+	cmd_header = devm_kzalloc(dev, sizeof(*cmd_header),
+				  GFP_KERNEL);
+	if (!cmd_header)
+		return -ENOMEM;
+
+	response_header = devm_kzalloc(dev, sizeof(*response_header),
+				       GFP_KERNEL);
+	if (!response_header)
+		return -ENOMEM;
+
+	nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
+					   sizeof(unsigned char), GFP_KERNEL);
+	if (!nct6694->int_buffer)
+		return -ENOMEM;
+
+	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!nct6694->int_in_urb)
+		return -ENOMEM;
+
+	nct6694->domain = irq_domain_add_simple(NULL, NCT6694_NR_IRQS, 0,
+						&nct6694_irq_domain_ops,
+						nct6694);
+	if (!nct6694->domain)
+		return -ENODEV;
+
+	nct6694->udev = udev;
+	nct6694->timeout = NCT6694_URB_TIMEOUT;	/* Wait until urb complete */
+	nct6694->cmd_header = cmd_header;
+	nct6694->response_header = response_header;
+
+	mutex_init(&nct6694->access_lock);
+	mutex_init(&nct6694->irq_lock);
+
+	usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
+			 nct6694->int_buffer, maxp, usb_int_callback,
+			 nct6694, int_endpoint->bInterval);
+	ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+	if (ret)
+		goto err_urb;
+
+	dev_set_drvdata(dev, nct6694);
+	usb_set_intfdata(iface, nct6694);
+
+	ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
+	if (ret)
+		goto err_mfd;
+
+	dev_info(dev, "Probed device: (%04X:%04X)\n", id->idVendor, id->idProduct);
+	return 0;
+
+err_mfd:
+	usb_kill_urb(nct6694->int_in_urb);
+err_urb:
+	usb_free_urb(nct6694->int_in_urb);
+	return dev_err_probe(dev, ret, "Probe failed\n");
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+	mfd_remove_devices(&udev->dev);
+	usb_kill_urb(nct6694->int_in_urb);
+	usb_free_urb(nct6694->int_in_urb);
+}
+
+static const struct usb_device_id nct6694_ids[] = {
+	{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID,
+					NCT6694_PRODUCT_ID,
+					0xFF, 0x00, 0x00)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, nct6694_ids);
+
+static struct usb_driver nct6694_usb_driver = {
+	.name	= "nct6694",
+	.id_table = nct6694_ids,
+	.probe = nct6694_usb_probe,
+	.disconnect = nct6694_usb_disconnect,
+};
+
+module_usb_driver(nct6694_usb_driver);
+
+MODULE_DESCRIPTION("USB-MFD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
new file mode 100644
index 000000000000..9733d47f5a01
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Nuvoton NCT6694 USB transaction and data structure.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#ifndef __MFD_NCT6694_H
+#define __MFD_NCT6694_H
+
+#define NCT6694_DEV_GPIO	"nct6694-gpio"
+#define NCT6694_DEV_I2C		"nct6694-i2c"
+#define NCT6694_DEV_CAN		"nct6694-can"
+#define NCT6694_DEV_WDT		"nct6694-wdt"
+#define NCT6694_DEV_HWMON	"nct6694-hwmon"
+#define NCT6694_DEV_RTC		"nct6694-rtc"
+
+#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_MAX_PACKET_SZ	0x200
+
+#define NCT6694_CMD_PACKET_SZ	0x8
+#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_CAN1,
+	NCT6694_IRQ_CAN2,
+	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 nct6694 {
+	struct usb_device *udev;
+	struct urb *int_in_urb;
+	struct irq_domain *domain;
+	struct nct6694_cmd_header *cmd_header;
+	struct nct6694_response_header *response_header;
+	struct mutex access_lock;
+	struct mutex irq_lock;
+	unsigned char *int_buffer;
+	unsigned int irq_enable;
+	/* time in msec to wait for the urb to the complete */
+	long timeout;
+};
+
+struct nct6694_cmd_header {
+	unsigned char rsv1;
+	unsigned char mod;
+	unsigned char cmd;
+	unsigned char sel;
+	unsigned char hctrl;
+	unsigned char rsv2;
+	unsigned short len;
+} __packed;
+
+struct nct6694_response_header {
+	unsigned char sequence_id;
+	unsigned char sts;
+	unsigned int rsv;
+	unsigned short len;
+} __packed;
+
+/*
+ * nct6694_read_msg - Receive data from NCT6694 USB device
+ *
+ * @nct6694 - Nuvoton NCT6694 structure
+ * @mod - Module byte
+ * @offset - Offset byte or (Select byte | Command byte)
+ * @length - Length byte
+ * @buf - Read data from rx buffer
+ *
+ * USB Transaction format:
+ *
+ *	OUT	|RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
+ *	OUT	|SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
+ *	IN	|-------D------A------D------A-------|
+ *	IN			......
+ *	IN	|-------D------A------D------A-------|
+ */
+int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+		     u16 length, void *buf);
+
+/*
+ * nct6694_read_msg - Transmit data to NCT6694 USB device
+ *
+ * @nct6694 - Nuvoton NCT6694 structure
+ * @mod - Module byte
+ * @offset - Offset byte or (Select byte | Command byte)
+ * @length - Length byte
+ * @buf - Write data to tx buffer
+ *
+ * USB Transaction format:
+ *
+ *	OUT	|RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
+ *	OUT	|-------D------A------D------A-------|
+ *	OUT			......
+ *	OUT	|-------D------A------D------A-------|
+ *	IN	|SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
+ *	IN	|-------D------A------D------A-------|
+ *	IN			......
+ *	IN	|-------D------A------D------A-------|
+ */
+int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+		      u16 length, void *buf);
+
+#endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-10 12:46   ` Bartosz Golaszewski
  2024-12-20 12:41   ` Linus Walleij
  2024-12-10 10:45 ` [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

This driver supports GPIO and IRQ functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                 |   1 +
 drivers/gpio/Kconfig        |  12 +
 drivers/gpio/Makefile       |   1 +
 drivers/gpio/gpio-nct6694.c | 438 ++++++++++++++++++++++++++++++++++++
 4 files changed, 452 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9c9a94ff8f98..6688c5c470b7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16545,6 +16545,7 @@ NUVOTON NCT6694 MFD DRIVER
 M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
+F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 56fee58e281e..7773b5326e60 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1460,6 +1460,18 @@ config GPIO_MAX77650
 	  GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
 	  These chips have a single pin that can be configured as GPIO.
 
+config GPIO_NCT6694
+	tristate "Nuvoton NCT6694 GPIO controller support"
+	depends on MFD_NCT6694
+	select GENERIC_IRQ_CHIP
+	select GPIOLIB_IRQCHIP
+	help
+	  This driver supports 8 GPIO pins per bank that can all be interrupt
+	  sources.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-nct6694.
+
 config GPIO_PALMAS
 	bool "TI PALMAS series PMICs GPIO"
 	depends on MFD_PALMAS
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b58..ad80a078b27b 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_GPIO_MXC)			+= gpio-mxc.o
 obj-$(CONFIG_GPIO_MXS)			+= gpio-mxs.o
 obj-$(CONFIG_GPIO_NOMADIK)		+= gpio-nomadik.o
 obj-$(CONFIG_GPIO_NPCM_SGPIO)		+= gpio-npcm-sgpio.o
+obj-$(CONFIG_GPIO_NCT6694)		+= gpio-nct6694.o
 obj-$(CONFIG_GPIO_OCTEON)		+= gpio-octeon.o
 obj-$(CONFIG_GPIO_OMAP)			+= gpio-omap.o
 obj-$(CONFIG_GPIO_PALMAS)		+= gpio-palmas.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 000000000000..8aac034e6d06
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/* Host interface */
+#define NCT6694_GPIO_MOD	0xFF
+#define NCT6694_GPIO_LEN	0x01
+
+/* Report Channel */
+#define NCT6694_GPIO_VER	0x90
+#define NCT6694_GPIO_VALID	0x110
+#define NCT6694_GPI_DATA	0x120
+#define NCT6694_GPO_DIR		0x170
+#define NCT6694_GPO_TYPE	0x180
+#define NCT6694_GPO_DATA	0x190
+
+#define NCT6694_GPI_STS		0x130
+#define NCT6694_GPI_CLR		0x140
+#define NCT6694_GPI_FALLING	0x150
+#define NCT6694_GPI_RISING	0x160
+
+#define NCT6694_NR_GPIO		8
+
+struct nct6694_gpio_data {
+	struct nct6694 *nct6694;
+	struct gpio_chip gpio;
+	struct mutex lock;
+	/* Protect irq operation */
+	struct mutex irq_lock;
+
+	unsigned char xmit_buf;
+	unsigned char irq_trig_falling;
+	unsigned char irq_trig_rising;
+
+	/* Current gpio group */
+	unsigned char group;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_DIR + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	return !(BIT(offset) & data->xmit_buf);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_DIR + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	data->xmit_buf &= ~(1 << offset);
+
+	return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				 NCT6694_GPO_DIR + data->group,
+				 NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+				    unsigned int offset, int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	/* Set direction to output */
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_DIR + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	data->xmit_buf |= (1 << offset);
+	ret = nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				NCT6694_GPO_DIR + data->group,
+				NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	/* Then set output level */
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_DATA + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	if (val)
+		data->xmit_buf |= (1 << offset);
+	else
+		data->xmit_buf &= ~(1 << offset);
+
+	return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				 NCT6694_GPO_DATA + data->group,
+				 NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_DIR + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	if (BIT(offset) & data->xmit_buf) {
+		ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+				       NCT6694_GPO_DATA + data->group,
+				       NCT6694_GPIO_LEN, &data->xmit_buf);
+		if (ret < 0)
+			return ret;
+
+		return !!(BIT(offset) & data->xmit_buf);
+	}
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPI_DATA + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	return !!(BIT(offset) & data->xmit_buf);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+			      int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+	guard(mutex)(&data->lock);
+
+	nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			 NCT6694_GPO_DATA + data->group,
+			 NCT6694_GPIO_LEN, &data->xmit_buf);
+
+	if (val)
+		data->xmit_buf |= (1 << offset);
+	else
+		data->xmit_buf &= ~(1 << offset);
+
+	nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+			  NCT6694_GPO_DATA + data->group,
+			  NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+			      unsigned long config)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPO_TYPE + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	switch (pinconf_to_config_param(config)) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		data->xmit_buf |= (1 << offset);
+		break;
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		data->xmit_buf &= ~(1 << offset);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				 NCT6694_GPO_TYPE + data->group,
+				 NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_init_valid_mask(struct gpio_chip *gpio,
+				   unsigned long *valid_mask,
+				   unsigned int ngpios)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPIO_VALID + data->group,
+			       NCT6694_GPIO_LEN, &data->xmit_buf);
+	if (ret < 0)
+		return ret;
+
+	*valid_mask = data->xmit_buf;
+
+	return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+	struct nct6694_gpio_data *data = priv;
+	unsigned char status;
+
+	guard(mutex)(&data->lock);
+
+	nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			 NCT6694_GPI_STS + data->group,
+			 NCT6694_GPIO_LEN, &data->xmit_buf);
+
+	status = data->xmit_buf;
+
+	while (status) {
+		int bit = __ffs(status);
+
+		data->xmit_buf = BIT(bit);
+		handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+		status &= ~(1 << bit);
+		nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				  NCT6694_GPI_CLR + data->group,
+				  NCT6694_GPIO_LEN, &data->xmit_buf);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+			       NCT6694_GPI_FALLING + data->group,
+			       NCT6694_GPIO_LEN, &data->irq_trig_falling);
+	if (ret)
+		return ret;
+
+	return nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+				NCT6694_GPI_RISING + data->group,
+				NCT6694_GPIO_LEN, &data->irq_trig_rising);
+}
+
+static void nct6694_irq_mask(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	gpiochip_disable_irq(gpio, hwirq);
+}
+
+static void nct6694_irq_unmask(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	gpiochip_enable_irq(gpio, hwirq);
+}
+
+static int nct6694_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	guard(mutex)(&data->lock);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		data->irq_trig_rising |= BIT(hwirq);
+		break;
+
+	case IRQ_TYPE_EDGE_FALLING:
+		data->irq_trig_falling |= BIT(hwirq);
+		break;
+
+	case IRQ_TYPE_EDGE_BOTH:
+		data->irq_trig_rising |= BIT(hwirq);
+		data->irq_trig_falling |= BIT(hwirq);
+		break;
+
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static void nct6694_irq_bus_lock(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+	mutex_lock(&data->irq_lock);
+}
+
+static void nct6694_irq_bus_sync_unlock(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+	scoped_guard(mutex, &data->lock) {
+		nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				  NCT6694_GPI_FALLING + data->group,
+				  NCT6694_GPIO_LEN, &data->irq_trig_falling);
+
+		nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+				  NCT6694_GPI_RISING + data->group,
+				  NCT6694_GPIO_LEN, &data->irq_trig_rising);
+	}
+
+	mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+	.name			= "nct6694-gpio",
+	.irq_mask		= nct6694_irq_mask,
+	.irq_unmask		= nct6694_irq_unmask,
+	.irq_set_type		= nct6694_irq_set_type,
+	.irq_bus_lock		= nct6694_irq_bus_lock,
+	.irq_bus_sync_unlock	= nct6694_irq_bus_sync_unlock,
+	.flags			= IRQCHIP_IMMUTABLE,
+	GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int nct6694_gpio_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct device *dev = &pdev->dev;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_gpio_data *data;
+	struct gpio_irq_chip *girq;
+	int ret, irq, i;
+	char **names;
+
+	irq = irq_create_mapping(nct6694->domain,
+				 NCT6694_IRQ_GPIO0 + cell->id);
+	if (!irq)
+		return -EINVAL;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
+			     GFP_KERNEL);
+	if (!names)
+		return -ENOMEM;
+
+	for (i = 0; i < NCT6694_NR_GPIO; i++) {
+		names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
+					  cell->id, i);
+		if (!names[i])
+			return -ENOMEM;
+	}
+
+	data->nct6694 = nct6694;
+	data->group = cell->id;
+
+	data->gpio.names		= (const char * const*)names;
+	data->gpio.label		= pdev->name;
+	data->gpio.direction_input	= nct6694_direction_input;
+	data->gpio.get			= nct6694_get_value;
+	data->gpio.direction_output	= nct6694_direction_output;
+	data->gpio.set			= nct6694_set_value;
+	data->gpio.get_direction	= nct6694_get_direction;
+	data->gpio.set_config		= nct6694_set_config;
+	data->gpio.init_valid_mask	= nct6694_init_valid_mask;
+	data->gpio.base			= -1;
+	data->gpio.can_sleep		= false;
+	data->gpio.owner		= THIS_MODULE;
+	data->gpio.ngpio		= NCT6694_NR_GPIO;
+
+	mutex_init(&data->irq_lock);
+
+	ret = nct6694_get_irq_trig(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+
+	/* Register gpio chip to GPIO framework */
+	girq = &data->gpio.irq;
+	gpio_irq_chip_set_chip(girq, &nct6694_irq_chip);
+	girq->parent_handler = NULL;
+	girq->num_parents = 0;
+	girq->parents = NULL;
+	girq->default_type = IRQ_TYPE_NONE;
+	girq->handler = handle_level_irq;
+	girq->threaded = true;
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_irq_handler,
+					IRQF_ONESHOT | IRQF_SHARED,
+					"nct6694-gpio", data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+	return devm_gpiochip_add_data(dev, &data->gpio, data);
+}
+
+static struct platform_driver nct6694_gpio_driver = {
+	.driver = {
+		.name	= "nct6694-gpio",
+	},
+	.probe		= nct6694_gpio_probe,
+};
+
+module_platform_driver(nct6694_gpio_driver);
+
+MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-gpio");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
  2024-12-10 10:45 ` [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-26  0:43   ` Andi Shyti
  2024-12-10 10:45 ` [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

This driver supports I2C adapter functionality for NCT6694 MFD
device based on USB interface, each I2C controller use default
baudrate(100K).

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                      |   1 +
 drivers/i2c/busses/Kconfig       |  10 ++
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-nct6694.c | 153 +++++++++++++++++++++++++++++++
 4 files changed, 165 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6688c5c470b7..a190f2b08fa3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16546,6 +16546,7 @@ M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
+F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 4977abcd7c46..1962cf1e71f9 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1325,6 +1325,16 @@ config I2C_LJCA
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-ljca.
 
+config I2C_NCT6694
+	tristate "Nuvoton NCT6694 I2C adapter support"
+	depends on MFD_NCT6694
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB to I2C interface.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called i2c-nct6694.
+
 config I2C_CP2615
 	tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
 	depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index a6bcbf2febcf..6d2fd8e56569 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP)		+= i2c-gxp.o
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
 obj-$(CONFIG_I2C_DLN2)		+= i2c-dln2.o
 obj-$(CONFIG_I2C_LJCA)		+= i2c-ljca.o
+obj-$(CONFIG_I2C_NCT6694)	+= i2c-nct6694.o
 obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
 obj-$(CONFIG_I2C_PARPORT)	+= i2c-parport.o
 obj-$(CONFIG_I2C_PCI1XXXX)	+= i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 000000000000..d35e1ea3521f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 I2C adapter driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/* Host interface */
+#define NCT6694_I2C_MOD		0x03
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_I2C_CMD0_OFFSET	0x0000	/* OFFSET = SEL|CMD */
+#define NCT6694_I2C_CMD0_LEN	0x90
+
+enum i2c_baudrate {
+	I2C_BR_25K = 0,
+	I2C_BR_50K,
+	I2C_BR_100K,
+	I2C_BR_200K,
+	I2C_BR_400K,
+	I2C_BR_800K,
+	I2C_BR_1M
+};
+
+struct __packed nct6694_i2c_cmd0 {
+	u8 port;
+	u8 br;
+	u8 addr;
+	u8 w_cnt;
+	u8 r_cnt;
+	u8 rsv[11];
+	u8 write_data[0x40];
+	u8 read_data[0x40];
+};
+
+struct nct6694_i2c_data {
+	struct nct6694 *nct6694;
+	struct i2c_adapter adapter;
+	unsigned char *xmit_buf;
+	unsigned char port;
+	unsigned char br;
+};
+
+static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct nct6694_i2c_data *data = adap->algo_data;
+	struct nct6694_i2c_cmd0 *cmd = (struct nct6694_i2c_cmd0 *)data->xmit_buf;
+	int ret, i;
+
+	for (i = 0; i < num ; i++) {
+		struct i2c_msg *msg_temp = &msgs[i];
+
+		memset(data->xmit_buf, 0, sizeof(struct nct6694_i2c_cmd0));
+
+		if (msg_temp->len > 64)
+			return -EPROTO;
+		cmd->port = data->port;
+		cmd->br = data->br;
+		cmd->addr = i2c_8bit_addr_from_msg(msg_temp);
+		if (msg_temp->flags & I2C_M_RD) {
+			cmd->r_cnt = msg_temp->len;
+			ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
+						NCT6694_I2C_CMD0_OFFSET,
+						NCT6694_I2C_CMD0_LEN,
+						cmd);
+			if (ret < 0)
+				return 0;
+
+			memcpy(msg_temp->buf, cmd->read_data, msg_temp->len);
+		} else {
+			cmd->w_cnt = msg_temp->len;
+			memcpy(cmd->write_data, msg_temp->buf, msg_temp->len);
+			ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
+						NCT6694_I2C_CMD0_OFFSET,
+						NCT6694_I2C_CMD0_LEN,
+						cmd);
+			if (ret < 0)
+				return 0;
+		}
+	}
+
+	return num;
+}
+
+static u32 nct6694_func(struct i2c_adapter *adapter)
+{
+	return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
+}
+
+static const struct i2c_algorithm algorithm = {
+	.master_xfer = nct6694_xfer,
+	.functionality = nct6694_func,
+};
+
+static int nct6694_i2c_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_i2c_data *data;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+				      sizeof(unsigned char), GFP_KERNEL);
+	if (!data->xmit_buf)
+		return -ENOMEM;
+
+	data->nct6694 = nct6694;
+	data->port = cell->id;
+	data->br = I2C_BR_100K;
+
+	sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", cell->id);
+	data->adapter.owner = THIS_MODULE;
+	data->adapter.algo = &algorithm;
+	data->adapter.dev.parent = &pdev->dev;
+	data->adapter.algo_data = data;
+
+	platform_set_drvdata(pdev, data);
+
+	return i2c_add_adapter(&data->adapter);
+}
+
+static void nct6694_i2c_remove(struct platform_device *pdev)
+{
+	struct nct6694_i2c_data *data = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&data->adapter);
+}
+
+static struct platform_driver nct6694_i2c_driver = {
+	.driver = {
+		.name	= "nct6694-i2c",
+	},
+	.probe		= nct6694_i2c_probe,
+	.remove		= nct6694_i2c_remove,
+};
+
+module_platform_driver(nct6694_i2c_driver);
+
+MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-i2c");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
                   ` (2 preceding siblings ...)
  2024-12-10 10:45 ` [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-11  9:59   ` Marc Kleine-Budde
  2024-12-10 10:45 ` [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

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

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

diff --git a/MAINTAINERS b/MAINTAINERS
index a190f2b08fa3..eb5d46825e71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16548,6 +16548,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/nct6694_canfd.c
 F:	include/linux/mfd/nct6694.h
 
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index cf989bea9aa3..130e98ec28a5 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -200,6 +200,16 @@ config CAN_SUN4I
 	  To compile this driver as a module, choose M here: the module will
 	  be called sun4i_can.
 
+config CAN_NCT6694
+	tristate "Nuvoton NCT6694 Socket CANfd support"
+	depends on MFD_NCT6694
+	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_TI_HECC
 	depends on ARM
 	tristate "TI High End CAN Controller"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index a71db2cfe990..4a6b5b9d6c2b 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3)	+= janz-ican3.o
 obj-$(CONFIG_CAN_KVASER_PCIEFD)	+= kvaser_pciefd.o
 obj-$(CONFIG_CAN_MSCAN)		+= mscan/
 obj-$(CONFIG_CAN_M_CAN)		+= m_can/
+obj-$(CONFIG_CAN_NCT6694)	+= nct6694_canfd.o
 obj-$(CONFIG_CAN_PEAK_PCIEFD)	+= peak_canfd/
 obj-$(CONFIG_CAN_SJA1000)	+= sja1000/
 obj-$(CONFIG_CAN_SUN4I)		+= sun4i_can.o
diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c
new file mode 100644
index 000000000000..54f20f0681e2
--- /dev/null
+++ b/drivers/net/can/nct6694_canfd.c
@@ -0,0 +1,920 @@
+// 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/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 DRVNAME "nct6694-can"
+
+/* Host interface */
+#define NCT6694_CAN_MOD			0x05
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_CAN_CMD0_OFFSET(idx)	(idx ? 0x0100 : 0x0000)
+#define NCT6694_CAN_CTRL1_MON		BIT(0)
+#define NCT6694_CAN_CTRL1_NISO		BIT(1)
+#define NCT6694_CAN_CTRL1_LBCK		BIT(2)
+
+/* Command 01h */
+#define NCT6694_CAN_CMD1_OFFSET		0x0001
+
+/* Command 02h */
+#define NCT6694_CAN_CMD2_OFFSET(idx, mask)			\
+	({ typeof(mask) mask_ = (mask);				\
+	   idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) :	\
+		 ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
+
+#define NCT6694_CAN_EVENT_ERR		BIT(0)
+#define NCT6694_CAN_EVENT_STATUS	BIT(1)
+#define NCT6694_CAN_EVENT_TX_EVT	BIT(2)
+#define NCT6694_CAN_EVENT_RX_EVT	BIT(3)
+#define NCT6694_CAN_EVENT_REC		BIT(4)
+#define NCT6694_CAN_EVENT_TEC		BIT(5)
+#define NCT6694_CAN_EVT_TX_FIFO_EMPTY	BIT(7)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_LOST	BIT(5)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_HALF_FULL	BIT(6)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_IN	BIT(7)
+
+/* Command 10h */
+#define NCT6694_CAN_CMD10_OFFSET(buf_cnt)	\
+	(((buf_cnt) & 0xFF) << 8 | 0x10)
+#define NCT6694_CAN_TAG_CAN0		0xC0
+#define NCT6694_CAN_TAG_CAN1		0xC1
+#define NCT6694_CAN_FLAG_EFF		BIT(0)
+#define NCT6694_CAN_FLAG_RTR		BIT(1)
+#define NCT6694_CAN_FLAG_FD		BIT(2)
+#define NCT6694_CAN_FLAG_BRS		BIT(3)
+#define NCT6694_CAN_FLAG_ERR		BIT(4)
+
+/* Command 11h */
+#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt)			\
+	({ typeof(buf_cnt) buf_cnt_ = (buf_cnt);		\
+	   idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) :	\
+		 ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })
+
+#define NCT6694_CAN_RX_QUOTA		64
+
+enum nct6694_event_err {
+	NCT6694_CAN_EVT_NO_ERROR,
+	NCT6694_CAN_EVT_CRC_ERROR,
+	NCT6694_CAN_EVT_STUFF_ERROR,
+	NCT6694_CAN_EVT_ACK_ERROR,
+	NCT6694_CAN_EVT_FORM_ERROR,
+	NCT6694_CAN_EVT_BIT_ERROR,
+	NCT6694_CAN_EVT_TIMEOUT_ERROR,
+	NCT6694_CAN_EVT_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+	NCT6694_CAN_EVT_ERROR_ACTIVE,
+	NCT6694_CAN_EVT_ERROR_PASSIVE,
+	NCT6694_CAN_EVT_BUS_OFF,
+	NCT6694_CAN_EVT_WARNING,
+};
+
+struct __packed nct6694_can_setting {
+	__le32 nbr;
+	__le32 dbr;
+	u8 active;
+	u8 reserved[3];
+	__le16 ctrl1;
+	__le16 ctrl2;
+	__le32 nbtp;
+	__le32 dbtp;
+};
+
+struct __packed nct6694_can_information {
+	u8 tx_fifo_cnt;
+	u8 rx_fifo_cnt;
+	__le16 reserved;
+	__le32 can_clk;
+};
+
+struct __packed nct6694_can_event {
+	u8 err1;
+	u8 status1;
+	u8 tx_evt1;
+	u8 rx_evt1;
+	u8 rec1;
+	u8 tec1;
+	u8 reserved1[2];
+	u8 err2;
+	u8 status2;
+	u8 tx_evt2;
+	u8 rx_evt2;
+	u8 rec2;
+	u8 tec2;
+	u8 reserved2[2];
+};
+
+struct __packed nct6694_can_xmit {
+	u8 tag;
+	u8 flag;
+	u8 reserved;
+	u8 dlc;
+	__le32 id;
+	u8 data[64];
+	u8 msg_buf[72];
+};
+
+struct nct6694_can_priv {
+	struct can_priv can;	/* must be the first member */
+	struct net_device *ndev;
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	struct sk_buff *tx_skb;
+	struct workqueue_struct *wq;
+	struct work_struct tx_work;
+	unsigned char *tx_buf;
+	unsigned char *rx_buf;
+	unsigned char can_idx;
+	bool tx_busy;
+};
+
+static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
+	.name = DRVNAME,
+	.tseg1_min = 2,
+	.tseg1_max = 256,
+	.tseg2_min = 2,
+	.tseg2_max = 128,
+	.sjw_max = 128,
+	.brp_min = 1,
+	.brp_max = 511,
+	.brp_inc = 1,
+};
+
+static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
+	.name = DRVNAME,
+	.tseg1_min = 1,
+	.tseg1_max = 32,
+	.tseg2_min = 1,
+	.tseg2_max = 16,
+	.sjw_max = 16,
+	.brp_min = 1,
+	.brp_max = 31,
+	.brp_inc = 1,
+};
+
+static void nct6694_can_clean(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	if (priv->tx_skb || priv->tx_busy)
+		ndev->stats.tx_errors++;
+	dev_kfree_skb(priv->tx_skb);
+	if (priv->tx_busy)
+		can_free_echo_skb(priv->ndev, 0, NULL);
+	priv->tx_skb = NULL;
+	priv->tx_busy = false;
+}
+
+static int nct6694_can_get_berr_counter(const struct net_device *ndev,
+					struct can_berr_counter *bec)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
+	u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
+	int ret;
+
+	guard(mutex)(&priv->lock);
+
+	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+			       NCT6694_CAN_CMD2_OFFSET(priv->can_idx, mask),
+			       sizeof(struct nct6694_can_event),
+			       evt);
+	if (ret < 0)
+		return ret;
+
+	bec->rxerr = priv->can_idx ? evt->rec2 : evt->rec1;
+	bec->txerr = priv->can_idx ? evt->tec2 : evt->tec1;
+
+	return 0;
+}
+
+static int nct6694_can_handle_lost_msg(struct net_device *ndev)
+{
+	struct net_device_stats *stats = &ndev->stats;
+	struct sk_buff *skb;
+	struct can_frame *frame;
+
+	netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+	stats->rx_errors++;
+	stats->rx_over_errors++;
+
+	skb = alloc_can_err_skb(ndev, &frame);
+	if (unlikely(!skb))
+		return 0;
+
+	frame->can_id |= CAN_ERR_CRTL;
+	frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+	netif_receive_skb(skb);
+
+	return 1;
+}
+
+static void nct6694_can_read_fifo(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->rx_buf;
+	struct net_device_stats *stats = &ndev->stats;
+	struct canfd_frame *cf;
+	struct sk_buff *skb;
+	int can_idx = priv->can_idx;
+	u32 id;
+	int ret;
+	u8 fd_format = 0;
+
+	guard(mutex)(&priv->lock);
+
+	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+			       NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
+			       sizeof(struct nct6694_can_xmit), xmit);
+	if (ret < 0)
+		return;
+
+	/* Check type of frame and create skb */
+	fd_format = xmit->flag & NCT6694_CAN_FLAG_FD;
+	if (fd_format)
+		skb = alloc_canfd_skb(ndev, &cf);
+	else
+		skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+	if (!skb) {
+		stats->rx_dropped++;
+		return;
+	}
+
+	cf->len = xmit->dlc;
+
+	/* Get ID and set flag by its type(Standard ID format or Ext ID format) */
+	id = le32_to_cpu(xmit->id);
+	if (xmit->flag & NCT6694_CAN_FLAG_EFF) {
+		/*
+		 * In case the Extended ID frame is received, the standard
+		 * and extended part of the ID are swapped in the register,
+		 * so swap them back to obtain the correct ID.
+		 */
+		id |= CAN_EFF_FLAG;
+	}
+
+	cf->can_id = id;
+
+	/* Set ESI flag */
+	if (xmit->flag & NCT6694_CAN_FLAG_ERR) {
+		cf->flags |= CANFD_ESI;
+		netdev_dbg(ndev, "ESI Error\n");
+	}
+
+	/* Set RTR and BRS */
+	if (!fd_format && (xmit->flag & NCT6694_CAN_FLAG_RTR)) {
+		cf->can_id |= CAN_RTR_FLAG;
+	} else {
+		if (xmit->flag & NCT6694_CAN_FLAG_BRS)
+			cf->flags |= CANFD_BRS;
+
+		memcpy(cf->data, xmit->data, cf->len);
+
+		stats->rx_bytes += cf->len;
+	}
+
+	stats->rx_packets++;
+
+	netif_receive_skb(skb);
+}
+
+static int nct6694_can_do_rx_poll(struct net_device *ndev, int quota)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
+	int can_idx = priv->can_idx;
+	u32 pkts = 0;
+	u8 mask_rx = NCT6694_CAN_EVENT_RX_EVT;
+	u8 rx_evt;
+
+	for (;;) {
+		scoped_guard(mutex, &priv->lock) {
+			nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+					 NCT6694_CAN_CMD2_OFFSET(can_idx, mask_rx),
+					 sizeof(struct nct6694_can_event), evt);
+
+			rx_evt = can_idx ? evt->rx_evt2 : evt->rx_evt1;
+		}
+
+		if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST)
+			nct6694_can_handle_lost_msg(ndev);
+
+		/* No data */
+		if ((rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) == 0)
+			break;
+
+		if (quota <= 0)
+			break;
+
+		nct6694_can_read_fifo(ndev);
+		quota--;
+		pkts++;
+	}
+
+	return pkts;
+}
+
+static int nct6694_can_handle_lec_err(struct net_device *ndev, u8 bus_err)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
+		return 0;
+
+	priv->can.can_stats.bus_error++;
+	stats->rx_errors++;
+
+	/* Propagate the error condition to the CAN stack. */
+	skb = alloc_can_err_skb(ndev, &cf);
+
+	if (unlikely(!skb))
+		return 0;
+
+	/* Read the error counter register and check for new errors. */
+	cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+	switch (bus_err) {
+	case NCT6694_CAN_EVT_CRC_ERROR:
+		cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
+		break;
+
+	case NCT6694_CAN_EVT_STUFF_ERROR:
+		cf->data[2] |= CAN_ERR_PROT_STUFF;
+		break;
+
+	case NCT6694_CAN_EVT_ACK_ERROR:
+		cf->data[3] = CAN_ERR_PROT_LOC_ACK;
+		break;
+
+	case NCT6694_CAN_EVT_FORM_ERROR:
+		cf->data[2] |= CAN_ERR_PROT_FORM;
+		break;
+
+	case NCT6694_CAN_EVT_BIT_ERROR:
+		cf->data[2] |= CAN_ERR_PROT_BIT |
+			       CAN_ERR_PROT_BIT0 |
+			       CAN_ERR_PROT_BIT1;
+		break;
+
+	case NCT6694_CAN_EVT_TIMEOUT_ERROR:
+		cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+		break;
+
+	case NCT6694_CAN_EVT_UNKNOWN_ERROR:
+		cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+		/*
+		 * It means 'unspecified'(the value is '0').
+		 * But it is not sure if it's ok to send an error package
+		 * without specific error bit.
+		 */
+		break;
+
+	default:
+		break;
+	}
+
+	/* Reset the error counter, ack the IRQ and re-enable the counter. */
+	stats->rx_packets++;
+	stats->rx_bytes += cf->can_dlc;
+	netif_receive_skb(skb);
+
+	return 1;
+}
+
+static int nct6694_can_handle_state_change(struct net_device *ndev,
+					   enum can_state new_state)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+	struct can_berr_counter bec;
+
+	switch (new_state) {
+	case CAN_STATE_ERROR_ACTIVE:
+		priv->can.can_stats.error_warning++;
+		priv->can.state = CAN_STATE_ERROR_ACTIVE;
+		break;
+	case CAN_STATE_ERROR_WARNING:
+		priv->can.can_stats.error_warning++;
+		priv->can.state = CAN_STATE_ERROR_WARNING;
+		break;
+	case CAN_STATE_ERROR_PASSIVE:
+		priv->can.can_stats.error_passive++;
+		priv->can.state = CAN_STATE_ERROR_PASSIVE;
+		break;
+	case CAN_STATE_BUS_OFF:
+		priv->can.state = CAN_STATE_BUS_OFF;
+		priv->can.can_stats.bus_off++;
+		can_bus_off(ndev);
+		break;
+	default:
+		break;
+	}
+
+	/* propagate the error condition to the CAN stack */
+	skb = alloc_can_err_skb(ndev, &cf);
+	if (unlikely(!skb))
+		return 0;
+
+	nct6694_can_get_berr_counter(ndev, &bec);
+
+	switch (new_state) {
+	case CAN_STATE_ERROR_WARNING:
+		/* error warning state */
+		cf->can_id |= CAN_ERR_CRTL;
+		cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
+							CAN_ERR_CRTL_RX_WARNING;
+		cf->data[6] = bec.txerr;
+		cf->data[7] = bec.rxerr;
+		break;
+	case CAN_STATE_ERROR_PASSIVE:
+		/* error passive state */
+		cf->can_id |= CAN_ERR_CRTL;
+		cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+		if (bec.txerr > 127)
+			cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+		cf->data[6] = bec.txerr;
+		cf->data[7] = bec.rxerr;
+		break;
+	case CAN_STATE_BUS_OFF:
+		/* bus-off state */
+		cf->can_id |= CAN_ERR_BUSOFF;
+		break;
+	default:
+		break;
+	}
+
+	stats->rx_packets++;
+	stats->rx_bytes += cf->can_dlc;
+	netif_receive_skb(skb);
+
+	return 1;
+}
+
+static int nct6694_can_handle_state_errors(struct net_device *ndev,
+					   unsigned char can_status)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	int work_done = 0;
+
+	if (can_status == NCT6694_CAN_EVT_ERROR_ACTIVE &&
+	    priv->can.state != CAN_STATE_ERROR_ACTIVE) {
+		netdev_dbg(ndev, "Error, entered active state\n");
+		work_done += nct6694_can_handle_state_change(ndev,
+							     CAN_STATE_ERROR_ACTIVE);
+	}
+
+	if (can_status == NCT6694_CAN_EVT_WARNING &&
+	    priv->can.state != CAN_STATE_ERROR_WARNING) {
+		netdev_dbg(ndev, "Error, entered warning state\n");
+		work_done += nct6694_can_handle_state_change(ndev,
+							     CAN_STATE_ERROR_WARNING);
+	}
+
+	if (can_status == NCT6694_CAN_EVT_ERROR_PASSIVE &&
+	    priv->can.state != CAN_STATE_ERROR_PASSIVE) {
+		netdev_dbg(ndev, "Error, entered passive state\n");
+		work_done += nct6694_can_handle_state_change(ndev,
+							     CAN_STATE_ERROR_PASSIVE);
+	}
+
+	if (can_status == NCT6694_CAN_EVT_BUS_OFF &&
+	    priv->can.state != CAN_STATE_BUS_OFF) {
+		netdev_dbg(ndev, "Error, entered bus-off state\n");
+		work_done += nct6694_can_handle_state_change(ndev,
+							     CAN_STATE_BUS_OFF);
+	}
+
+	return work_done;
+}
+
+static int nct6694_can_poll(struct net_device *ndev, int quota)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
+	int can_idx = priv->can_idx;
+	int work_done = 0, ret;
+	u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
+	u8 bus_err, can_status;
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+				       NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
+				       sizeof(struct nct6694_can_event), evt);
+		if (ret < 0)
+			return IRQ_NONE;
+
+		if (can_idx) {
+			bus_err = evt->err2;
+			can_status = evt->status2;
+		} else {
+			bus_err = evt->err1;
+			can_status = evt->status1;
+		}
+	}
+
+	/* Handle bus state changes */
+	work_done += nct6694_can_handle_state_errors(ndev, can_status);
+
+	/* Handle lec errors on the bus */
+	if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+		work_done += nct6694_can_handle_lec_err(ndev, bus_err);
+
+	/* Handle RX events */
+	work_done += nct6694_can_do_rx_poll(ndev, quota - work_done);
+	return work_done;
+}
+
+static void nct6694_can_tx_irq(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+
+	guard(mutex)(&priv->lock);
+	stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
+	stats->tx_packets++;
+	priv->tx_busy = false;
+	netif_wake_queue(ndev);
+}
+
+static irqreturn_t nct6694_can_irq(int irq, void *data)
+{
+	struct net_device *ndev = data;
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
+	int can_idx = priv->can_idx;
+	int ret;
+	u8 mask_sts = NCT6694_CAN_EVENT_TX_EVT;
+	u8 tx_evt;
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+				       NCT6694_CAN_CMD2_OFFSET(can_idx, mask_sts),
+				       sizeof(struct nct6694_can_event), evt);
+		if (ret < 0)
+			return IRQ_NONE;
+
+		tx_evt = can_idx ? evt->tx_evt2 : evt->tx_evt1;
+	}
+
+	if (tx_evt) {
+		nct6694_can_tx_irq(ndev);
+	} else {
+		ret = nct6694_can_poll(ndev, NCT6694_CAN_RX_QUOTA);
+		if (!ret)
+			return IRQ_NONE;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_can_start(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_setting *setting = (struct nct6694_can_setting *)priv->tx_buf;
+	const struct can_bittiming *n_bt = &priv->can.bittiming;
+	const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+	int ret;
+
+	guard(mutex)(&priv->lock);
+
+	memset(priv->tx_buf, 0, sizeof(struct nct6694_can_setting));
+	setting->nbr = cpu_to_le32(n_bt->bitrate);
+	setting->dbr = cpu_to_le32(d_bt->bitrate);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_MON);
+
+	if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+	    priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_NISO);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_LBCK);
+
+	ret = nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
+				NCT6694_CAN_CMD0_OFFSET(priv->can_idx),
+				sizeof(struct nct6694_can_setting), setting);
+	if (ret < 0)
+		return ret;
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	return 0;
+}
+
+static int nct6694_can_stop(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+	free_irq(ndev->irq, ndev);
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+	nct6694_can_clean(ndev);
+	priv->can.state = CAN_STATE_STOPPED;
+	close_candev(ndev);
+
+	return 0;
+}
+
+static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+	switch (mode) {
+	case CAN_MODE_START:
+		nct6694_can_clean(ndev);
+		nct6694_can_start(ndev);
+		netif_wake_queue(ndev);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int nct6694_can_open(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	int ret;
+
+	ret = open_candev(ndev);
+	if (ret)
+		return ret;
+
+	ret = request_threaded_irq(ndev->irq, NULL,
+				   nct6694_can_irq, IRQF_ONESHOT,
+				   "nct6694_can", 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;
+	}
+
+	priv->tx_skb = NULL;
+	priv->tx_busy = false;
+
+	ret = nct6694_can_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:
+	close_candev(ndev);
+	return ret;
+}
+
+static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
+					  struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	if (priv->tx_skb || priv->tx_busy) {
+		netdev_err(ndev, "hard_xmit called while tx busy\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	if (can_dev_dropped_skb(ndev, skb))
+		return NETDEV_TX_OK;
+
+	netif_stop_queue(ndev);
+	priv->tx_skb = skb;
+	queue_work(priv->wq, &priv->tx_work);
+
+	return NETDEV_TX_OK;
+}
+
+static void nct6694_can_tx(struct net_device *ndev, struct canfd_frame *cf)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->tx_buf;
+	u32 txid = 0;
+
+	memset(xmit, 0, sizeof(struct nct6694_can_xmit));
+
+	if (priv->can_idx == 0)
+		xmit->tag = NCT6694_CAN_TAG_CAN0;
+	else
+		xmit->tag = NCT6694_CAN_TAG_CAN1;
+
+	if (cf->can_id & CAN_EFF_FLAG) {
+		txid = cf->can_id & CAN_EFF_MASK;
+		/*
+		 * In case the Extended ID frame is transmitted, the
+		 * standard and extended part of the ID are swapped
+		 * in the register, so swap them back to send the
+		 * correct ID.
+		 */
+		xmit->flag |= NCT6694_CAN_FLAG_EFF;
+	} else {
+		txid = cf->can_id & CAN_SFF_MASK;
+	}
+
+	xmit->id = cpu_to_le32(txid);
+	xmit->dlc = cf->len;
+
+	if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+	    can_is_canfd_skb(priv->tx_skb)) {
+		xmit->flag |= NCT6694_CAN_FLAG_FD;
+		if (cf->flags & CANFD_BRS)
+			xmit->flag |= NCT6694_CAN_FLAG_BRS;
+	}
+
+	if (cf->can_id & CAN_RTR_FLAG)
+		xmit->flag |= NCT6694_CAN_FLAG_RTR;
+
+	memcpy(xmit->data, cf->data, cf->len);
+
+	nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
+			  NCT6694_CAN_CMD10_OFFSET(1),
+			  sizeof(struct nct6694_can_xmit),
+			  xmit);
+}
+
+static void nct6694_can_tx_work(struct work_struct *work)
+{
+	struct nct6694_can_priv *priv = container_of(work,
+						     struct nct6694_can_priv,
+						     tx_work);
+	struct net_device *ndev = priv->ndev;
+	struct canfd_frame *cf;
+
+	guard(mutex)(&priv->lock);
+
+	if (priv->tx_skb) {
+		if (priv->can.state == CAN_STATE_BUS_OFF) {
+			nct6694_can_clean(ndev);
+		} else {
+			cf = (struct canfd_frame *)priv->tx_skb->data;
+			nct6694_can_tx(ndev, cf);
+			priv->tx_busy = true;
+			can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
+			priv->tx_skb = NULL;
+		}
+	}
+}
+
+static const struct net_device_ops nct6694_can_netdev_ops = {
+	.ndo_open = nct6694_can_open,
+	.ndo_stop = nct6694_can_stop,
+	.ndo_start_xmit = nct6694_can_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static const struct ethtool_ops nct6694_can_ethtool_ops = {
+	.get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
+{
+	struct nct6694_can_information *info = (struct nct6694_can_information *)priv->rx_buf;
+	int ret;
+
+	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+			       NCT6694_CAN_CMD1_OFFSET,
+			       sizeof(struct nct6694_can_information),
+			       info);
+	if (ret)
+		return ret;
+
+	return le32_to_cpu(info->can_clk);
+}
+
+static int nct6694_can_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_can_priv *priv;
+	struct net_device *ndev;
+	int ret, irq, can_clk;
+
+	irq = irq_create_mapping(nct6694->domain,
+				 NCT6694_IRQ_CAN1 + cell->id);
+	if (!irq)
+		return -EINVAL;
+
+	ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
+	if (!ndev)
+		return -ENOMEM;
+
+	ndev->irq = irq;
+	ndev->flags |= IFF_ECHO;
+	ndev->netdev_ops = &nct6694_can_netdev_ops;
+	ndev->ethtool_ops = &nct6694_can_ethtool_ops;
+
+	priv = netdev_priv(ndev);
+	priv->nct6694 = nct6694;
+	priv->ndev = ndev;
+
+	priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+				    sizeof(unsigned char), GFP_KERNEL);
+	if (!priv->tx_buf) {
+		ret = -ENOMEM;
+		goto free_candev;
+	}
+
+	priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+				    sizeof(unsigned char), GFP_KERNEL);
+	if (!priv->rx_buf) {
+		ret = -ENOMEM;
+		goto free_candev;
+	}
+
+	can_clk = nct6694_can_get_clock(priv);
+	if (can_clk < 0) {
+		ret = -EIO;
+		goto free_candev;
+	}
+
+	mutex_init(&priv->lock);
+	INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
+
+	priv->can_idx = cell->id;
+	priv->can.state = CAN_STATE_STOPPED;
+	priv->can.clock.freq = can_clk;
+	priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
+	priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
+	priv->can.do_set_mode = nct6694_can_set_mode;
+	priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
+
+	priv->can.ctrlmode = CAN_CTRLMODE_FD;
+
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK		|
+				       CAN_CTRLMODE_LISTENONLY		|
+				       CAN_CTRLMODE_FD			|
+				       CAN_CTRLMODE_FD_NON_ISO		|
+				       CAN_CTRLMODE_BERR_REPORTING;
+
+	platform_set_drvdata(pdev, priv);
+	SET_NETDEV_DEV(priv->ndev, &pdev->dev);
+
+	ret = register_candev(priv->ndev);
+	if (ret)
+		goto free_candev;
+
+	return 0;
+
+free_candev:
+	free_candev(ndev);
+	return dev_err_probe(&pdev->dev, ret, "Probe failed\n");
+}
+
+static void nct6694_can_remove(struct platform_device *pdev)
+{
+	struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
+
+	cancel_work_sync(&priv->tx_work);
+	unregister_candev(priv->ndev);
+	free_candev(priv->ndev);
+}
+
+static struct platform_driver nct6694_can_driver = {
+	.driver = {
+		.name	= DRVNAME,
+	},
+	.probe		= nct6694_can_probe,
+	.remove		= nct6694_can_remove,
+};
+
+module_platform_driver(nct6694_can_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-can");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
                   ` (3 preceding siblings ...)
  2024-12-10 10:45 ` [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-10 15:22   ` Guenter Roeck
  2024-12-10 10:45 ` [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
  2024-12-10 10:45 ` [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
  6 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

This driver supports Watchdog timer functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                    |   1 +
 drivers/watchdog/Kconfig       |  11 ++
 drivers/watchdog/Makefile      |   1 +
 drivers/watchdog/nct6694_wdt.c | 277 +++++++++++++++++++++++++++++++++
 4 files changed, 290 insertions(+)
 create mode 100644 drivers/watchdog/nct6694_wdt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index eb5d46825e71..496fe7d5a23f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16549,6 +16549,7 @@ F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/nct6694_canfd.c
+F:	drivers/watchdog/nct6694_wdt.c
 F:	include/linux/mfd/nct6694.h
 
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2333476a42c0..fbfa5a5f7acf 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -739,6 +739,17 @@ config MAX77620_WATCHDOG
 	  MAX77620 chips. To compile this driver as a module,
 	  choose M here: the module will be called max77620_wdt.
 
+config NCT6694_WATCHDOG
+	tristate "Nuvoton NCT6694 watchdog support"
+	depends on MFD_NCT6694
+	select WATCHDOG_CORE
+	help
+	  Say Y here to support Nuvoton NCT6694 watchdog timer
+	  functionality.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6694_wdt.
+
 config IMX2_WDT
 	tristate "IMX2+ Watchdog"
 	depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 386d88d89fe5..8355893b4435 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -232,6 +232,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
 obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
 obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
+obj-$(CONFIG_NCT6694_WATCHDOG)	+= nct6694_wdt.o
 obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
 obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
new file mode 100644
index 000000000000..52b785e2fbd6
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define DRVNAME "nct6694-wdt"
+
+#define NCT6694_DEFAULT_TIMEOUT		10
+#define NCT6694_DEFAULT_PRETIMEOUT	0
+
+/* Host interface */
+#define NCT6694_WDT_MOD			0x07
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_WDT_CMD0_LEN		0x0F
+#define NCT6694_WDT_CMD0_OFFSET(idx)	(idx ? 0x0100 : 0x0000)	/* OFFSET = SEL|CMD */
+
+/* Command 01h */
+#define NCT6694_WDT_CMD1_LEN		0x08
+#define NCT6694_WDT_CMD1_OFFSET(idx)	(idx ? 0x0101 : 0x0001)	/* OFFSET = SEL|CMD */
+
+static unsigned int timeout = NCT6694_DEFAULT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int pretimeout = NCT6694_DEFAULT_PRETIMEOUT;
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+			   __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum {
+	NCT6694_ACTION_NONE = 0,
+	NCT6694_ACTION_SIRQ,
+	NCT6694_ACTION_GPO,
+};
+
+struct __packed nct6694_wdt_cmd0 {
+	__le32 pretimeout;
+	__le32 timeout;
+	u8 owner;
+	u8 scratch;
+	u8 control;
+	u8 status;
+	__le32 countdown;
+};
+
+struct __packed nct6694_wdt_cmd1 {
+	u32 wdt_cmd;
+	u32 reserved;
+};
+
+struct nct6694_wdt_data {
+	struct watchdog_device wdev;
+	struct device *dev;
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	unsigned char *xmit_buf;
+	unsigned int wdev_idx;
+};
+
+static int nct6694_wdt_setting(struct watchdog_device *wdev,
+			       u32 timeout_val, u8 timeout_act,
+			       u32 pretimeout_val, u8 pretimeout_act)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
+	struct nct6694 *nct6694 = data->nct6694;
+	unsigned int timeout_fmt, pretimeout_fmt;
+
+	guard(mutex)(&data->lock);
+
+	if (pretimeout_val == 0)
+		pretimeout_act = NCT6694_ACTION_NONE;
+
+	timeout_fmt = (timeout_val * 1000) | (timeout_act << 24);
+	pretimeout_fmt = (pretimeout_val * 1000) | (pretimeout_act << 24);
+
+	memset(buf, 0, NCT6694_WDT_CMD0_LEN);
+	buf->timeout = cpu_to_le32(timeout_fmt);
+	buf->pretimeout = cpu_to_le32(pretimeout_fmt);
+
+	return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+				 NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
+				 NCT6694_WDT_CMD0_LEN, buf);
+}
+
+static int nct6694_wdt_start(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+				  wdev->pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
+		data->wdev_idx, wdev->timeout, wdev->pretimeout);
+
+	return ret;
+}
+
+static int nct6694_wdt_stop(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
+	struct nct6694 *nct6694 = data->nct6694;
+
+	guard(mutex)(&data->lock);
+
+	memcpy(buf, "WDTC", 4);
+	buf->reserved = 0;
+
+	return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+				 NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
+				 NCT6694_WDT_CMD1_LEN, buf);
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
+	struct nct6694 *nct6694 = data->nct6694;
+
+	guard(mutex)(&data->lock);
+	memcpy(buf, "WDTS", 4);
+	buf->reserved = 0;
+
+	return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+				 NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
+				 NCT6694_WDT_CMD1_LEN, buf);
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+				   unsigned int timeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, timeout, NCT6694_ACTION_GPO,
+				  wdev->pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->timeout = timeout;
+
+	return 0;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+				      unsigned int pretimeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+				  pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->pretimeout = pretimeout;
+
+	return 0;
+}
+
+static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
+	struct nct6694 *nct6694 = data->nct6694;
+	unsigned int timeleft_ms;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(nct6694, NCT6694_WDT_MOD,
+			       NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
+			       NCT6694_WDT_CMD0_LEN, buf);
+	if (ret)
+		return 0;
+
+	timeleft_ms = le32_to_cpu(buf->countdown);
+
+	return timeleft_ms / 1000;
+}
+
+static const struct watchdog_info nct6694_wdt_info = {
+	.options = WDIOF_SETTIMEOUT	|
+		   WDIOF_KEEPALIVEPING	|
+		   WDIOF_MAGICCLOSE	|
+		   WDIOF_PRETIMEOUT,
+	.identity = DRVNAME,
+};
+
+static const struct watchdog_ops nct6694_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = nct6694_wdt_start,
+	.stop = nct6694_wdt_stop,
+	.set_timeout = nct6694_wdt_set_timeout,
+	.set_pretimeout = nct6694_wdt_set_pretimeout,
+	.get_timeleft = nct6694_wdt_get_time,
+	.ping = nct6694_wdt_ping,
+};
+
+static int nct6694_wdt_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct device *dev = &pdev->dev;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_wdt_data *data;
+	struct watchdog_device *wdev;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->xmit_buf = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
+				      sizeof(unsigned char), GFP_KERNEL);
+	if (!data->xmit_buf)
+		return -ENOMEM;
+
+	data->dev = dev;
+	data->nct6694 = nct6694;
+	data->wdev_idx = cell->id;
+
+	wdev = &data->wdev;
+	wdev->info = &nct6694_wdt_info;
+	wdev->ops = &nct6694_wdt_ops;
+	wdev->timeout = timeout;
+	wdev->pretimeout = pretimeout;
+	if (timeout < pretimeout) {
+		dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+		wdev->pretimeout = 0;
+	}
+
+	wdev->min_timeout = 1;
+	wdev->max_timeout = 255;
+
+	mutex_init(&data->lock);
+
+	platform_set_drvdata(pdev, data);
+
+	/* Register watchdog timer device to WDT framework */
+	watchdog_set_drvdata(&data->wdev, data);
+	watchdog_init_timeout(&data->wdev, timeout, dev);
+	watchdog_set_nowayout(&data->wdev, nowayout);
+	watchdog_stop_on_reboot(&data->wdev);
+
+	return devm_watchdog_register_device(dev, &data->wdev);
+}
+
+static struct platform_driver nct6694_wdt_driver = {
+	.driver = {
+		.name	= DRVNAME,
+	},
+	.probe		= nct6694_wdt_probe,
+};
+
+module_platform_driver(nct6694_wdt_driver);
+
+MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-wdt");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
                   ` (4 preceding siblings ...)
  2024-12-10 10:45 ` [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-10 15:58   ` Guenter Roeck
  2024-12-12 16:10   ` Vincent Mailhol
  2024-12-10 10:45 ` [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
  6 siblings, 2 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

This driver supports Hardware monitor functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS                   |   1 +
 drivers/hwmon/Kconfig         |  10 +
 drivers/hwmon/Makefile        |   1 +
 drivers/hwmon/nct6694-hwmon.c | 768 ++++++++++++++++++++++++++++++++++
 4 files changed, 780 insertions(+)
 create mode 100644 drivers/hwmon/nct6694-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 496fe7d5a23f..d6414eea0463 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16546,6 +16546,7 @@ M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
+F:	drivers/hwmon/nct6694-hwmon.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/nct6694_canfd.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index dd376602f3f1..df40986424bd 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1636,6 +1636,16 @@ config SENSORS_NCT6683
 	  This driver can also be built as a module. If so, the module
 	  will be called nct6683.
 
+config SENSORS_NCT6694
+	tristate "Nuvoton NCT6694 Hardware Monitor support"
+	depends on MFD_NCT6694
+	help
+	  Say Y here to support Nuvoton NCT6694 hardware monitoring
+	  functionality.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6694-hwmon.
+
 config SENSORS_NCT6775_CORE
 	tristate
 	select REGMAP
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b827b92f2a78..27a43e67cdb7 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -168,6 +168,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
+obj-$(CONFIG_SENSORS_NCT6694)	+= nct6694-hwmon.o
 obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
 nct6775-objs			:= nct6775-platform.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
new file mode 100644
index 000000000000..b2320d64090b
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,768 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* Host interface */
+#define NCT6694_RPT_MOD			0xFF
+#define NCT6694_HWMON_MOD		0x00
+#define NCT6694_PWM_MOD			0x01
+
+/* Report Channel */
+#define NCT6694_VIN_IDX(x)		(0x00 + (x))
+#define NCT6694_TIN_IDX(x)			\
+	({ typeof(x) (_x) = (x);		\
+	 ((_x) < 10) ? (0x10 + ((_x) * 2)) :	\
+	 (0x30 + (((_x) - 10) * 2)); })
+#define NCT6694_FIN_IDX(x)		(0x50 + ((x) * 2))
+#define NCT6694_PWM_IDX(x)		(0x70 + (x))
+#define NCT6694_VIN_STS(x)		(0x68 + (x))
+#define NCT6694_TIN_STS(x)		(0x6A + (x))
+#define NCT6694_FIN_STS(x)		(0x6E + (x))
+
+/* Message Channel*/
+/* HWMON Command */
+/* Command 00h */
+#define NCT6694_HWMON_CMD0_LEN		0x40
+#define NCT6694_HWMON_CMD0_OFFSET	0x0000	/* OFFSET = SEL|CMD */
+#define NCT6694_VIN_EN(x)		(0x00 + (x))
+#define NCT6694_TIN_EN(x)		(0x02 + (x))
+#define NCT6694_FIN_EN(x)		(0x04 + (x))
+#define NCT6694_PWM_EN(x)		(0x06 + (x))
+#define NCT6694_PWM_FREQ_IDX(x)		(0x30 + (x))
+/* Command 02h */
+#define NCT6694_HWMON_CMD2_LEN		0x90
+#define NCT6694_HWMON_CMD2_OFFSET	0x0002	/* OFFSET = SEL|CMD */
+#define NCT6694_SMI_CTRL_IDX		0x00
+#define NCT6694_VIN_HL(x)		(0x10 + ((x) * 2))
+#define NCT6694_VIN_LL(x)		(0x11 + ((x) * 2))
+#define NCT6694_TIN_HYST(x)		(0x30 + ((x) * 2))
+#define NCT6694_TIN_HL(x)		(0x31 + ((x) * 2))
+#define NCT6694_FIN_HL(x)		(0x70 + ((x) * 2))
+#define NCT6694_FIN_LL(x)		(0x71 + ((x) * 2))
+/* PWM Command */
+#define NCT6694_PWM_CMD1_LEN		0x18
+#define NCT6694_PWM_CMD1_OFFSET		0x0001
+#define NCT6694_MAL_VAL(x)		(0x02 + (x))
+
+#define NCT6694_FREQ_FROM_REG(reg)	((reg) * 25000 / 255)
+#define NCT6694_FREQ_TO_REG(val)	\
+	(DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
+
+#define NCT6694_LSB_REG_MASK		GENMASK(7, 5)
+#define NCT6694_TIN_HYST_MASK		GENMASK(7, 5)
+
+static inline long in_from_reg(u8 reg)
+{
+	return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+	if (val <= 0)
+		return 0;
+	return val / 16;
+}
+
+static inline long temp_from_reg(u8 reg)
+{
+	return reg * 1000;
+}
+
+static inline u8 temp_to_reg(long val)
+{
+	return val / 1000;
+}
+
+struct nct6694_hwmon_data {
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	unsigned char *xmit_buf;
+	unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
+};
+
+#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE |	\
+				 HWMON_I_MAX | HWMON_I_MIN |		\
+				 HWMON_I_ALARM)
+#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE |	\
+				   HWMON_T_MAX | HWMON_T_MAX_HYST |	\
+				   HWMON_T_MAX_ALARM)
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE |	\
+				  HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE |	\
+				  HWMON_PWM_FREQ)
+static const struct hwmon_channel_info *nct6694_info[] = {
+	HWMON_CHANNEL_INFO(in,
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN0 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN1 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN2 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN3 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN5 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN6 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN7 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN14 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN15 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN16 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VBAT */
+			   NCT6694_HWMON_IN_CONFIG,	/* VSB */
+			   NCT6694_HWMON_IN_CONFIG,	/* AVSB */
+			   NCT6694_HWMON_IN_CONFIG,	/* VCC */
+			   NCT6694_HWMON_IN_CONFIG,	/* VHIF */
+			   NCT6694_HWMON_IN_CONFIG),	/* VTT */
+
+	HWMON_CHANNEL_INFO(temp,
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR14 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR15 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR16 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP0 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP3 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP4 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN0 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN3 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN4 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN5 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN6 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN7 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN8 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN9 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN10 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN11 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN12 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN13 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN14 */
+			   NCT6694_HWMON_TEMP_CONFIG),	/* DTIN15 */
+
+	HWMON_CHANNEL_INFO(fan,
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN0 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN1 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN2 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN3 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN4 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN5 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN6 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN7 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN8 */
+			   NCT6694_HWMON_FAN_CONFIG),	/* FIN9 */
+
+	HWMON_CHANNEL_INFO(pwm,
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM0 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM1 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM2 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM3 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM4 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM5 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM6 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM7 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM8 */
+			   NCT6694_HWMON_PWM_CONFIG),	/* PWM9 */
+	NULL
+};
+
+static int nct6694_in_read(struct device *dev, u32 attr, int channel,
+			   long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	unsigned char vin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
+		*val = !!(vin_en & BIT(channel % 8)) ? 1 : 0;
+
+		return 0;
+	case hwmon_in_input:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_VIN_IDX(channel), 1,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->xmit_buf[0]);
+
+		return 0;
+	case hwmon_in_max:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->xmit_buf[NCT6694_VIN_HL(channel)]);
+
+		return 0;
+	case hwmon_in_min:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->xmit_buf[NCT6694_VIN_LL(channel)]);
+
+		return 0;
+	case hwmon_in_alarm:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_VIN_STS(channel / 8), 1,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
+			     long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	unsigned char temp_en, temp_hyst;
+	int ret, int_part, frac_part;
+	signed char temp_max;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
+		*val = !!(temp_en & BIT(channel % 8)) ? 1 : 0;
+
+		return 0;
+	case hwmon_temp_input:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_TIN_IDX(channel), 2,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		int_part = sign_extend32(data->xmit_buf[0], 7);
+		frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
+		if (int_part < 0)
+			*val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
+		else
+			*val = int_part * 1000 + frac_part * 125;
+
+		return 0;
+	case hwmon_temp_max:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
+
+		return 0;
+	case hwmon_temp_max_hyst:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
+		temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+				      data->xmit_buf[NCT6694_TIN_HYST(channel)]);
+		if (temp_max < 0)
+			*val = temp_from_reg(temp_max + temp_hyst);
+		else
+			*val = temp_from_reg(temp_max - temp_hyst);
+
+		return 0;
+	case hwmon_temp_max_alarm:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_TIN_STS(channel / 8), 1,
+					   data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	unsigned char fanin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		fanin_en = data->hwmon_en[NCT6694_FIN_EN(channel / 8)];
+		*val = !!(fanin_en & BIT(channel % 8)) ? 1 : 0;
+
+		return 0;
+	case hwmon_fan_input:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_FIN_IDX(channel), 2,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = (data->xmit_buf[1] |
+		       (data->xmit_buf[0] << 8)) & 0xFFFF;
+
+		return 0;
+	case hwmon_fan_min:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = (data->xmit_buf[NCT6694_FIN_LL(channel)] |
+			data->xmit_buf[NCT6694_FIN_HL(channel)] << 8) & 0xFFFF;
+
+		return 0;
+	case hwmon_fan_min_alarm:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_FIN_STS(channel / 8),
+				       1, data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	unsigned char pwm_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		pwm_en = data->hwmon_en[NCT6694_PWM_EN(channel / 8)];
+		*val = !!(pwm_en & BIT(channel % 8)) ? 1 : 0;
+
+		return 0;
+	case hwmon_pwm_input:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+				       NCT6694_PWM_IDX(channel),
+				       1, data->xmit_buf);
+		if (ret)
+			return ret;
+
+		*val = data->xmit_buf[0];
+
+		return 0;
+	case hwmon_pwm_freq:
+		*val = NCT6694_FREQ_FROM_REG(data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)]);
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_enable_channel(struct device *dev, u8 reg,
+				  int channel, long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+
+	if (val == 0)
+		data->hwmon_en[reg] &= ~BIT(channel % 8);
+	else if (val == 1)
+		data->hwmon_en[reg] |= BIT(channel % 8);
+	else
+		return -EINVAL;
+
+	return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+				 NCT6694_HWMON_CMD0_OFFSET,
+				 NCT6694_HWMON_CMD0_LEN,
+				 data->hwmon_en);
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+			    long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		return nct6694_enable_channel(dev, NCT6694_VIN_EN(channel / 8),
+					      channel, val);
+	case hwmon_in_max:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->xmit_buf[NCT6694_VIN_HL(channel)] = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD2_OFFSET,
+					 NCT6694_HWMON_CMD2_LEN,
+					 data->xmit_buf);
+	case hwmon_in_min:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->xmit_buf[NCT6694_VIN_LL(channel)] = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD2_OFFSET,
+					 NCT6694_HWMON_CMD2_LEN,
+					 data->xmit_buf);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
+			      long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	signed char temp_max, temp_hyst;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		return nct6694_enable_channel(dev, NCT6694_TIN_EN(channel / 8),
+					      channel, val);
+	case hwmon_temp_max:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, -127000, 127000);
+		data->xmit_buf[NCT6694_TIN_HL(channel)] = temp_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD2_OFFSET,
+					 NCT6694_HWMON_CMD2_LEN,
+					 data->xmit_buf);
+	case hwmon_temp_max_hyst:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+
+		val = clamp_val(val, -127000, 127000);
+		temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
+		temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
+					     (temp_max - val / 1000);
+		temp_hyst = clamp_val(temp_hyst, 0, 7);
+		data->xmit_buf[NCT6694_TIN_HYST(channel)] =
+		       (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
+		       FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD2_OFFSET,
+					 NCT6694_HWMON_CMD2_LEN,
+					 data->xmit_buf);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		return nct6694_enable_channel(dev, NCT6694_FIN_EN(channel / 8),
+					      channel, val);
+	case hwmon_fan_min:
+		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+				       NCT6694_HWMON_CMD2_OFFSET,
+				       NCT6694_HWMON_CMD2_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 1, 65535);
+		data->xmit_buf[NCT6694_FIN_HL(channel)] = (u8)((val >> 8) & 0xFF);
+		data->xmit_buf[NCT6694_FIN_LL(channel)] = (u8)(val & 0xFF);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD2_OFFSET,
+					 NCT6694_HWMON_CMD2_LEN,
+					 data->xmit_buf);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		return nct6694_enable_channel(dev, NCT6694_PWM_EN(channel / 8),
+					      channel, val);
+	case hwmon_pwm_input:
+		if (val < 0 || val > 255)
+			return -EINVAL;
+
+		ret = nct6694_read_msg(data->nct6694, NCT6694_PWM_MOD,
+				       NCT6694_PWM_CMD1_OFFSET,
+				       NCT6694_PWM_CMD1_LEN,
+				       data->xmit_buf);
+		if (ret)
+			return ret;
+
+		data->xmit_buf[NCT6694_MAL_VAL(channel)] = val;
+
+		return nct6694_write_msg(data->nct6694, NCT6694_PWM_MOD,
+					 NCT6694_PWM_CMD1_OFFSET,
+					 NCT6694_PWM_CMD1_LEN,
+					 data->xmit_buf);
+	case hwmon_pwm_freq:
+		data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)] = NCT6694_FREQ_TO_REG(val);
+
+		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+					 NCT6694_HWMON_CMD0_OFFSET,
+					 NCT6694_HWMON_CMD0_LEN,
+					 data->hwmon_en);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+			u32 attr, int channel, long *val)
+{
+	switch (type) {
+	case hwmon_in:	/* in mV */
+		return nct6694_in_read(dev, attr, channel, val);
+	case hwmon_temp:/* in mC */
+		return nct6694_temp_read(dev, attr, channel, val);
+	case hwmon_fan:	/* in RPM */
+		return nct6694_fan_read(dev, attr, channel, val);
+	case hwmon_pwm:	/* in value 0~255 */
+		return nct6694_pwm_read(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long val)
+{
+	switch (type) {
+	case hwmon_in:
+		return nct6694_in_write(dev, attr, channel, val);
+	case hwmon_temp:
+		return nct6694_temp_write(dev, attr, channel, val);
+	case hwmon_fan:
+		return nct6694_fan_write(dev, attr, channel, val);
+	case hwmon_pwm:
+		return nct6694_pwm_write(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static umode_t nct6694_is_visible(const void *data,
+				  enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_enable:
+		case hwmon_in_max:
+		case hwmon_in_min:
+			return 0644;
+		case hwmon_in_alarm:
+		case hwmon_in_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_enable:
+		case hwmon_temp_max:
+		case hwmon_temp_max_hyst:
+			return 0644;
+		case hwmon_temp_input:
+		case hwmon_temp_max_alarm:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_enable:
+		case hwmon_fan_min:
+			return 0644;
+		case hwmon_fan_input:
+		case hwmon_fan_min_alarm:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+		case hwmon_pwm_freq:
+		case hwmon_pwm_input:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static const struct hwmon_ops nct6694_hwmon_ops = {
+	.is_visible = nct6694_is_visible,
+	.read = nct6694_read,
+	.write = nct6694_write,
+};
+
+static const struct hwmon_chip_info nct6694_chip_info = {
+	.ops = &nct6694_hwmon_ops,
+	.info = nct6694_info,
+};
+
+static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
+{
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	/*
+	 *  Record each Hardware Monitor Channel enable status
+	 *  and PWM frequency register
+	 */
+	ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+			       NCT6694_HWMON_CMD0_OFFSET,
+			       NCT6694_HWMON_CMD0_LEN,
+			       data->hwmon_en);
+	if (ret)
+		return ret;
+
+	/* Set Fan input Real Time alarm mode */
+	ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+			       NCT6694_HWMON_CMD2_OFFSET,
+			       NCT6694_HWMON_CMD2_LEN,
+			       data->xmit_buf);
+	if (ret)
+		return ret;
+
+	data->xmit_buf[NCT6694_SMI_CTRL_IDX] = 0x02;
+
+	return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+				 NCT6694_HWMON_CMD2_OFFSET,
+				 NCT6694_HWMON_CMD2_LEN,
+				 data->xmit_buf);
+}
+
+static int nct6694_hwmon_probe(struct platform_device *pdev)
+{
+	struct nct6694_hwmon_data *data;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct device *hwmon_dev;
+	int ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+				      sizeof(unsigned char), GFP_KERNEL);
+	if (!data->xmit_buf)
+		return -ENOMEM;
+
+	data->nct6694 = nct6694;
+	mutex_init(&data->lock);
+	platform_set_drvdata(pdev, data);
+
+	ret = nct6694_hwmon_init(data);
+	if (ret)
+		return ret;
+
+	/* Register hwmon device to HWMON framework */
+	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+							 "nct6694", data,
+							 &nct6694_chip_info,
+							 NULL);
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+	.driver = {
+		.name	= "nct6694-hwmon",
+	},
+	.probe		= nct6694_hwmon_probe,
+};
+
+module_platform_driver(nct6694_hwmon_driver);
+
+MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-hwmon");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support
  2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
                   ` (5 preceding siblings ...)
  2024-12-10 10:45 ` [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-12-10 10:45 ` Ming Yu
  2024-12-16 10:41   ` Alexandre Belloni
  6 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-10 10:45 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

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

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
 MAINTAINERS               |   1 +
 drivers/rtc/Kconfig       |  10 ++
 drivers/rtc/Makefile      |   1 +
 drivers/rtc/rtc-nct6694.c | 264 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 276 insertions(+)
 create mode 100644 drivers/rtc/rtc-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d6414eea0463..6d1cfec28076 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16550,6 +16550,7 @@ F:	drivers/hwmon/nct6694-hwmon.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/nct6694_canfd.c
+F:	drivers/rtc/rtc-nct6694.c
 F:	drivers/watchdog/nct6694_wdt.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 66eb1122248b..36829d096194 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -406,6 +406,16 @@ config RTC_DRV_NCT3018Y
 	   This driver can also be built as a module, if so, the module will be
 	   called "rtc-nct3018y".
 
+config RTC_DRV_NCT6694
+	tristate "Nuvoton NCT6694 RTC support"
+	depends on MFD_NCT6694
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB device to RTC.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called rtc-nct6694.
+
 config RTC_DRV_RK808
 	tristate "Rockchip RK805/RK808/RK809/RK817/RK818 RTC"
 	depends on MFD_RK8XX
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index f62340ecc534..64443d26bb5b 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_RTC_DRV_MXC)	+= rtc-mxc.o
 obj-$(CONFIG_RTC_DRV_MXC_V2)	+= rtc-mxc_v2.o
 obj-$(CONFIG_RTC_DRV_GAMECUBE)	+= rtc-gamecube.o
 obj-$(CONFIG_RTC_DRV_NCT3018Y)	+= rtc-nct3018y.o
+obj-$(CONFIG_RTC_DRV_NCT6694)	+= rtc-nct6694.o
 obj-$(CONFIG_RTC_DRV_NTXEC)	+= rtc-ntxec.o
 obj-$(CONFIG_RTC_DRV_OMAP)	+= rtc-omap.o
 obj-$(CONFIG_RTC_DRV_OPAL)	+= rtc-opal.o
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
new file mode 100644
index 000000000000..0dbe2aa0a042
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bcd.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/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/* Host interface */
+#define NCT6694_RTC_MOD		0x08
+
+/* Message Channel */
+/* Command 00h */
+#define NCT6694_RTC_CMD0_LEN	0x07
+#define NCT6694_RTC_CMD0_OFFSET	0x0000	/* OFFSET = SEL|CMD */
+/* Command 01h */
+#define NCT6694_RTC_CMD1_LEN	0x05
+#define NCT6694_RTC_CMD1_OFFSET	0x0001	/* OFFSET = SEL|CMD */
+/* Command 02h */
+#define NCT6694_RTC_CMD2_LEN	0x02
+#define NCT6694_RTC_CMD2_OFFSET	0x0002	/* OFFSET = SEL|CMD */
+
+#define NCT6694_RTC_IRQ_INT_EN		BIT(0)	/* Transmit a USB INT-in when RTC alarm */
+#define NCT6694_RTC_IRQ_GPO_EN		BIT(5)	/* Trigger a GPO Low Pulse when RTC alarm */
+
+#define NCT6694_RTC_IRQ_EN		(NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_EN)
+#define NCT6694_RTC_IRQ_STS		BIT(0)	/* Write 1 clear IRQ status */
+
+struct __packed nct6694_rtc_cmd0 {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 week;
+	u8 day;
+	u8 month;
+	u8 year;
+};
+
+struct __packed nct6694_rtc_cmd1 {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 alarm_en;
+	u8 alarm_pend;
+};
+
+struct __packed nct6694_rtc_cmd2 {
+	u8 irq_en;
+	u8 irq_pend;
+};
+
+struct nct6694_rtc_data {
+	struct nct6694 *nct6694;
+	struct rtc_device *rtc;
+	struct mutex lock;
+	unsigned char *xmit_buf;
+};
+
+static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_cmd0 *buf = (struct nct6694_rtc_cmd0 *)data->xmit_buf;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_RTC_MOD,
+			       NCT6694_RTC_CMD0_OFFSET,
+			       NCT6694_RTC_CMD0_LEN,
+			       buf);
+	if (ret)
+		return ret;
+
+	tm->tm_sec = bcd2bin(buf->sec);		/* tm_sec expect 0 ~ 59 */
+	tm->tm_min = bcd2bin(buf->min);		/* tm_min expect 0 ~ 59 */
+	tm->tm_hour = bcd2bin(buf->hour);	/* tm_hour expect 0 ~ 23 */
+	tm->tm_wday = bcd2bin(buf->week) - 1;	/* tm_wday expect 0 ~ 6 */
+	tm->tm_mday = bcd2bin(buf->day);	/* tm_mday expect 1 ~ 31 */
+	tm->tm_mon = bcd2bin(buf->month) - 1;	/* tm_month expect 0 ~ 11 */
+	tm->tm_year = bcd2bin(buf->year) + 100;	/* tm_year expect since 1900 */
+
+	return ret;
+}
+
+static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_cmd0 *buf = (struct nct6694_rtc_cmd0 *)data->xmit_buf;
+
+	guard(mutex)(&data->lock);
+
+	buf->sec = bin2bcd(tm->tm_sec);
+	buf->min = bin2bcd(tm->tm_min);
+	buf->hour = bin2bcd(tm->tm_hour);
+	buf->week = bin2bcd(tm->tm_wday + 1);
+	buf->day = bin2bcd(tm->tm_mday);
+	buf->month = bin2bcd(tm->tm_mon + 1);
+	buf->year = bin2bcd(tm->tm_year - 100);
+
+	return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+				 NCT6694_RTC_CMD0_OFFSET,
+				 NCT6694_RTC_CMD0_LEN,
+				 buf);
+}
+
+static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_cmd1 *buf = (struct nct6694_rtc_cmd1 *)data->xmit_buf;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, NCT6694_RTC_MOD,
+			       NCT6694_RTC_CMD1_OFFSET,
+			       NCT6694_RTC_CMD1_LEN,
+			       buf);
+	if (ret)
+		return ret;
+
+	alrm->time.tm_sec = bcd2bin(buf->sec);
+	alrm->time.tm_min = bcd2bin(buf->min);
+	alrm->time.tm_hour = bcd2bin(buf->hour);
+	alrm->enabled = buf->alarm_en;
+	alrm->pending = buf->alarm_pend;
+
+	return ret;
+}
+
+static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_cmd1 *buf = (struct nct6694_rtc_cmd1 *)data->xmit_buf;
+
+	guard(mutex)(&data->lock);
+
+	buf->sec = bin2bcd(alrm->time.tm_sec);
+	buf->min = bin2bcd(alrm->time.tm_min);
+	buf->hour = bin2bcd(alrm->time.tm_hour);
+	buf->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0;
+	buf->alarm_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+				 NCT6694_RTC_CMD1_OFFSET,
+				 NCT6694_RTC_CMD1_LEN,
+				 buf);
+}
+
+static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_cmd2 *buf = (struct nct6694_rtc_cmd2 *)data->xmit_buf;
+
+	guard(mutex)(&data->lock);
+
+	if (enabled)
+		buf->irq_en |= NCT6694_RTC_IRQ_EN;
+	else
+		buf->irq_en &= ~NCT6694_RTC_IRQ_EN;
+
+	buf->irq_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+				 NCT6694_RTC_CMD2_OFFSET,
+				 NCT6694_RTC_CMD2_LEN,
+				 buf);
+}
+
+static const struct rtc_class_ops nct6694_rtc_ops = {
+	.read_time = nct6694_rtc_read_time,
+	.set_time = nct6694_rtc_set_time,
+	.read_alarm = nct6694_rtc_read_alarm,
+	.set_alarm = nct6694_rtc_set_alarm,
+	.alarm_irq_enable = nct6694_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t nct6694_irq(int irq, void *dev_id)
+{
+	struct nct6694_rtc_data *data = dev_id;
+	struct nct6694_rtc_cmd2 *buf = (struct nct6694_rtc_cmd2 *)data->xmit_buf;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	buf->irq_en = NCT6694_RTC_IRQ_EN;
+	buf->irq_pend = NCT6694_RTC_IRQ_STS;
+	ret = nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+				NCT6694_RTC_CMD2_OFFSET,
+				NCT6694_RTC_CMD2_LEN,
+				buf);
+	if (ret)
+		return IRQ_NONE;
+
+	rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF);
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_rtc_probe(struct platform_device *pdev)
+{
+	struct nct6694_rtc_data *data;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	int ret, irq;
+
+	irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
+	if (!irq)
+		return -EINVAL;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+				      sizeof(unsigned char), GFP_KERNEL);
+	if (!data->xmit_buf)
+		return -ENOMEM;
+
+	data->rtc = devm_rtc_allocate_device(&pdev->dev);
+	if (IS_ERR(data->rtc))
+		return PTR_ERR(data->rtc);
+
+	data->nct6694 = nct6694;
+	data->rtc->ops = &nct6694_rtc_ops;
+	data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+	data->rtc->range_max = RTC_TIMESTAMP_END_2099;
+
+	mutex_init(&data->lock);
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+
+	platform_set_drvdata(pdev, data);
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+					nct6694_irq, IRQF_ONESHOT,
+					"nct6694-rtc", data);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+	/* Register rtc device to RTC framework */
+	return devm_rtc_register_device(data->rtc);
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+	.driver = {
+		.name	= "nct6694-rtc",
+	},
+	.probe		= nct6694_rtc_probe,
+};
+
+module_platform_driver(nct6694_rtc_driver);
+
+MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-rtc");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2024-12-10 10:57   ` Mateusz Polchlopek
  2024-12-11  1:56     ` Jakub Kicinski
  2024-12-10 14:38   ` Krzysztof Kozlowski
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 39+ messages in thread
From: Mateusz Polchlopek @ 2024-12-10 10:57 UTC (permalink / raw)
  To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
	mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
	wim, linux, jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc



On 12/10/2024 11:45 AM, Ming Yu wrote:
> 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 implements USB device functionality and shares the
> chip's peripherals as a child device.
> 
> 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.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
>   MAINTAINERS                 |   7 +
>   drivers/mfd/Kconfig         |  10 +
>   drivers/mfd/Makefile        |   2 +
>   drivers/mfd/nct6694.c       | 382 ++++++++++++++++++++++++++++++++++++
>   include/linux/mfd/nct6694.h | 139 +++++++++++++
>   5 files changed, 540 insertions(+)
>   create mode 100644 drivers/mfd/nct6694.c
>   create mode 100644 include/linux/mfd/nct6694.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 67d2159406c2..9c9a94ff8f98 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16541,6 +16541,13 @@ F:	drivers/nubus/
>   F:	include/linux/nubus.h
>   F:	include/uapi/linux/nubus.h
>   
> +NUVOTON NCT6694 MFD DRIVER
> +M:	Ming Yu <tmyu0@nuvoton.com>
> +L:	linux-kernel@vger.kernel.org
> +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 03c1e4e3eea4..07ccc32f9985 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -558,6 +558,16 @@ config MFD_MX25_TSADC
>   	  i.MX25 processors. They consist of a conversion queue for general
>   	  purpose ADC and a queue for Touchscreens.
>   
> +config MFD_NCT6694
> +	tristate "Nuvoton NCT6694 support"
> +	select MFD_CORE
> +	depends on USB
> +	help
> +	  This adds support for Nuvoton USB device NCT6694 sharing peripherals
> +	  This includes the USB devcie driver and core APIs.
> +	  Additional drivers must be enabled in order to use the functionality
> +	  of the device.
> +
>   config MFD_HI6421_PMIC
>   	tristate "HiSilicon Hi6421 PMU/Codec IC"
>   	depends on OF
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index e057d6d6faef..9d0365ba6a26 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -117,6 +117,8 @@ obj-$(CONFIG_TWL6040_CORE)	+= twl6040.o
>   
>   obj-$(CONFIG_MFD_MX25_TSADC)	+= fsl-imx25-tsadc.o
>   
> +obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
> +
>   obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
>   obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
>   obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
> diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> new file mode 100644
> index 000000000000..071333d13eae
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,382 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 MFD driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#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>
> +
> +#define MFD_DEV_SIMPLE(_name)				\
> +{							\
> +	.name = NCT6694_DEV_##_name,			\
> +}							\
> +
> +#define MFD_DEV_WITH_ID(_name, _id)			\
> +{							\
> +	.name = NCT6694_DEV_##_name,			\
> +	.id = _id,					\
> +}
> +
> +/* MFD device resources */
> +static const struct mfd_cell nct6694_dev[] = {
> +	MFD_DEV_WITH_ID(GPIO, 0x0),
> +	MFD_DEV_WITH_ID(GPIO, 0x1),
> +	MFD_DEV_WITH_ID(GPIO, 0x2),
> +	MFD_DEV_WITH_ID(GPIO, 0x3),
> +	MFD_DEV_WITH_ID(GPIO, 0x4),
> +	MFD_DEV_WITH_ID(GPIO, 0x5),
> +	MFD_DEV_WITH_ID(GPIO, 0x6),
> +	MFD_DEV_WITH_ID(GPIO, 0x7),
> +	MFD_DEV_WITH_ID(GPIO, 0x8),
> +	MFD_DEV_WITH_ID(GPIO, 0x9),
> +	MFD_DEV_WITH_ID(GPIO, 0xA),
> +	MFD_DEV_WITH_ID(GPIO, 0xB),
> +	MFD_DEV_WITH_ID(GPIO, 0xC),
> +	MFD_DEV_WITH_ID(GPIO, 0xD),
> +	MFD_DEV_WITH_ID(GPIO, 0xE),
> +	MFD_DEV_WITH_ID(GPIO, 0xF),
> +
> +	MFD_DEV_WITH_ID(I2C, 0x0),
> +	MFD_DEV_WITH_ID(I2C, 0x1),
> +	MFD_DEV_WITH_ID(I2C, 0x2),
> +	MFD_DEV_WITH_ID(I2C, 0x3),
> +	MFD_DEV_WITH_ID(I2C, 0x4),
> +	MFD_DEV_WITH_ID(I2C, 0x5),
> +
> +	MFD_DEV_WITH_ID(CAN, 0x0),
> +	MFD_DEV_WITH_ID(CAN, 0x1),
> +
> +	MFD_DEV_WITH_ID(WDT, 0x0),
> +	MFD_DEV_WITH_ID(WDT, 0x1),
> +
> +	MFD_DEV_SIMPLE(HWMON),
> +	MFD_DEV_SIMPLE(RTC),
> +};
> +
> +static int nct6694_response_err_handling(struct nct6694 *nct6694,
> +					 unsigned char err_status)
> +{
> +	struct device *dev = &nct6694->udev->dev;
> +
> +	switch (err_status) {
> +	case NCT6694_NO_ERROR:
> +		return err_status;
> +	case NCT6694_NOT_SUPPORT_ERROR:
> +		dev_dbg(dev, "%s: Command is not support!\n", __func__);
> +		break;
> +	case NCT6694_NO_RESPONSE_ERROR:
> +		dev_dbg(dev, "%s: Command is no response!\n", __func__);
> +		break;
> +	case NCT6694_TIMEOUT_ERROR:
> +		dev_dbg(dev, "%s: Command is timeout!\n", __func__);
> +		break;
> +	case NCT6694_PENDING:
> +		dev_dbg(dev, "%s: Command is pending!\n", __func__);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EIO;
> +}
> +
> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		     u16 length, void *buf)
> +{
> +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> +	struct nct6694_response_header *response_header = nct6694->response_header;

RCT violation

> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);
> +
> +	/* Send command packet to USB device */
> +	cmd_header->mod = mod;
> +	cmd_header->cmd = offset & 0xFF;
> +	cmd_header->sel = (offset >> 8) & 0xFF;
> +	cmd_header->hctrl = NCT6694_HCTRL_GET;
> +	cmd_header->len = length;
> +
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
> +			   cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive response packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	return nct6694_response_err_handling(nct6694, response_header->sts);
> +}
> +EXPORT_SYMBOL(nct6694_read_msg);
> +
> +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		      u16 length, void *buf)
> +{
> +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> +	struct nct6694_response_header *response_header = nct6694->response_header;

Same as above

> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);
> +
> +	/* Send command packet to USB device  */
> +	cmd_header->mod = mod;
> +	cmd_header->cmd = offset & 0xFF;
> +	cmd_header->sel = (offset >> 8) & 0xFF;
> +	cmd_header->hctrl = NCT6694_HCTRL_SET;
> +	cmd_header->len = length;
> +
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
> +			   cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
> +			   buf, length, &tx_len, nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive response packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	return nct6694_response_err_handling(nct6694, response_header->sts);
> +}
> +EXPORT_SYMBOL(nct6694_write_msg);
> +
> +static void usb_int_callback(struct urb *urb)
> +{
> +	struct nct6694 *nct6694 = urb->context;
> +	struct device *dev = &nct6694->udev->dev;
> +	unsigned int *int_status = urb->transfer_buffer;

Again RCT violation

> +	int ret;
> +
> +	switch (urb->status) {
> +	case 0:
> +		break;
> +	case -ECONNRESET:
> +	case -ENOENT:
> +	case -ESHUTDOWN:
> +		return;
> +	default:
> +		goto resubmit;
> +	}
> +
> +	while (*int_status) {
> +		int irq = __ffs(*int_status);
> +
> +		if (*int_status & (1 << irq))
> +			generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> +
> +		*int_status &= ~(1 << irq);
> +	}
> +
> +resubmit:
> +	ret = usb_submit_urb(urb, GFP_ATOMIC);
> +	if (ret)
> +		dev_dbg(dev, "%s: Failed to resubmit urb, status %d",
> +			__func__, ret);
> +}
> +
> +static void nct6694_irq_lock(struct irq_data *data)
> +{
> +	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +
> +	mutex_lock(&nct6694->irq_lock);
> +}
> +
> +static void nct6694_irq_sync_unlock(struct irq_data *data)
> +{
> +	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +
> +	mutex_unlock(&nct6694->irq_lock);
> +}
> +
> +static void nct6694_irq_enable(struct irq_data *data)
> +{
> +	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +	unsigned int irq = data->hwirq;
> +
> +	nct6694->irq_enable |= (1 << irq);

I wonder if this is necessary to create local variable irq
if this is used only once. Maybe instead:

nct6694->irq_enable |= (1 << data->hwirq);

Not big thing though

> +}
> +
> +static void nct6694_irq_disable(struct irq_data *data)
> +{
> +	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +	unsigned int irq = data->hwirq;
> +
> +	nct6694->irq_enable &= ~(1 << irq);
> +}
> +
> +static struct irq_chip nct6694_irq_chip = {
> +	.name = "nct6694-irq",
> +	.flags = IRQCHIP_SKIP_SET_WAKE,
> +	.irq_bus_lock = nct6694_irq_lock,
> +	.irq_bus_sync_unlock = nct6694_irq_sync_unlock,
> +	.irq_enable = nct6694_irq_enable,
> +	.irq_disable = nct6694_irq_disable,
> +};
> +
> +static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq,
> +				  irq_hw_number_t hw)
> +{
> +	struct nct6694 *nct6694 = d->host_data;
> +
> +	irq_set_chip_data(irq, nct6694);
> +	irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
> +
> +	return 0;
> +}
> +
> +static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
> +{
> +	irq_set_chip_and_handler(irq, NULL, NULL);
> +	irq_set_chip_data(irq, NULL);
> +}
> +
> +static const struct irq_domain_ops nct6694_irq_domain_ops = {
> +	.map	= nct6694_irq_domain_map,
> +	.unmap	= nct6694_irq_domain_unmap,
> +};
> +
> +static int nct6694_usb_probe(struct usb_interface *iface,
> +			     const struct usb_device_id *id)
> +{
> +	struct usb_device *udev = interface_to_usbdev(iface);
> +	struct device *dev = &udev->dev;
> +	struct usb_host_interface *interface;
> +	struct usb_endpoint_descriptor *int_endpoint;
> +	struct nct6694 *nct6694;
> +	struct nct6694_cmd_header *cmd_header;
> +	struct nct6694_response_header *response_header;
> +	int pipe, maxp;
> +	int ret;

Please follow RCT rule

> +
> +	interface = iface->cur_altsetting;
> +
> +	int_endpoint = &interface->endpoint[0].desc;
> +	if (!usb_endpoint_is_int_in(int_endpoint))
> +		return -ENODEV;
> +
> +	nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> +	if (!nct6694)
> +		return -ENOMEM;
> +
> +	pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> +	maxp = usb_maxpacket(udev, pipe);
> +
> +	cmd_header = devm_kzalloc(dev, sizeof(*cmd_header),
> +				  GFP_KERNEL);
> +	if (!cmd_header)
> +		return -ENOMEM;
> +
> +	response_header = devm_kzalloc(dev, sizeof(*response_header),
> +				       GFP_KERNEL);
> +	if (!response_header)
> +		return -ENOMEM;
> +
> +	nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
> +					   sizeof(unsigned char), GFP_KERNEL);
> +	if (!nct6694->int_buffer)
> +		return -ENOMEM;
> +
> +	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> +	if (!nct6694->int_in_urb)
> +		return -ENOMEM;> +
> +	nct6694->domain = irq_domain_add_simple(NULL, NCT6694_NR_IRQS, 0,
> +						&nct6694_irq_domain_ops,
> +						nct6694);
> +	if (!nct6694->domain)
> +		return -ENODEV;
> +
> +	nct6694->udev = udev;
> +	nct6694->timeout = NCT6694_URB_TIMEOUT;	/* Wait until urb complete */
> +	nct6694->cmd_header = cmd_header;
> +	nct6694->response_header = response_header;
> +
> +	mutex_init(&nct6694->access_lock);
> +	mutex_init(&nct6694->irq_lock);
> +
> +	usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
> +			 nct6694->int_buffer, maxp, usb_int_callback,
> +			 nct6694, int_endpoint->bInterval);
> +	ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
> +	if (ret)
> +		goto err_urb;
> +
> +	dev_set_drvdata(dev, nct6694);
> +	usb_set_intfdata(iface, nct6694);
> +
> +	ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
> +	if (ret)
> +		goto err_mfd;
> +
> +	dev_info(dev, "Probed device: (%04X:%04X)\n", id->idVendor, id->idProduct);
> +	return 0;
> +
> +err_mfd:
> +	usb_kill_urb(nct6694->int_in_urb);
> +err_urb:
> +	usb_free_urb(nct6694->int_in_urb);
> +	return dev_err_probe(dev, ret, "Probe failed\n");
> +}
> +
> +static void nct6694_usb_disconnect(struct usb_interface *iface)
> +{
> +	struct usb_device *udev = interface_to_usbdev(iface);
> +	struct nct6694 *nct6694 = usb_get_intfdata(iface);
> +
> +	mfd_remove_devices(&udev->dev);
> +	usb_kill_urb(nct6694->int_in_urb);
> +	usb_free_urb(nct6694->int_in_urb);
> +}
> +
> +static const struct usb_device_id nct6694_ids[] = {
> +	{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID,
> +					NCT6694_PRODUCT_ID,
> +					0xFF, 0x00, 0x00)},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(usb, nct6694_ids);
> +
> +static struct usb_driver nct6694_usb_driver = {
> +	.name	= "nct6694",
> +	.id_table = nct6694_ids,
> +	.probe = nct6694_usb_probe,
> +	.disconnect = nct6694_usb_disconnect,
> +};
> +
> +module_usb_driver(nct6694_usb_driver);
> +
> +MODULE_DESCRIPTION("USB-MFD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> new file mode 100644
> index 000000000000..9733d47f5a01
> --- /dev/null
> +++ b/include/linux/mfd/nct6694.h
> @@ -0,0 +1,139 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Nuvoton NCT6694 USB transaction and data structure.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#ifndef __MFD_NCT6694_H
> +#define __MFD_NCT6694_H
> +
> +#define NCT6694_DEV_GPIO	"nct6694-gpio"
> +#define NCT6694_DEV_I2C		"nct6694-i2c"
> +#define NCT6694_DEV_CAN		"nct6694-can"
> +#define NCT6694_DEV_WDT		"nct6694-wdt"
> +#define NCT6694_DEV_HWMON	"nct6694-hwmon"
> +#define NCT6694_DEV_RTC		"nct6694-rtc"
> +
> +#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_MAX_PACKET_SZ	0x200
> +
> +#define NCT6694_CMD_PACKET_SZ	0x8
> +#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_CAN1,
> +	NCT6694_IRQ_CAN2,
> +	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 nct6694 {
> +	struct usb_device *udev;
> +	struct urb *int_in_urb;
> +	struct irq_domain *domain;
> +	struct nct6694_cmd_header *cmd_header;
> +	struct nct6694_response_header *response_header;
> +	struct mutex access_lock;
> +	struct mutex irq_lock;
> +	unsigned char *int_buffer;
> +	unsigned int irq_enable;
> +	/* time in msec to wait for the urb to the complete */
> +	long timeout;
> +};
> +
> +struct nct6694_cmd_header {
> +	unsigned char rsv1;
> +	unsigned char mod;
> +	unsigned char cmd;
> +	unsigned char sel;
> +	unsigned char hctrl;
> +	unsigned char rsv2;
> +	unsigned short len;
> +} __packed;
> +
> +struct nct6694_response_header {
> +	unsigned char sequence_id;
> +	unsigned char sts;
> +	unsigned int rsv;
> +	unsigned short len;
> +} __packed;
> +
> +/*
> + * nct6694_read_msg - Receive data from NCT6694 USB device
> + *
> + * @nct6694 - Nuvoton NCT6694 structure
> + * @mod - Module byte
> + * @offset - Offset byte or (Select byte | Command byte)
> + * @length - Length byte
> + * @buf - Read data from rx buffer
> + *
> + * USB Transaction format:
> + *
> + *	OUT	|RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
> + *	OUT	|SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
> + *	IN	|-------D------A------D------A-------|
> + *	IN			......
> + *	IN	|-------D------A------D------A-------|
> + */
> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		     u16 length, void *buf);
> +
> +/*
> + * nct6694_read_msg - Transmit data to NCT6694 USB device
> + *
> + * @nct6694 - Nuvoton NCT6694 structure
> + * @mod - Module byte
> + * @offset - Offset byte or (Select byte | Command byte)
> + * @length - Length byte
> + * @buf - Write data to tx buffer
> + *
> + * USB Transaction format:
> + *
> + *	OUT	|RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
> + *	OUT	|-------D------A------D------A-------|
> + *	OUT			......
> + *	OUT	|-------D------A------D------A-------|
> + *	IN	|SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
> + *	IN	|-------D------A------D------A-------|
> + *	IN			......
> + *	IN	|-------D------A------D------A-------|
> + */
> +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		      u16 length, void *buf);
> +
> +#endif


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2024-12-10 10:45 ` [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-12-10 12:46   ` Bartosz Golaszewski
  2024-12-12  6:58     ` Ming Yu
  2024-12-20 12:41   ` Linus Walleij
  1 sibling, 1 reply; 39+ messages in thread
From: Bartosz Golaszewski @ 2024-12-10 12:46 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

On Tue, Dec 10, 2024 at 11:46 AM Ming Yu <a0282524688@gmail.com> wrote:
>
> This driver supports GPIO and IRQ functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---

Looks much better now. Please address one more issue I just noticed.

> +
> +       mutex_init(&data->irq_lock);

This is never destroyed. Please use devm_mutex_init() preferably to
not add remove(). Also, the other mutex doesn't seem to be initialized
at all.

Bart

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
  2024-12-10 10:57   ` Mateusz Polchlopek
@ 2024-12-10 14:38   ` Krzysztof Kozlowski
  2024-12-12  5:27     ` Ming Yu
  2024-12-11 16:44   ` Christophe JAILLET
  2024-12-20 12:45   ` Linus Walleij
  3 siblings, 1 reply; 39+ messages in thread
From: Krzysztof Kozlowski @ 2024-12-10 14:38 UTC (permalink / raw)
  To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
	mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
	wim, linux, jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

On 10/12/2024 11:45, Ming Yu wrote:
> +	nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
> +					   sizeof(unsigned char), GFP_KERNEL);
> +	if (!nct6694->int_buffer)
> +		return -ENOMEM;
> +
> +	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> +	if (!nct6694->int_in_urb)
> +		return -ENOMEM;
> +
> +	nct6694->domain = irq_domain_add_simple(NULL, NCT6694_NR_IRQS, 0,
> +						&nct6694_irq_domain_ops,
> +						nct6694);
> +	if (!nct6694->domain)
> +		return -ENODEV;
> +
> +	nct6694->udev = udev;
> +	nct6694->timeout = NCT6694_URB_TIMEOUT;	/* Wait until urb complete */
> +	nct6694->cmd_header = cmd_header;
> +	nct6694->response_header = response_header;
> +
> +	mutex_init(&nct6694->access_lock);
> +	mutex_init(&nct6694->irq_lock);
> +
> +	usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
> +			 nct6694->int_buffer, maxp, usb_int_callback,
> +			 nct6694, int_endpoint->bInterval);
> +	ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
> +	if (ret)
> +		goto err_urb;
> +
> +	dev_set_drvdata(dev, nct6694);
> +	usb_set_intfdata(iface, nct6694);
> +
> +	ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
> +	if (ret)
> +		goto err_mfd;
> +
> +	dev_info(dev, "Probed device: (%04X:%04X)\n", id->idVendor, id->idProduct);

Drop. Duplicating existing messages and interfaces. Your driver is
supposed to be silent on success.

> +	return 0;
> +
> +err_mfd:
> +	usb_kill_urb(nct6694->int_in_urb);
> +err_urb:
> +	usb_free_urb(nct6694->int_in_urb);
> +	return dev_err_probe(dev, ret, "Probe failed\n");

No, this should go to individual call causing errors so this will be
informative. Above is not informative at all and kernel already reports
this, so drop.

> +}
> +
> +static void nct6694_usb_disconnect(struct usb_interface *iface)
> +{
> +	struct usb_device *udev = interface_to_usbdev(iface);
> +	struct nct6694 *nct6694 = usb_get_intfdata(iface);


Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support
  2024-12-10 10:45 ` [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-12-10 15:22   ` Guenter Roeck
  2024-12-12  6:26     ` Ming Yu
  0 siblings, 1 reply; 39+ messages in thread
From: Guenter Roeck @ 2024-12-10 15:22 UTC (permalink / raw)
  To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
	mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
	wim, jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

On 12/10/24 02:45, Ming Yu wrote:
> This driver supports Watchdog timer functionality for NCT6694 MFD
> device based on USB interface.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
...
> +static int nct6694_wdt_probe(struct platform_device *pdev)
> +{
...
> +	wdev->timeout = timeout;
> +	wdev->pretimeout = pretimeout;
> +	if (timeout < pretimeout) {
> +		dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
> +		wdev->pretimeout = 0;
> +	}
> +
> +	wdev->min_timeout = 1;
> +	wdev->max_timeout = 255;
> +
> +	mutex_init(&data->lock);
> +
> +	platform_set_drvdata(pdev, data);
> +
> +	/* Register watchdog timer device to WDT framework */
> +	watchdog_set_drvdata(&data->wdev, data);
> +	watchdog_init_timeout(&data->wdev, timeout, dev);

This is pointless since timeout is pre-initialized with a value != 0.
That means a value provided through devicetree will never be used
unless the user sets timeout=0 as module parameter. But then the above
check for pretimeout is useless.

Guenter


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2024-12-10 10:45 ` [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-12-10 15:58   ` Guenter Roeck
  2024-12-12  7:09     ` Ming Yu
  2024-12-12 16:10   ` Vincent Mailhol
  1 sibling, 1 reply; 39+ messages in thread
From: Guenter Roeck @ 2024-12-10 15:58 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

On Tue, Dec 10, 2024 at 06:45:23PM +0800, Ming Yu wrote:
> This driver supports Hardware monitor functionality for NCT6694 MFD
> device based on USB interface.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>

checkpatch:

WARNING: From:/Signed-off-by: email address mismatch: 'From: Ming Yu <a0282524688@gmail.com>' != 'Signed-off-by: Ming Yu <tmyu0@nuvoton.com>'

> ---
>  MAINTAINERS                   |   1 +
>  drivers/hwmon/Kconfig         |  10 +
>  drivers/hwmon/Makefile        |   1 +
>  drivers/hwmon/nct6694-hwmon.c | 768 ++++++++++++++++++++++++++++++++++
>  4 files changed, 780 insertions(+)
>  create mode 100644 drivers/hwmon/nct6694-hwmon.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 496fe7d5a23f..d6414eea0463 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16546,6 +16546,7 @@ M:	Ming Yu <tmyu0@nuvoton.com>
>  L:	linux-kernel@vger.kernel.org
>  S:	Supported
>  F:	drivers/gpio/gpio-nct6694.c
> +F:	drivers/hwmon/nct6694-hwmon.c
>  F:	drivers/i2c/busses/i2c-nct6694.c
>  F:	drivers/mfd/nct6694.c
>  F:	drivers/net/can/nct6694_canfd.c
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index dd376602f3f1..df40986424bd 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1636,6 +1636,16 @@ config SENSORS_NCT6683
>  	  This driver can also be built as a module. If so, the module
>  	  will be called nct6683.
>  
> +config SENSORS_NCT6694
> +	tristate "Nuvoton NCT6694 Hardware Monitor support"
> +	depends on MFD_NCT6694
> +	help
> +	  Say Y here to support Nuvoton NCT6694 hardware monitoring
> +	  functionality.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called nct6694-hwmon.
> +
>  config SENSORS_NCT6775_CORE
>  	tristate
>  	select REGMAP
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index b827b92f2a78..27a43e67cdb7 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -168,6 +168,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
>  obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
>  obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
>  obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
> +obj-$(CONFIG_SENSORS_NCT6694)	+= nct6694-hwmon.o
>  obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
>  nct6775-objs			:= nct6775-platform.o
>  obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
> diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
> new file mode 100644
> index 000000000000..b2320d64090b
> --- /dev/null
> +++ b/drivers/hwmon/nct6694-hwmon.c
> @@ -0,0 +1,768 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/hwmon.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +/* Host interface */
> +#define NCT6694_RPT_MOD			0xFF
> +#define NCT6694_HWMON_MOD		0x00
> +#define NCT6694_PWM_MOD			0x01
> +
> +/* Report Channel */
> +#define NCT6694_VIN_IDX(x)		(0x00 + (x))
> +#define NCT6694_TIN_IDX(x)			\
> +	({ typeof(x) (_x) = (x);		\
> +	 ((_x) < 10) ? (0x10 + ((_x) * 2)) :	\
> +	 (0x30 + (((_x) - 10) * 2)); })
> +#define NCT6694_FIN_IDX(x)		(0x50 + ((x) * 2))
> +#define NCT6694_PWM_IDX(x)		(0x70 + (x))
> +#define NCT6694_VIN_STS(x)		(0x68 + (x))
> +#define NCT6694_TIN_STS(x)		(0x6A + (x))
> +#define NCT6694_FIN_STS(x)		(0x6E + (x))
> +
> +/* Message Channel*/
> +/* HWMON Command */
> +/* Command 00h */
> +#define NCT6694_HWMON_CMD0_LEN		0x40
> +#define NCT6694_HWMON_CMD0_OFFSET	0x0000	/* OFFSET = SEL|CMD */
> +#define NCT6694_VIN_EN(x)		(0x00 + (x))
> +#define NCT6694_TIN_EN(x)		(0x02 + (x))
> +#define NCT6694_FIN_EN(x)		(0x04 + (x))
> +#define NCT6694_PWM_EN(x)		(0x06 + (x))
> +#define NCT6694_PWM_FREQ_IDX(x)		(0x30 + (x))
> +/* Command 02h */
> +#define NCT6694_HWMON_CMD2_LEN		0x90
> +#define NCT6694_HWMON_CMD2_OFFSET	0x0002	/* OFFSET = SEL|CMD */
> +#define NCT6694_SMI_CTRL_IDX		0x00
> +#define NCT6694_VIN_HL(x)		(0x10 + ((x) * 2))
> +#define NCT6694_VIN_LL(x)		(0x11 + ((x) * 2))
> +#define NCT6694_TIN_HYST(x)		(0x30 + ((x) * 2))
> +#define NCT6694_TIN_HL(x)		(0x31 + ((x) * 2))
> +#define NCT6694_FIN_HL(x)		(0x70 + ((x) * 2))
> +#define NCT6694_FIN_LL(x)		(0x71 + ((x) * 2))
> +/* PWM Command */
> +#define NCT6694_PWM_CMD1_LEN		0x18
> +#define NCT6694_PWM_CMD1_OFFSET		0x0001
> +#define NCT6694_MAL_VAL(x)		(0x02 + (x))
> +
> +#define NCT6694_FREQ_FROM_REG(reg)	((reg) * 25000 / 255)
> +#define NCT6694_FREQ_TO_REG(val)	\
> +	(DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
> +
> +#define NCT6694_LSB_REG_MASK		GENMASK(7, 5)
> +#define NCT6694_TIN_HYST_MASK		GENMASK(7, 5)
> +
> +static inline long in_from_reg(u8 reg)
> +{
> +	return reg * 16;
> +}
> +
> +static inline u8 in_to_reg(long val)
> +{
> +	if (val <= 0)
> +		return 0;

This is pointless since the calling code already clamps the value to [0, 2032].

> +	return val / 16;

DIV_ROUND_CLOSEST() ?

> +}
> +
> +static inline long temp_from_reg(u8 reg)
> +{
> +	return reg * 1000;

This always returns a positive value, even though the temperature is
supposed to be signed. More on that below.

> +}
> +
> +static inline u8 temp_to_reg(long val)
> +{
> +	return val / 1000;

DIV_ROUND_CLOSEST() ?

> +}
> +
> +struct nct6694_hwmon_data {
> +	struct nct6694 *nct6694;
> +	struct mutex lock;
> +	unsigned char *xmit_buf;
> +	unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
> +};
> +
> +#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE |	\
> +				 HWMON_I_MAX | HWMON_I_MIN |		\
> +				 HWMON_I_ALARM)
> +#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE |	\
> +				   HWMON_T_MAX | HWMON_T_MAX_HYST |	\
> +				   HWMON_T_MAX_ALARM)
> +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE |	\
> +				  HWMON_F_MIN | HWMON_F_MIN_ALARM)
> +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE |	\
> +				  HWMON_PWM_FREQ)
> +static const struct hwmon_channel_info *nct6694_info[] = {
> +	HWMON_CHANNEL_INFO(in,
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN0 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN1 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN2 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN3 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN5 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN6 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN7 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN14 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN15 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VIN16 */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VBAT */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VSB */
> +			   NCT6694_HWMON_IN_CONFIG,	/* AVSB */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VCC */
> +			   NCT6694_HWMON_IN_CONFIG,	/* VHIF */
> +			   NCT6694_HWMON_IN_CONFIG),	/* VTT */
> +
> +	HWMON_CHANNEL_INFO(temp,
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* THR1 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* THR2 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* THR14 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* THR15 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* THR16 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP0 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP1 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP2 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP3 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP4 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN0 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN1 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN2 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN3 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN4 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN5 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN6 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN7 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN8 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN9 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN10 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN11 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN12 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN13 */
> +			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN14 */
> +			   NCT6694_HWMON_TEMP_CONFIG),	/* DTIN15 */
> +
> +	HWMON_CHANNEL_INFO(fan,
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN0 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN1 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN2 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN3 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN4 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN5 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN6 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN7 */
> +			   NCT6694_HWMON_FAN_CONFIG,	/* FIN8 */
> +			   NCT6694_HWMON_FAN_CONFIG),	/* FIN9 */
> +
> +	HWMON_CHANNEL_INFO(pwm,
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM0 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM1 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM2 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM3 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM4 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM5 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM6 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM7 */
> +			   NCT6694_HWMON_PWM_CONFIG,	/* PWM8 */
> +			   NCT6694_HWMON_PWM_CONFIG),	/* PWM9 */
> +	NULL
> +};
> +
> +static int nct6694_in_read(struct device *dev, u32 attr, int channel,
> +			   long *val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	unsigned char vin_en;
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_in_enable:
> +		vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
> +		*val = !!(vin_en & BIT(channel % 8)) ? 1 : 0;

Drop "? 1 : 0"

> +
> +		return 0;
> +	case hwmon_in_input:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_VIN_IDX(channel), 1,
> +				       data->xmit_buf);

I am curious: Since the received data length is not returned, and the
expected minimum length is not passed to nct6694_read_msg(), the driver
has no means to check if the received data actually includes the expected
values at the expected index. That doesn't matter here, but some of the
returned data is located far into the buffer. Is it indeed not necessary
for the driver to check if the received data is actually as long as
expected ?

> +		if (ret)
> +			return ret;
> +
> +		*val = in_from_reg(data->xmit_buf[0]);
> +
> +		return 0;
> +	case hwmon_in_max:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = in_from_reg(data->xmit_buf[NCT6694_VIN_HL(channel)]);
> +
> +		return 0;
> +	case hwmon_in_min:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = in_from_reg(data->xmit_buf[NCT6694_VIN_LL(channel)]);
> +
> +		return 0;
> +	case hwmon_in_alarm:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_VIN_STS(channel / 8), 1,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
> +
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
> +			     long *val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	unsigned char temp_en, temp_hyst;
> +	int ret, int_part, frac_part;
> +	signed char temp_max;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_temp_enable:
> +		temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
> +		*val = !!(temp_en & BIT(channel % 8)) ? 1 : 0;

Drop "? 1 : 0"

> +
> +		return 0;
> +	case hwmon_temp_input:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_TIN_IDX(channel), 2,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		int_part = sign_extend32(data->xmit_buf[0], 7);
> +		frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
> +		if (int_part < 0)
> +			*val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
> +		else
> +			*val = int_part * 1000 + frac_part * 125;
> +
> +		return 0;
> +	case hwmon_temp_max:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
> +

If the value in NCT6694_TIN_HL() is signed, this will return a large positive
value instead.

> +		return 0;
> +	case hwmon_temp_max_hyst:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> +		temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
> +				      data->xmit_buf[NCT6694_TIN_HYST(channel)]);
> +		if (temp_max < 0)

This suggests that the temperature limit is signed, suggesting in turn that
temp_from_reg() is wrong.

> +			*val = temp_from_reg(temp_max + temp_hyst);
> +		else
> +			*val = temp_from_reg(temp_max - temp_hyst);
> +
> +		return 0;
> +	case hwmon_temp_max_alarm:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_TIN_STS(channel / 8), 1,
> +					   data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
> +
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
> +			    long *val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	unsigned char fanin_en;
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_fan_enable:
> +		fanin_en = data->hwmon_en[NCT6694_FIN_EN(channel / 8)];
> +		*val = !!(fanin_en & BIT(channel % 8)) ? 1 : 0;

Drop "? 1 : 0"

> +
> +		return 0;
> +	case hwmon_fan_input:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_FIN_IDX(channel), 2,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = (data->xmit_buf[1] |
> +		       (data->xmit_buf[0] << 8)) & 0xFFFF;

The "& 0xffff" is pointless.

> +
> +		return 0;
> +	case hwmon_fan_min:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = (data->xmit_buf[NCT6694_FIN_LL(channel)] |
> +			data->xmit_buf[NCT6694_FIN_HL(channel)] << 8) & 0xFFFF;

Same here.

> +
> +		return 0;
> +	case hwmon_fan_min_alarm:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_FIN_STS(channel / 8),
> +				       1, data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = !!(data->xmit_buf[0] & BIT(channel % 8));
> +
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
> +			    long *val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	unsigned char pwm_en;
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_pwm_enable:
> +		pwm_en = data->hwmon_en[NCT6694_PWM_EN(channel / 8)];
> +		*val = !!(pwm_en & BIT(channel % 8)) ? 1 : 0;

Drop "? 1 : 0".

> +
> +		return 0;
> +	case hwmon_pwm_input:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> +				       NCT6694_PWM_IDX(channel),
> +				       1, data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		*val = data->xmit_buf[0];
> +
> +		return 0;
> +	case hwmon_pwm_freq:
> +		*val = NCT6694_FREQ_FROM_REG(data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)]);
> +
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_enable_channel(struct device *dev, u8 reg,
> +				  int channel, long val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +
> +	if (val == 0)
> +		data->hwmon_en[reg] &= ~BIT(channel % 8);
> +	else if (val == 1)
> +		data->hwmon_en[reg] |= BIT(channel % 8);
> +	else
> +		return -EINVAL;
> +
> +	return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				 NCT6694_HWMON_CMD0_OFFSET,
> +				 NCT6694_HWMON_CMD0_LEN,
> +				 data->hwmon_en);
> +}
> +
> +static int nct6694_in_write(struct device *dev, u32 attr, int channel,
> +			    long val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_in_enable:
> +		return nct6694_enable_channel(dev, NCT6694_VIN_EN(channel / 8),
> +					      channel, val);
> +	case hwmon_in_max:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		val = clamp_val(val, 0, 2032);
> +		data->xmit_buf[NCT6694_VIN_HL(channel)] = in_to_reg(val);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD2_OFFSET,
> +					 NCT6694_HWMON_CMD2_LEN,
> +					 data->xmit_buf);
> +	case hwmon_in_min:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		val = clamp_val(val, 0, 2032);
> +		data->xmit_buf[NCT6694_VIN_LL(channel)] = in_to_reg(val);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD2_OFFSET,
> +					 NCT6694_HWMON_CMD2_LEN,
> +					 data->xmit_buf);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
> +			      long val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	signed char temp_max, temp_hyst;
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_temp_enable:
> +		return nct6694_enable_channel(dev, NCT6694_TIN_EN(channel / 8),
> +					      channel, val);
> +	case hwmon_temp_max:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		val = clamp_val(val, -127000, 127000);
> +		data->xmit_buf[NCT6694_TIN_HL(channel)] = temp_to_reg(val);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD2_OFFSET,
> +					 NCT6694_HWMON_CMD2_LEN,
> +					 data->xmit_buf);
> +	case hwmon_temp_max_hyst:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +
> +		val = clamp_val(val, -127000, 127000);
> +		temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> +		temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
> +					     (temp_max - val / 1000);

DIV_ROUND_CLOSEST() ?

> +		temp_hyst = clamp_val(temp_hyst, 0, 7);
> +		data->xmit_buf[NCT6694_TIN_HYST(channel)] =
> +		       (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
> +		       FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD2_OFFSET,
> +					 NCT6694_HWMON_CMD2_LEN,
> +					 data->xmit_buf);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> +			     long val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_fan_enable:
> +		return nct6694_enable_channel(dev, NCT6694_FIN_EN(channel / 8),
> +					      channel, val);
> +	case hwmon_fan_min:
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				       NCT6694_HWMON_CMD2_OFFSET,
> +				       NCT6694_HWMON_CMD2_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		val = clamp_val(val, 1, 65535);
> +		data->xmit_buf[NCT6694_FIN_HL(channel)] = (u8)((val >> 8) & 0xFF);
> +		data->xmit_buf[NCT6694_FIN_LL(channel)] = (u8)(val & 0xFF);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD2_OFFSET,
> +					 NCT6694_HWMON_CMD2_LEN,
> +					 data->xmit_buf);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
> +			     long val)
> +{
> +	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +
> +	switch (attr) {
> +	case hwmon_pwm_enable:
> +		return nct6694_enable_channel(dev, NCT6694_PWM_EN(channel / 8),
> +					      channel, val);
> +	case hwmon_pwm_input:
> +		if (val < 0 || val > 255)
> +			return -EINVAL;
> +
> +		ret = nct6694_read_msg(data->nct6694, NCT6694_PWM_MOD,
> +				       NCT6694_PWM_CMD1_OFFSET,
> +				       NCT6694_PWM_CMD1_LEN,
> +				       data->xmit_buf);
> +		if (ret)
> +			return ret;
> +
> +		data->xmit_buf[NCT6694_MAL_VAL(channel)] = val;
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_PWM_MOD,
> +					 NCT6694_PWM_CMD1_OFFSET,
> +					 NCT6694_PWM_CMD1_LEN,
> +					 data->xmit_buf);
> +	case hwmon_pwm_freq:
> +		data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)] = NCT6694_FREQ_TO_REG(val);
> +
> +		return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +					 NCT6694_HWMON_CMD0_OFFSET,
> +					 NCT6694_HWMON_CMD0_LEN,
> +					 data->hwmon_en);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> +			u32 attr, int channel, long *val)
> +{
> +	switch (type) {
> +	case hwmon_in:	/* in mV */
> +		return nct6694_in_read(dev, attr, channel, val);
> +	case hwmon_temp:/* in mC */
> +		return nct6694_temp_read(dev, attr, channel, val);
> +	case hwmon_fan:	/* in RPM */
> +		return nct6694_fan_read(dev, attr, channel, val);
> +	case hwmon_pwm:	/* in value 0~255 */
> +		return nct6694_pwm_read(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> +			 u32 attr, int channel, long val)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		return nct6694_in_write(dev, attr, channel, val);
> +	case hwmon_temp:
> +		return nct6694_temp_write(dev, attr, channel, val);
> +	case hwmon_fan:
> +		return nct6694_fan_write(dev, attr, channel, val);
> +	case hwmon_pwm:
> +		return nct6694_pwm_write(dev, attr, channel, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static umode_t nct6694_is_visible(const void *data,
> +				  enum hwmon_sensor_types type,
> +				  u32 attr, int channel)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		switch (attr) {
> +		case hwmon_in_enable:
> +		case hwmon_in_max:
> +		case hwmon_in_min:
> +			return 0644;
> +		case hwmon_in_alarm:
> +		case hwmon_in_input:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_temp:
> +		switch (attr) {
> +		case hwmon_temp_enable:
> +		case hwmon_temp_max:
> +		case hwmon_temp_max_hyst:
> +			return 0644;
> +		case hwmon_temp_input:
> +		case hwmon_temp_max_alarm:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_enable:
> +		case hwmon_fan_min:
> +			return 0644;
> +		case hwmon_fan_input:
> +		case hwmon_fan_min_alarm:
> +			return 0444;
> +		default:
> +			return 0;
> +		}
> +	case hwmon_pwm:
> +		switch (attr) {
> +		case hwmon_pwm_enable:
> +		case hwmon_pwm_freq:
> +		case hwmon_pwm_input:
> +			return 0644;
> +		default:
> +			return 0;
> +		}
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static const struct hwmon_ops nct6694_hwmon_ops = {
> +	.is_visible = nct6694_is_visible,
> +	.read = nct6694_read,
> +	.write = nct6694_write,
> +};
> +
> +static const struct hwmon_chip_info nct6694_chip_info = {
> +	.ops = &nct6694_hwmon_ops,
> +	.info = nct6694_info,
> +};
> +
> +static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
> +{
> +	int ret;
> +
> +	guard(mutex)(&data->lock);
> +

Not significant, but this is unnecessary because the function is only
called once from probe.

> +	/*
> +	 *  Record each Hardware Monitor Channel enable status
> +	 *  and PWM frequency register
> +	 */
> +	ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +			       NCT6694_HWMON_CMD0_OFFSET,
> +			       NCT6694_HWMON_CMD0_LEN,
> +			       data->hwmon_en);
> +	if (ret)
> +		return ret;
> +
> +	/* Set Fan input Real Time alarm mode */
> +	ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> +			       NCT6694_HWMON_CMD2_OFFSET,
> +			       NCT6694_HWMON_CMD2_LEN,
> +			       data->xmit_buf);
> +	if (ret)
> +		return ret;
> +
> +	data->xmit_buf[NCT6694_SMI_CTRL_IDX] = 0x02;
> +
> +	return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> +				 NCT6694_HWMON_CMD2_OFFSET,
> +				 NCT6694_HWMON_CMD2_LEN,
> +				 data->xmit_buf);
> +}
> +
> +static int nct6694_hwmon_probe(struct platform_device *pdev)
> +{
> +	struct nct6694_hwmon_data *data;
> +	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> +	struct device *hwmon_dev;
> +	int ret;
> +
> +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> +				      sizeof(unsigned char), GFP_KERNEL);

Wondering ... why not just devm_kzalloc(..,. NCT6694_MAX_PACKET_SZ, ...) ?
sizeof(unsigned char) is always 1, after all.

> +	if (!data->xmit_buf)
> +		return -ENOMEM;
> +
> +	data->nct6694 = nct6694;
> +	mutex_init(&data->lock);
> +	platform_set_drvdata(pdev, data);

This is unnecessary unless I am missing something.

> +
> +	ret = nct6694_hwmon_init(data);
> +	if (ret)
> +		return ret;
> +
> +	/* Register hwmon device to HWMON framework */
> +	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> +							 "nct6694", data,
> +							 &nct6694_chip_info,
> +							 NULL);
> +	return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static struct platform_driver nct6694_hwmon_driver = {
> +	.driver = {
> +		.name	= "nct6694-hwmon",
> +	},
> +	.probe		= nct6694_hwmon_probe,
> +};
> +
> +module_platform_driver(nct6694_hwmon_driver);
> +
> +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-hwmon");
> -- 
> 2.34.1
> 
> 

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:57   ` Mateusz Polchlopek
@ 2024-12-11  1:56     ` Jakub Kicinski
  2024-12-11  7:16       ` Mateusz Polchlopek
  0 siblings, 1 reply; 39+ messages in thread
From: Jakub Kicinski @ 2024-12-11  1:56 UTC (permalink / raw)
  To: Mateusz Polchlopek
  Cc: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
	mailhol.vincent, andrew+netdev, davem, edumazet, pabeni, wim,
	linux, jdelvare, alexandre.belloni, linux-kernel, linux-gpio,
	linux-i2c, linux-can, netdev, linux-watchdog, linux-hwmon,
	linux-rtc

On Tue, 10 Dec 2024 11:57:41 +0100 Mateusz Polchlopek wrote:
> > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> > +		     u16 length, void *buf)
> > +{
> > +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> > +	struct nct6694_response_header *response_header = nct6694->response_header;  
> 
> RCT violation

This code is not under net not drivers/net
As a general rule please focus on functional review, formatting and
process issues are harder to judge unless you read all of the mailing
list traffic.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-11  1:56     ` Jakub Kicinski
@ 2024-12-11  7:16       ` Mateusz Polchlopek
  0 siblings, 0 replies; 39+ messages in thread
From: Mateusz Polchlopek @ 2024-12-11  7:16 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
	mailhol.vincent, andrew+netdev, davem, edumazet, pabeni, wim,
	linux, jdelvare, alexandre.belloni, linux-kernel, linux-gpio,
	linux-i2c, linux-can, netdev, linux-watchdog, linux-hwmon,
	linux-rtc



On 12/11/2024 2:56 AM, Jakub Kicinski wrote:
> On Tue, 10 Dec 2024 11:57:41 +0100 Mateusz Polchlopek wrote:
>>> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
>>> +		     u16 length, void *buf)
>>> +{
>>> +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
>>> +	struct nct6694_response_header *response_header = nct6694->response_header;
>>
>> RCT violation
> 
> This code is not under net not drivers/net
> As a general rule please focus on functional review, formatting and
> process issues are harder to judge unless you read all of the mailing
> list traffic.

Okay, my bad. Thanks Kuba for explanation and I will focus on code next
time

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-10 10:45 ` [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-12-11  9:59   ` Marc Kleine-Budde
  2024-12-11 15:25     ` Vincent Mailhol
  2024-12-16  6:21     ` Ming Yu
  0 siblings, 2 replies; 39+ messages in thread
From: Marc Kleine-Budde @ 2024-12-11  9:59 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

[-- Attachment #1: Type: text/plain, Size: 30111 bytes --]

On 10.12.2024 18:45:21, Ming Yu wrote:
> This driver supports Socket CANfd functionality for NCT6694 MFD
> device based on USB interface.

Please use the rx-offload helper, otherwise the CAN frames might be
revived out of order.

> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
>  MAINTAINERS                     |   1 +
>  drivers/net/can/Kconfig         |  10 +
>  drivers/net/can/Makefile        |   1 +
>  drivers/net/can/nct6694_canfd.c | 920 ++++++++++++++++++++++++++++++++
>  4 files changed, 932 insertions(+)
>  create mode 100644 drivers/net/can/nct6694_canfd.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a190f2b08fa3..eb5d46825e71 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16548,6 +16548,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/nct6694_canfd.c
>  F:	include/linux/mfd/nct6694.h
>  
>  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
> index cf989bea9aa3..130e98ec28a5 100644
> --- a/drivers/net/can/Kconfig
> +++ b/drivers/net/can/Kconfig
> @@ -200,6 +200,16 @@ config CAN_SUN4I
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called sun4i_can.
>  
> +config CAN_NCT6694
> +	tristate "Nuvoton NCT6694 Socket CANfd support"
> +	depends on MFD_NCT6694
> +	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_TI_HECC
>  	depends on ARM
>  	tristate "TI High End CAN Controller"
> diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> index a71db2cfe990..4a6b5b9d6c2b 100644
> --- a/drivers/net/can/Makefile
> +++ b/drivers/net/can/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3)	+= janz-ican3.o
>  obj-$(CONFIG_CAN_KVASER_PCIEFD)	+= kvaser_pciefd.o
>  obj-$(CONFIG_CAN_MSCAN)		+= mscan/
>  obj-$(CONFIG_CAN_M_CAN)		+= m_can/
> +obj-$(CONFIG_CAN_NCT6694)	+= nct6694_canfd.o
>  obj-$(CONFIG_CAN_PEAK_PCIEFD)	+= peak_canfd/
>  obj-$(CONFIG_CAN_SJA1000)	+= sja1000/
>  obj-$(CONFIG_CAN_SUN4I)		+= sun4i_can.o
> diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c
> new file mode 100644
> index 000000000000..54f20f0681e2
> --- /dev/null
> +++ b/drivers/net/can/nct6694_canfd.c
> @@ -0,0 +1,920 @@
> +// 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/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 DRVNAME "nct6694-can"
> +
> +/* Host interface */
> +#define NCT6694_CAN_MOD			0x05
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define NCT6694_CAN_CMD0_OFFSET(idx)	(idx ? 0x0100 : 0x0000)
> +#define NCT6694_CAN_CTRL1_MON		BIT(0)
> +#define NCT6694_CAN_CTRL1_NISO		BIT(1)
> +#define NCT6694_CAN_CTRL1_LBCK		BIT(2)
> +
> +/* Command 01h */
> +#define NCT6694_CAN_CMD1_OFFSET		0x0001
> +
> +/* Command 02h */
> +#define NCT6694_CAN_CMD2_OFFSET(idx, mask)			\
> +	({ typeof(mask) mask_ = (mask);				\
> +	   idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) :	\
> +		 ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
> +
> +#define NCT6694_CAN_EVENT_ERR		BIT(0)
> +#define NCT6694_CAN_EVENT_STATUS	BIT(1)
> +#define NCT6694_CAN_EVENT_TX_EVT	BIT(2)
> +#define NCT6694_CAN_EVENT_RX_EVT	BIT(3)
> +#define NCT6694_CAN_EVENT_REC		BIT(4)
> +#define NCT6694_CAN_EVENT_TEC		BIT(5)
> +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY	BIT(7)	/* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_LOST	BIT(5)	/* Read-clear */
> +#define NCT6694_CAN_EVT_RX_HALF_FULL	BIT(6)	/* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_IN	BIT(7)
> +
> +/* Command 10h */
> +#define NCT6694_CAN_CMD10_OFFSET(buf_cnt)	\
> +	(((buf_cnt) & 0xFF) << 8 | 0x10)
> +#define NCT6694_CAN_TAG_CAN0		0xC0
> +#define NCT6694_CAN_TAG_CAN1		0xC1
> +#define NCT6694_CAN_FLAG_EFF		BIT(0)
> +#define NCT6694_CAN_FLAG_RTR		BIT(1)
> +#define NCT6694_CAN_FLAG_FD		BIT(2)
> +#define NCT6694_CAN_FLAG_BRS		BIT(3)
> +#define NCT6694_CAN_FLAG_ERR		BIT(4)
> +
> +/* Command 11h */
> +#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt)			\
> +	({ typeof(buf_cnt) buf_cnt_ = (buf_cnt);		\
> +	   idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) :	\
> +		 ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })
> +
> +#define NCT6694_CAN_RX_QUOTA		64
> +
> +enum nct6694_event_err {
> +	NCT6694_CAN_EVT_NO_ERROR,
                      ^^^ add _ERR_
> +	NCT6694_CAN_EVT_CRC_ERROR,
> +	NCT6694_CAN_EVT_STUFF_ERROR,
> +	NCT6694_CAN_EVT_ACK_ERROR,
> +	NCT6694_CAN_EVT_FORM_ERROR,
> +	NCT6694_CAN_EVT_BIT_ERROR,
> +	NCT6694_CAN_EVT_TIMEOUT_ERROR,
> +	NCT6694_CAN_EVT_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> +	NCT6694_CAN_EVT_ERROR_ACTIVE,
                      ^^^ add _STATUS_
> +	NCT6694_CAN_EVT_ERROR_PASSIVE,
> +	NCT6694_CAN_EVT_BUS_OFF,
> +	NCT6694_CAN_EVT_WARNING,
> +};
> +
> +struct __packed nct6694_can_setting {
> +	__le32 nbr;
> +	__le32 dbr;
> +	u8 active;
> +	u8 reserved[3];
> +	__le16 ctrl1;
> +	__le16 ctrl2;
> +	__le32 nbtp;
> +	__le32 dbtp;
> +};
> +
> +struct __packed nct6694_can_information {
> +	u8 tx_fifo_cnt;
> +	u8 rx_fifo_cnt;
> +	__le16 reserved;
u8 reserved[2];
> +	__le32 can_clk;
> +};
> +
> +struct __packed nct6694_can_event {
> +	u8 err1;
> +	u8 status1;
> +	u8 tx_evt1;
> +	u8 rx_evt1;
> +	u8 rec1;
> +	u8 tec1;
> +	u8 reserved1[2];
> +	u8 err2;
> +	u8 status2;
> +	u8 tx_evt2;
> +	u8 rx_evt2;
> +	u8 rec2;
> +	u8 tec2;
> +	u8 reserved2[2];
> +};

Create an extra struct that only describes a channel

struct __packed nct6694_can_event_channel {
	u8 err;
	u8 status;
	u8 tx_evt;
	u8 rx_evt;
	u8 rec;
	u8 tec;
	u8 reserved[2];
}

and put an array of 2 into struct __packed nct6694_can_event.

> +
> +struct __packed nct6694_can_xmit {
> +	u8 tag;
> +	u8 flag;
> +	u8 reserved;
> +	u8 dlc;
> +	__le32 id;
> +	u8 data[64];
> +	u8 msg_buf[72];

Why is the message so long? What's in the msg_buf?

> +};
> +
> +struct nct6694_can_priv {
> +	struct can_priv can;	/* must be the first member */
> +	struct net_device *ndev;
> +	struct nct6694 *nct6694;
> +	struct mutex lock;

What does lock protect?

> +	struct sk_buff *tx_skb;
> +	struct workqueue_struct *wq;
> +	struct work_struct tx_work;
> +	unsigned char *tx_buf;
void *
> +	unsigned char *rx_buf;
void *
> +	unsigned char can_idx;
> +	bool tx_busy;

IMHO it makes no sense to have tx_skb and tx_busy

> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> +	.name = DRVNAME,
> +	.tseg1_min = 2,
> +	.tseg1_max = 256,
> +	.tseg2_min = 2,
> +	.tseg2_max = 128,
> +	.sjw_max = 128,
> +	.brp_min = 1,
> +	.brp_max = 511,
> +	.brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> +	.name = DRVNAME,
> +	.tseg1_min = 1,
> +	.tseg1_max = 32,
> +	.tseg2_min = 1,
> +	.tseg2_max = 16,
> +	.sjw_max = 16,
> +	.brp_min = 1,
> +	.brp_max = 31,
> +	.brp_inc = 1,
> +};
> +
> +static void nct6694_can_clean(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +	if (priv->tx_skb || priv->tx_busy)
> +		ndev->stats.tx_errors++;
> +	dev_kfree_skb(priv->tx_skb);
> +	if (priv->tx_busy)
> +		can_free_echo_skb(priv->ndev, 0, NULL);
> +	priv->tx_skb = NULL;
> +	priv->tx_busy = false;
> +}
> +
> +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> +					struct can_berr_counter *bec)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> +	u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
> +	int ret;
> +
> +	guard(mutex)(&priv->lock);
> +
> +	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +			       NCT6694_CAN_CMD2_OFFSET(priv->can_idx, mask),
> +			       sizeof(struct nct6694_can_event),
> +			       evt);
> +	if (ret < 0)
> +		return ret;
> +
> +	bec->rxerr = priv->can_idx ? evt->rec2 : evt->rec1;
> +	bec->txerr = priv->can_idx ? evt->tec2 : evt->tec1;
> +
> +	return 0;
> +}
> +
> +static int nct6694_can_handle_lost_msg(struct net_device *ndev)
> +{
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct sk_buff *skb;
> +	struct can_frame *frame;
> +
> +	netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
> +
> +	stats->rx_errors++;
> +	stats->rx_over_errors++;
> +
> +	skb = alloc_can_err_skb(ndev, &frame);
> +	if (unlikely(!skb))
> +		return 0;
> +
> +	frame->can_id |= CAN_ERR_CRTL;
> +	frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> +
> +	netif_receive_skb(skb);
> +
> +	return 1;
> +}
> +
> +static void nct6694_can_read_fifo(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->rx_buf;
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct canfd_frame *cf;
> +	struct sk_buff *skb;
> +	int can_idx = priv->can_idx;
> +	u32 id;
> +	int ret;
> +	u8 fd_format = 0;
bool - no need to init
> +
> +	guard(mutex)(&priv->lock);
> +
> +	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +			       NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
> +			       sizeof(struct nct6694_can_xmit), xmit);
> +	if (ret < 0)
> +		return;
> +
> +	/* Check type of frame and create skb */
> +	fd_format = xmit->flag & NCT6694_CAN_FLAG_FD;
> +	if (fd_format)
> +		skb = alloc_canfd_skb(ndev, &cf);
> +	else
> +		skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> +
> +	if (!skb) {
> +		stats->rx_dropped++;
> +		return;
> +	}
> +
> +	cf->len = xmit->dlc;

what does xmit->dlc contain? The DLC or the length?

> +
> +	/* Get ID and set flag by its type(Standard ID format or Ext ID format) */
> +	id = le32_to_cpu(xmit->id);
> +	if (xmit->flag & NCT6694_CAN_FLAG_EFF) {
> +		/*
> +		 * In case the Extended ID frame is received, the standard
> +		 * and extended part of the ID are swapped in the register,
> +		 * so swap them back to obtain the correct ID.
> +		 */

You comment doesn't match the code.

> +		id |= CAN_EFF_FLAG;
> +	}
> +
> +	cf->can_id = id;
> +
> +	/* Set ESI flag */
> +	if (xmit->flag & NCT6694_CAN_FLAG_ERR) {
> +		cf->flags |= CANFD_ESI;
> +		netdev_dbg(ndev, "ESI Error\n");
> +	}
> +
> +	/* Set RTR and BRS */
> +	if (!fd_format && (xmit->flag & NCT6694_CAN_FLAG_RTR)) {
> +		cf->can_id |= CAN_RTR_FLAG;
> +	} else {
> +		if (xmit->flag & NCT6694_CAN_FLAG_BRS)
> +			cf->flags |= CANFD_BRS;
> +
> +		memcpy(cf->data, xmit->data, cf->len);
> +
> +		stats->rx_bytes += cf->len;
> +	}
> +
> +	stats->rx_packets++;
> +
> +	netif_receive_skb(skb);
> +}
> +
> +static int nct6694_can_do_rx_poll(struct net_device *ndev, int quota)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> +	int can_idx = priv->can_idx;
> +	u32 pkts = 0;
> +	u8 mask_rx = NCT6694_CAN_EVENT_RX_EVT;
> +	u8 rx_evt;
> +
> +	for (;;) {
> +		scoped_guard(mutex, &priv->lock) {
> +			nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +					 NCT6694_CAN_CMD2_OFFSET(can_idx, mask_rx),
> +					 sizeof(struct nct6694_can_event), evt);
> +
> +			rx_evt = can_idx ? evt->rx_evt2 : evt->rx_evt1;
> +		}
> +
> +		if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST)
> +			nct6694_can_handle_lost_msg(ndev);
> +
> +		/* No data */
> +		if ((rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) == 0)
> +			break;
> +
> +		if (quota <= 0)
> +			break;
> +
> +		nct6694_can_read_fifo(ndev);
> +		quota--;
> +		pkts++;
> +	}
> +
> +	return pkts;
> +}
> +
> +static int nct6694_can_handle_lec_err(struct net_device *ndev, u8 bus_err)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +
> +	if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
> +		return 0;
> +
> +	priv->can.can_stats.bus_error++;
> +	stats->rx_errors++;
> +
> +	/* Propagate the error condition to the CAN stack. */
> +	skb = alloc_can_err_skb(ndev, &cf);
> +
> +	if (unlikely(!skb))
> +		return 0;
> +
> +	/* Read the error counter register and check for new errors. */
> +	cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> +	switch (bus_err) {
> +	case NCT6694_CAN_EVT_CRC_ERROR:
> +		cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
> +		break;
> +
> +	case NCT6694_CAN_EVT_STUFF_ERROR:
> +		cf->data[2] |= CAN_ERR_PROT_STUFF;
> +		break;
> +
> +	case NCT6694_CAN_EVT_ACK_ERROR:
> +		cf->data[3] = CAN_ERR_PROT_LOC_ACK;
> +		break;
> +
> +	case NCT6694_CAN_EVT_FORM_ERROR:
> +		cf->data[2] |= CAN_ERR_PROT_FORM;
> +		break;
> +
> +	case NCT6694_CAN_EVT_BIT_ERROR:
> +		cf->data[2] |= CAN_ERR_PROT_BIT |
> +			       CAN_ERR_PROT_BIT0 |
> +			       CAN_ERR_PROT_BIT1;
> +		break;
> +
> +	case NCT6694_CAN_EVT_TIMEOUT_ERROR:
> +		cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> +		break;
> +
> +	case NCT6694_CAN_EVT_UNKNOWN_ERROR:
> +		cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> +		/*
> +		 * It means 'unspecified'(the value is '0').
> +		 * But it is not sure if it's ok to send an error package
> +		 * without specific error bit.
> +		 */
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	/* Reset the error counter, ack the IRQ and re-enable the counter. */
> +	stats->rx_packets++;
> +	stats->rx_bytes += cf->can_dlc;
> +	netif_receive_skb(skb);
> +
> +	return 1;
> +}
> +
> +static int nct6694_can_handle_state_change(struct net_device *ndev,
> +					   enum can_state new_state)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +	struct can_berr_counter bec;
> +
> +	switch (new_state) {
> +	case CAN_STATE_ERROR_ACTIVE:
> +		priv->can.can_stats.error_warning++;
> +		priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +		break;
> +	case CAN_STATE_ERROR_WARNING:
> +		priv->can.can_stats.error_warning++;
> +		priv->can.state = CAN_STATE_ERROR_WARNING;
> +		break;
> +	case CAN_STATE_ERROR_PASSIVE:
> +		priv->can.can_stats.error_passive++;
> +		priv->can.state = CAN_STATE_ERROR_PASSIVE;
> +		break;
> +	case CAN_STATE_BUS_OFF:
> +		priv->can.state = CAN_STATE_BUS_OFF;
> +		priv->can.can_stats.bus_off++;
> +		can_bus_off(ndev);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	/* propagate the error condition to the CAN stack */
> +	skb = alloc_can_err_skb(ndev, &cf);
> +	if (unlikely(!skb))
> +		return 0;
> +
> +	nct6694_can_get_berr_counter(ndev, &bec);
> +
> +	switch (new_state) {
> +	case CAN_STATE_ERROR_WARNING:
> +		/* error warning state */
> +		cf->can_id |= CAN_ERR_CRTL;
> +		cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> +							CAN_ERR_CRTL_RX_WARNING;
> +		cf->data[6] = bec.txerr;
> +		cf->data[7] = bec.rxerr;
> +		break;
> +	case CAN_STATE_ERROR_PASSIVE:
> +		/* error passive state */
> +		cf->can_id |= CAN_ERR_CRTL;
> +		cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> +		if (bec.txerr > 127)
> +			cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> +		cf->data[6] = bec.txerr;
> +		cf->data[7] = bec.rxerr;
> +		break;
> +	case CAN_STATE_BUS_OFF:
> +		/* bus-off state */
> +		cf->can_id |= CAN_ERR_BUSOFF;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	stats->rx_packets++;
> +	stats->rx_bytes += cf->can_dlc;
> +	netif_receive_skb(skb);
> +
> +	return 1;
> +}
> +
> +static int nct6694_can_handle_state_errors(struct net_device *ndev,
> +					   unsigned char can_status)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	int work_done = 0;
> +
> +	if (can_status == NCT6694_CAN_EVT_ERROR_ACTIVE &&
> +	    priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> +		netdev_dbg(ndev, "Error, entered active state\n");
> +		work_done += nct6694_can_handle_state_change(ndev,
> +							     CAN_STATE_ERROR_ACTIVE);
> +	}
> +
> +	if (can_status == NCT6694_CAN_EVT_WARNING &&
> +	    priv->can.state != CAN_STATE_ERROR_WARNING) {
> +		netdev_dbg(ndev, "Error, entered warning state\n");
> +		work_done += nct6694_can_handle_state_change(ndev,
> +							     CAN_STATE_ERROR_WARNING);
> +	}
> +
> +	if (can_status == NCT6694_CAN_EVT_ERROR_PASSIVE &&
> +	    priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> +		netdev_dbg(ndev, "Error, entered passive state\n");
> +		work_done += nct6694_can_handle_state_change(ndev,
> +							     CAN_STATE_ERROR_PASSIVE);
> +	}
> +
> +	if (can_status == NCT6694_CAN_EVT_BUS_OFF &&
> +	    priv->can.state != CAN_STATE_BUS_OFF) {
> +		netdev_dbg(ndev, "Error, entered bus-off state\n");
> +		work_done += nct6694_can_handle_state_change(ndev,
> +							     CAN_STATE_BUS_OFF);
> +	}
> +
> +	return work_done;
> +}
> +
> +static int nct6694_can_poll(struct net_device *ndev, int quota)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> +	int can_idx = priv->can_idx;
> +	int work_done = 0, ret;
> +	u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
> +	u8 bus_err, can_status;
> +
> +	scoped_guard(mutex, &priv->lock) {
> +		ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +				       NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
> +				       sizeof(struct nct6694_can_event), evt);
> +		if (ret < 0)
> +			return IRQ_NONE;

propagate the error

> +
> +		if (can_idx) {
> +			bus_err = evt->err2;
> +			can_status = evt->status2;
> +		} else {
> +			bus_err = evt->err1;
> +			can_status = evt->status1;
> +		}
> +	}
> +
> +	/* Handle bus state changes */
> +	work_done += nct6694_can_handle_state_errors(ndev, can_status);
> +
> +	/* Handle lec errors on the bus */
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> +		work_done += nct6694_can_handle_lec_err(ndev, bus_err);
> +
> +	/* Handle RX events */
> +	work_done += nct6694_can_do_rx_poll(ndev, quota - work_done);
> +	return work_done;
> +}
> +
> +static void nct6694_can_tx_irq(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +
> +	guard(mutex)(&priv->lock);
> +	stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
> +	stats->tx_packets++;
> +	priv->tx_busy = false;
> +	netif_wake_queue(ndev);
> +}
> +
> +static irqreturn_t nct6694_can_irq(int irq, void *data)
> +{
> +	struct net_device *ndev = data;
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> +	int can_idx = priv->can_idx;
> +	int ret;
> +	u8 mask_sts = NCT6694_CAN_EVENT_TX_EVT;
> +	u8 tx_evt;
> +
> +	scoped_guard(mutex, &priv->lock) {
> +		ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +				       NCT6694_CAN_CMD2_OFFSET(can_idx, mask_sts),
> +				       sizeof(struct nct6694_can_event), evt);
> +		if (ret < 0)
> +			return IRQ_NONE;
> +
> +		tx_evt = can_idx ? evt->tx_evt2 : evt->tx_evt1;
> +	}
> +
> +	if (tx_evt) {
> +		nct6694_can_tx_irq(ndev);
> +	} else {
> +		ret = nct6694_can_poll(ndev, NCT6694_CAN_RX_QUOTA);
> +		if (!ret)
> +			return IRQ_NONE;
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int nct6694_can_start(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_setting *setting = (struct nct6694_can_setting *)priv->tx_buf;
> +	const struct can_bittiming *n_bt = &priv->can.bittiming;
> +	const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> +	int ret;
> +
> +	guard(mutex)(&priv->lock);
> +
> +	memset(priv->tx_buf, 0, sizeof(struct nct6694_can_setting));
> +	setting->nbr = cpu_to_le32(n_bt->bitrate);
> +	setting->dbr = cpu_to_le32(d_bt->bitrate);
> +
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_MON);
> +
> +	if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
> +	    priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_NISO);
> +
> +	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> +		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_LBCK);
> +
> +	ret = nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> +				NCT6694_CAN_CMD0_OFFSET(priv->can_idx),
> +				sizeof(struct nct6694_can_setting), setting);
> +	if (ret < 0)
> +		return ret;
> +
> +	priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> +	return 0;
> +}
> +
> +static int nct6694_can_stop(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +	netif_stop_queue(ndev);
> +	free_irq(ndev->irq, ndev);
> +	destroy_workqueue(priv->wq);
> +	priv->wq = NULL;
> +	nct6694_can_clean(ndev);
> +	priv->can.state = CAN_STATE_STOPPED;
> +	close_candev(ndev);
> +
> +	return 0;
> +}
> +
> +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> +	switch (mode) {
> +	case CAN_MODE_START:
> +		nct6694_can_clean(ndev);
> +		nct6694_can_start(ndev);
> +		netif_wake_queue(ndev);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static int nct6694_can_open(struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	int ret;
> +
> +	ret = open_candev(ndev);
> +	if (ret)
> +		return ret;
> +
> +	ret = request_threaded_irq(ndev->irq, NULL,
> +				   nct6694_can_irq, IRQF_ONESHOT,
> +				   "nct6694_can", 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;
> +	}
> +
> +	priv->tx_skb = NULL;
> +	priv->tx_busy = false;
> +
> +	ret = nct6694_can_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:
> +	close_candev(ndev);
> +	return ret;
> +}
> +
> +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> +					  struct net_device *ndev)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +	if (priv->tx_skb || priv->tx_busy) {
> +		netdev_err(ndev, "hard_xmit called while tx busy\n");
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	if (can_dev_dropped_skb(ndev, skb))
> +		return NETDEV_TX_OK;

please drop first

> +
> +	netif_stop_queue(ndev);
> +	priv->tx_skb = skb;
> +	queue_work(priv->wq, &priv->tx_work);
> +
> +	return NETDEV_TX_OK;
> +}
> +
> +static void nct6694_can_tx(struct net_device *ndev, struct canfd_frame *cf)
> +{
> +	struct nct6694_can_priv *priv = netdev_priv(ndev);
> +	struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->tx_buf;
> +	u32 txid = 0;
> +
> +	memset(xmit, 0, sizeof(struct nct6694_can_xmit));
> +
> +	if (priv->can_idx == 0)
> +		xmit->tag = NCT6694_CAN_TAG_CAN0<;
> +	else
> +		xmit->tag = NCT6694_CAN_TAG_CAN1;
> +
> +	if (cf->can_id & CAN_EFF_FLAG) {
> +		txid = cf->can_id & CAN_EFF_MASK;
> +		/*
> +		 * In case the Extended ID frame is transmitted, the
> +		 * standard and extended part of the ID are swapped
> +		 * in the register, so swap them back to send the
> +		 * correct ID.

You comment doesn't match the code.

> +		 */
> +		xmit->flag |= NCT6694_CAN_FLAG_EFF;
> +	} else {
> +		txid = cf->can_id & CAN_SFF_MASK;
> +	}
> +
> +	xmit->id = cpu_to_le32(txid);
> +	xmit->dlc = cf->len;
> +
> +	if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&

No need to check ctrlmode

> +	    can_is_canfd_skb(priv->tx_skb)) {
> +		xmit->flag |= NCT6694_CAN_FLAG_FD;
> +		if (cf->flags & CANFD_BRS)
> +			xmit->flag |= NCT6694_CAN_FLAG_BRS;
> +	}
> +
> +	if (cf->can_id & CAN_RTR_FLAG)
> +		xmit->flag |= NCT6694_CAN_FLAG_RTR;

you can move this into the !can_is_canfd_skb branch of the if

> +
> +	memcpy(xmit->data, cf->data, cf->len);
> +
> +	nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> +			  NCT6694_CAN_CMD10_OFFSET(1),
> +			  sizeof(struct nct6694_can_xmit),
> +			  xmit);
> +}
> +
> +static void nct6694_can_tx_work(struct work_struct *work)
> +{
> +	struct nct6694_can_priv *priv = container_of(work,
> +						     struct nct6694_can_priv,
> +						     tx_work);
> +	struct net_device *ndev = priv->ndev;
> +	struct canfd_frame *cf;
> +
> +	guard(mutex)(&priv->lock);
> +
> +	if (priv->tx_skb) {
> +		if (priv->can.state == CAN_STATE_BUS_OFF) {
> +			nct6694_can_clean(ndev);
> +		} else {
> +			cf = (struct canfd_frame *)priv->tx_skb->data;
> +			nct6694_can_tx(ndev, cf);
> +			priv->tx_busy = true;
> +			can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
> +			priv->tx_skb = NULL;
> +		}
> +	}
> +}
> +
> +static const struct net_device_ops nct6694_can_netdev_ops = {
> +	.ndo_open = nct6694_can_open,
> +	.ndo_stop = nct6694_can_stop,
> +	.ndo_start_xmit = nct6694_can_start_xmit,
> +	.ndo_change_mtu = can_change_mtu,
> +};
> +
> +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> +	.get_ts_info = ethtool_op_get_ts_info,
> +};
> +
> +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> +{
> +	struct nct6694_can_information *info = (struct nct6694_can_information *)priv->rx_buf;
> +	int ret;
> +
> +	ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> +			       NCT6694_CAN_CMD1_OFFSET,
> +			       sizeof(struct nct6694_can_information),
> +			       info);
> +	if (ret)
> +		return ret;
> +
> +	return le32_to_cpu(info->can_clk);
> +}
> +
> +static int nct6694_can_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_can_priv *priv;
> +	struct net_device *ndev;
> +	int ret, irq, can_clk;
> +
> +	irq = irq_create_mapping(nct6694->domain,
> +				 NCT6694_IRQ_CAN1 + cell->id);
> +	if (!irq)
> +		return -EINVAL;

propagate error value

> +
> +	ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> +	if (!ndev)
> +		return -ENOMEM;
> +
> +	ndev->irq = irq;
> +	ndev->flags |= IFF_ECHO;
> +	ndev->netdev_ops = &nct6694_can_netdev_ops;
> +	ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> +
> +	priv = netdev_priv(ndev);
> +	priv->nct6694 = nct6694;
> +	priv->ndev = ndev;
> +
> +	priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> +				    sizeof(unsigned char), GFP_KERNEL);

devm_kzalloc()
> +	if (!priv->tx_buf) {
> +		ret = -ENOMEM;
> +		goto free_candev;
> +	}
> +
> +	priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> +				    sizeof(unsigned char), GFP_KERNEL);
devm_kzalloc()

> +	if (!priv->rx_buf) {
> +		ret = -ENOMEM;
> +		goto free_candev;
> +	}
> +
> +	can_clk = nct6694_can_get_clock(priv);
> +	if (can_clk < 0) {
> +		ret = -EIO;

propagate the error value, don't overwrite it

move the dev_err_probe() here.

> +		goto free_candev;
> +	}
> +
> +	mutex_init(&priv->lock);
> +	INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
> +
> +	priv->can_idx = cell->id;
> +	priv->can.state = CAN_STATE_STOPPED;
> +	priv->can.clock.freq = can_clk;
> +	priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> +	priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> +	priv->can.do_set_mode = nct6694_can_set_mode;
> +	priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> +
> +	priv->can.ctrlmode = CAN_CTRLMODE_FD;
> +
> +	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK		|
> +				       CAN_CTRLMODE_LISTENONLY		|
> +				       CAN_CTRLMODE_FD			|
> +				       CAN_CTRLMODE_FD_NON_ISO		|
> +				       CAN_CTRLMODE_BERR_REPORTING;
> +
> +	platform_set_drvdata(pdev, priv);
> +	SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> +
> +	ret = register_candev(priv->ndev);
> +	if (ret)
> +		goto free_candev;
> +
> +	return 0;
> +
> +free_candev:
> +	free_candev(ndev);
> +	return dev_err_probe(&pdev->dev, ret, "Probe failed\n");

Move the dev_err_probe() with an appropriate error message to where the
error occurs. If malloc fails, the kernel already prints for you, so
here it's only nct6694_can_get_clock() only.

> +}
> +
> +static void nct6694_can_remove(struct platform_device *pdev)
> +{
> +	struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> +
> +	cancel_work_sync(&priv->tx_work);
> +	unregister_candev(priv->ndev);
> +	free_candev(priv->ndev);
> +}
> +
> +static struct platform_driver nct6694_can_driver = {
> +	.driver = {
> +		.name	= DRVNAME,
> +	},
> +	.probe		= nct6694_can_probe,
> +	.remove		= nct6694_can_remove,
> +};
> +
> +module_platform_driver(nct6694_can_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-can");
> -- 
> 2.34.1
> 
> 
> 

regards,
Marc

-- 
Pengutronix e.K.                 | Marc Kleine-Budde          |
Embedded Linux                   | https://www.pengutronix.de |
Vertretung Nürnberg              | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax:   +49-5121-206917-9   |

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-11  9:59   ` Marc Kleine-Budde
@ 2024-12-11 15:25     ` Vincent Mailhol
  2024-12-16  6:58       ` Ming Yu
  2024-12-16  6:21     ` Ming Yu
  1 sibling, 1 reply; 39+ messages in thread
From: Vincent Mailhol @ 2024-12-11 15:25 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

On Wed. 11 Dec. 2024 at 18:59, Marc Kleine-Budde <mkl@pengutronix.de> wrote:
> On 10.12.2024 18:45:21, Ming Yu wrote:
> > This driver supports Socket CANfd functionality for NCT6694 MFD
> > device based on USB interface.
>
> Please use the rx-offload helper, otherwise the CAN frames might be
> revived out of order.
>
> >
> > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> > ---
> >  MAINTAINERS                     |   1 +
> >  drivers/net/can/Kconfig         |  10 +
> >  drivers/net/can/Makefile        |   1 +
> >  drivers/net/can/nct6694_canfd.c | 920 ++++++++++++++++++++++++++++++++
> >  4 files changed, 932 insertions(+)
> >  create mode 100644 drivers/net/can/nct6694_canfd.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index a190f2b08fa3..eb5d46825e71 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16548,6 +16548,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/nct6694_canfd.c
> >  F:   include/linux/mfd/nct6694.h
> >
> >  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> > diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
> > index cf989bea9aa3..130e98ec28a5 100644
> > --- a/drivers/net/can/Kconfig
> > +++ b/drivers/net/can/Kconfig
> > @@ -200,6 +200,16 @@ config CAN_SUN4I
> >         To compile this driver as a module, choose M here: the module will
> >         be called sun4i_can.
> >
> > +config CAN_NCT6694
> > +     tristate "Nuvoton NCT6694 Socket CANfd support"
> > +     depends on MFD_NCT6694
> > +     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_TI_HECC
> >       depends on ARM
> >       tristate "TI High End CAN Controller"
> > diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> > index a71db2cfe990..4a6b5b9d6c2b 100644
> > --- a/drivers/net/can/Makefile
> > +++ b/drivers/net/can/Makefile
> > @@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3)        += janz-ican3.o
> >  obj-$(CONFIG_CAN_KVASER_PCIEFD)      += kvaser_pciefd.o
> >  obj-$(CONFIG_CAN_MSCAN)              += mscan/
> >  obj-$(CONFIG_CAN_M_CAN)              += m_can/
> > +obj-$(CONFIG_CAN_NCT6694)    += nct6694_canfd.o
> >  obj-$(CONFIG_CAN_PEAK_PCIEFD)        += peak_canfd/
> >  obj-$(CONFIG_CAN_SJA1000)    += sja1000/
> >  obj-$(CONFIG_CAN_SUN4I)              += sun4i_can.o
> > diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c
> > new file mode 100644
> > index 000000000000..54f20f0681e2
> > --- /dev/null
> > +++ b/drivers/net/can/nct6694_canfd.c
> > @@ -0,0 +1,920 @@
> > +// 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/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 DRVNAME "nct6694-can"
> > +
> > +/* Host interface */
> > +#define NCT6694_CAN_MOD                      0x05
> > +
> > +/* Message Channel*/
> > +/* Command 00h */

Instead of this comment, explain what the command 00h does.

Also better to give a memorable name instead of CMD0. For example:
CMD_TX, CMD_RX…

If possible, make it match the structure names.

> > +#define NCT6694_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000)
> > +#define NCT6694_CAN_CTRL1_MON                BIT(0)
> > +#define NCT6694_CAN_CTRL1_NISO               BIT(1)
> > +#define NCT6694_CAN_CTRL1_LBCK               BIT(2)
> > +
> > +/* Command 01h */
> > +#define NCT6694_CAN_CMD1_OFFSET              0x0001
> > +
> > +/* Command 02h */
> > +#define NCT6694_CAN_CMD2_OFFSET(idx, mask)                   \
> > +     ({ typeof(mask) mask_ = (mask);                         \
> > +        idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) :        \
> > +              ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
> > +
> > +#define NCT6694_CAN_EVENT_ERR                BIT(0)
> > +#define NCT6694_CAN_EVENT_STATUS     BIT(1)
> > +#define NCT6694_CAN_EVENT_TX_EVT     BIT(2)
> > +#define NCT6694_CAN_EVENT_RX_EVT     BIT(3)
> > +#define NCT6694_CAN_EVENT_REC                BIT(4)
> > +#define NCT6694_CAN_EVENT_TEC                BIT(5)
> > +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY        BIT(7)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_DATA_LOST BIT(5)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_HALF_FULL BIT(6)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_DATA_IN   BIT(7)
> > +
> > +/* Command 10h */
> > +#define NCT6694_CAN_CMD10_OFFSET(buf_cnt)    \
> > +     (((buf_cnt) & 0xFF) << 8 | 0x10)
> > +#define NCT6694_CAN_TAG_CAN0         0xC0
> > +#define NCT6694_CAN_TAG_CAN1         0xC1
> > +#define NCT6694_CAN_FLAG_EFF         BIT(0)
> > +#define NCT6694_CAN_FLAG_RTR         BIT(1)
> > +#define NCT6694_CAN_FLAG_FD          BIT(2)
> > +#define NCT6694_CAN_FLAG_BRS         BIT(3)
> > +#define NCT6694_CAN_FLAG_ERR         BIT(4)
> > +
> > +/* Command 11h */
> > +#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt)                       \
> > +     ({ typeof(buf_cnt) buf_cnt_ = (buf_cnt);                \
> > +        idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) :     \
> > +              ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })

Simplify this. Do something like:

  #define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt) \
          (idx ? 0x80 : 0x00) | \
          (buf_cnt & 0xFF)) << 8 | 0x11) \

(apply this also to NCT6694_CAN_CMD2_OFFSET())

> > +#define NCT6694_CAN_RX_QUOTA         64
> > +
> > +enum nct6694_event_err {
> > +     NCT6694_CAN_EVT_NO_ERROR,
>                       ^^^ add _ERR_
> > +     NCT6694_CAN_EVT_CRC_ERROR,
> > +     NCT6694_CAN_EVT_STUFF_ERROR,
> > +     NCT6694_CAN_EVT_ACK_ERROR,
> > +     NCT6694_CAN_EVT_FORM_ERROR,
> > +     NCT6694_CAN_EVT_BIT_ERROR,
> > +     NCT6694_CAN_EVT_TIMEOUT_ERROR,
> > +     NCT6694_CAN_EVT_UNKNOWN_ERROR,
> > +};
> > +
> > +enum nct6694_event_status {
> > +     NCT6694_CAN_EVT_ERROR_ACTIVE,
>                       ^^^ add _STATUS_
> > +     NCT6694_CAN_EVT_ERROR_PASSIVE,
> > +     NCT6694_CAN_EVT_BUS_OFF,
> > +     NCT6694_CAN_EVT_WARNING,
> > +};
> > +
> > +struct __packed nct6694_can_setting {
> > +     __le32 nbr;
> > +     __le32 dbr;
> > +     u8 active;
> > +     u8 reserved[3];
> > +     __le16 ctrl1;
> > +     __le16 ctrl2;
> > +     __le32 nbtp;
> > +     __le32 dbtp;
> > +};
> > +
> > +struct __packed nct6694_can_information {
> > +     u8 tx_fifo_cnt;
> > +     u8 rx_fifo_cnt;
> > +     __le16 reserved;
> u8 reserved[2];
> > +     __le32 can_clk;
> > +};
> > +
> > +struct __packed nct6694_can_event {
> > +     u8 err1;
> > +     u8 status1;
> > +     u8 tx_evt1;
> > +     u8 rx_evt1;
> > +     u8 rec1;
> > +     u8 tec1;
> > +     u8 reserved1[2];
> > +     u8 err2;
> > +     u8 status2;
> > +     u8 tx_evt2;
> > +     u8 rx_evt2;
> > +     u8 rec2;
> > +     u8 tec2;
> > +     u8 reserved2[2];
> > +};
>
> Create an extra struct that only describes a channel
>
> struct __packed nct6694_can_event_channel {
>         u8 err;
>         u8 status;
>         u8 tx_evt;
>         u8 rx_evt;
>         u8 rec;
>         u8 tec;
>         u8 reserved[2];
> }
>
> and put an array of 2 into struct __packed nct6694_can_event.
>
> > +
> > +struct __packed nct6694_can_xmit {

Is this struct for both TX and RX? If so, name it something like

  struct nct6694_can_frame

The term xmit is only used for transmission, not for reception.

> > +     u8 tag;
> > +     u8 flag;
> > +     u8 reserved;
> > +     u8 dlc;
> > +     __le32 id;
> > +     u8 data[64];
> > +     u8 msg_buf[72];
>
> Why is the message so long? What's in the msg_buf?
>
> > +};
> > +
> > +struct nct6694_can_priv {
> > +     struct can_priv can;    /* must be the first member */
> > +     struct net_device *ndev;
> > +     struct nct6694 *nct6694;
> > +     struct mutex lock;
>
> What does lock protect?

+1

mutexes are good if you want to keep the lock for a long time.

For short period, spinlock are more performant:

          spinlock_t lock;

> > +     struct sk_buff *tx_skb;
> > +     struct workqueue_struct *wq;
> > +     struct work_struct tx_work;
> > +     unsigned char *tx_buf;
> void *
> > +     unsigned char *rx_buf;
> void *

Rather than void*, tx_buf and rx_buf can be replaced by an union:

  union nct6694_can_rx {
          struct nct6694_can_event event;
          struct nct6694_can_xmit xmit;
          struct nct6694_can_information info;
  };

(same for nct6694_can_tx)

Then in struct nct6694_can_priv, you will just have:

  union nct6694_can_tx tx;
  union nct6694_can_rx rx;

With this,

  NCT6694_MAX_PACKET_SZ

can most likely be replaced by sizeof(union nct6694_can_rx) or
sizeof(union nct6694_can_tx).

> > +     unsigned char can_idx;
> > +     bool tx_busy;
>
> IMHO it makes no sense to have tx_skb and tx_busy
>
> > +};
> > +
> > +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> > +     .name = DRVNAME,
> > +     .tseg1_min = 2,
> > +     .tseg1_max = 256,
> > +     .tseg2_min = 2,
> > +     .tseg2_max = 128,
> > +     .sjw_max = 128,
> > +     .brp_min = 1,
> > +     .brp_max = 511,
> > +     .brp_inc = 1,
> > +};
> > +
> > +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> > +     .name = DRVNAME,
> > +     .tseg1_min = 1,
> > +     .tseg1_max = 32,
> > +     .tseg2_min = 1,
> > +     .tseg2_max = 16,
> > +     .sjw_max = 16,
> > +     .brp_min = 1,
> > +     .brp_max = 31,
> > +     .brp_inc = 1,
> > +};
> > +
> > +static void nct6694_can_clean(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > +     if (priv->tx_skb || priv->tx_busy)
> > +             ndev->stats.tx_errors++;
> > +     dev_kfree_skb(priv->tx_skb);
> > +     if (priv->tx_busy)
> > +             can_free_echo_skb(priv->ndev, 0, NULL);
> > +     priv->tx_skb = NULL;
> > +     priv->tx_busy = false;
> > +}
> > +
> > +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> > +                                     struct can_berr_counter *bec)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> > +     u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
> > +     int ret;
> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                            NCT6694_CAN_CMD2_OFFSET(priv->can_idx, mask),
> > +                            sizeof(struct nct6694_can_event),
> > +                            evt);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     bec->rxerr = priv->can_idx ? evt->rec2 : evt->rec1;
> > +     bec->txerr = priv->can_idx ? evt->tec2 : evt->tec1;
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct6694_can_handle_lost_msg(struct net_device *ndev)
> > +{
> > +     struct net_device_stats *stats = &ndev->stats;
> > +     struct sk_buff *skb;
> > +     struct can_frame *frame;
> > +
> > +     netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
> > +
> > +     stats->rx_errors++;
> > +     stats->rx_over_errors++;
> > +
> > +     skb = alloc_can_err_skb(ndev, &frame);
> > +     if (unlikely(!skb))
> > +             return 0;
> > +
> > +     frame->can_id |= CAN_ERR_CRTL;
> > +     frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> > +
> > +     netif_receive_skb(skb);
> > +
> > +     return 1;
> > +}
> > +
> > +static void nct6694_can_read_fifo(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->rx_buf;
> > +     struct net_device_stats *stats = &ndev->stats;
> > +     struct canfd_frame *cf;
> > +     struct sk_buff *skb;
> > +     int can_idx = priv->can_idx;
> > +     u32 id;
> > +     int ret;
> > +     u8 fd_format = 0;
> bool - no need to init
> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                            NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
> > +                            sizeof(struct nct6694_can_xmit), xmit);
> > +     if (ret < 0)
> > +             return;
> > +
> > +     /* Check type of frame and create skb */
> > +     fd_format = xmit->flag & NCT6694_CAN_FLAG_FD;
> > +     if (fd_format)
> > +             skb = alloc_canfd_skb(ndev, &cf);
> > +     else
> > +             skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> > +
> > +     if (!skb) {
> > +             stats->rx_dropped++;
> > +             return;
> > +     }
> > +
> > +     cf->len = xmit->dlc;
>
> what does xmit->dlc contain? The DLC or the length?

+1

Also, do not trust the device data. Even if SPI attacks are less
common, make sure to sanitize this length.

  cf->len = canfd_sanitize_len(xmit->dlc);

Or

  cf->len = canfd_sanitize_len(xmit->dlc);

if xmit->dlc is in fact a DLC.

> > +
> > +     /* Get ID and set flag by its type(Standard ID format or Ext ID format) */
> > +     id = le32_to_cpu(xmit->id);
> > +     if (xmit->flag & NCT6694_CAN_FLAG_EFF) {
> > +             /*
> > +              * In case the Extended ID frame is received, the standard
> > +              * and extended part of the ID are swapped in the register,
> > +              * so swap them back to obtain the correct ID.
> > +              */
>
> You comment doesn't match the code.
>
> > +             id |= CAN_EFF_FLAG;
> > +     }
> > +
> > +     cf->can_id = id;
> > +
> > +     /* Set ESI flag */
> > +     if (xmit->flag & NCT6694_CAN_FLAG_ERR) {
> > +             cf->flags |= CANFD_ESI;
> > +             netdev_dbg(ndev, "ESI Error\n");
> > +     }
> > +
> > +     /* Set RTR and BRS */
> > +     if (!fd_format && (xmit->flag & NCT6694_CAN_FLAG_RTR)) {
> > +             cf->can_id |= CAN_RTR_FLAG;
> > +     } else {
> > +             if (xmit->flag & NCT6694_CAN_FLAG_BRS)
> > +                     cf->flags |= CANFD_BRS;
> > +
> > +             memcpy(cf->data, xmit->data, cf->len);
> > +
> > +             stats->rx_bytes += cf->len;
> > +     }
> > +
> > +     stats->rx_packets++;
> > +
> > +     netif_receive_skb(skb);
> > +}
> > +
> > +static int nct6694_can_do_rx_poll(struct net_device *ndev, int quota)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> > +     int can_idx = priv->can_idx;
> > +     u32 pkts = 0;
> > +     u8 mask_rx = NCT6694_CAN_EVENT_RX_EVT;
> > +     u8 rx_evt;
> > +
> > +     for (;;) {
> > +             scoped_guard(mutex, &priv->lock) {
> > +                     nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                                      NCT6694_CAN_CMD2_OFFSET(can_idx, mask_rx),
> > +                                      sizeof(struct nct6694_can_event), evt);
> > +
> > +                     rx_evt = can_idx ? evt->rx_evt2 : evt->rx_evt1;
> > +             }
> > +
> > +             if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST)
> > +                     nct6694_can_handle_lost_msg(ndev);
> > +
> > +             /* No data */
> > +             if ((rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) == 0)
> > +                     break;
> > +
> > +             if (quota <= 0)
> > +                     break;
> > +
> > +             nct6694_can_read_fifo(ndev);
> > +             quota--;
> > +             pkts++;
> > +     }
> > +
> > +     return pkts;
> > +}
> > +
> > +static int nct6694_can_handle_lec_err(struct net_device *ndev, u8 bus_err)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct net_device_stats *stats = &ndev->stats;
> > +     struct can_frame *cf;
> > +     struct sk_buff *skb;
> > +
> > +     if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
> > +             return 0;
> > +
> > +     priv->can.can_stats.bus_error++;
> > +     stats->rx_errors++;
> > +
> > +     /* Propagate the error condition to the CAN stack. */
> > +     skb = alloc_can_err_skb(ndev, &cf);
> > +
> > +     if (unlikely(!skb))
> > +             return 0;

Do not exit if the memory allocation fails. Instead, do the error
handling to increase the statistics.

Look at what other CAN drivers are doing.

> > +     /* Read the error counter register and check for new errors. */
> > +     cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> > +
> > +     switch (bus_err) {
> > +     case NCT6694_CAN_EVT_CRC_ERROR:
> > +             cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_STUFF_ERROR:
> > +             cf->data[2] |= CAN_ERR_PROT_STUFF;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_ACK_ERROR:
> > +             cf->data[3] = CAN_ERR_PROT_LOC_ACK;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_FORM_ERROR:
> > +             cf->data[2] |= CAN_ERR_PROT_FORM;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_BIT_ERROR:
> > +             cf->data[2] |= CAN_ERR_PROT_BIT |
> > +                            CAN_ERR_PROT_BIT0 |
> > +                            CAN_ERR_PROT_BIT1;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_TIMEOUT_ERROR:
> > +             cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> > +             break;
> > +
> > +     case NCT6694_CAN_EVT_UNKNOWN_ERROR:
> > +             cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> > +             /*
> > +              * It means 'unspecified'(the value is '0').
> > +              * But it is not sure if it's ok to send an error package
> > +              * without specific error bit.
> > +              */
> > +             break;
> > +
> > +     default:
> > +             break;
> > +     }
> > +
> > +     /* Reset the error counter, ack the IRQ and re-enable the counter. */
> > +     stats->rx_packets++;
> > +     stats->rx_bytes += cf->can_dlc;

The CAN error frames are not regular packets. Do not increase the RX
stats. Instead, increase the error stats.

> > +     netif_receive_skb(skb);
> > +
> > +     return 1;
> > +}
> > +
> > +static int nct6694_can_handle_state_change(struct net_device *ndev,
> > +                                        enum can_state new_state)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct net_device_stats *stats = &ndev->stats;
> > +     struct can_frame *cf;
> > +     struct sk_buff *skb;
> > +     struct can_berr_counter bec;
> > +
> > +     switch (new_state) {
> > +     case CAN_STATE_ERROR_ACTIVE:
> > +             priv->can.can_stats.error_warning++;
> > +             priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > +             break;
> > +     case CAN_STATE_ERROR_WARNING:
> > +             priv->can.can_stats.error_warning++;
> > +             priv->can.state = CAN_STATE_ERROR_WARNING;
> > +             break;
> > +     case CAN_STATE_ERROR_PASSIVE:
> > +             priv->can.can_stats.error_passive++;
> > +             priv->can.state = CAN_STATE_ERROR_PASSIVE;
> > +             break;
> > +     case CAN_STATE_BUS_OFF:
> > +             priv->can.state = CAN_STATE_BUS_OFF;
> > +             priv->can.can_stats.bus_off++;
> > +             can_bus_off(ndev);
> > +             break;
> > +     default:
> > +             break;
> > +     }
> > +
> > +     /* propagate the error condition to the CAN stack */
> > +     skb = alloc_can_err_skb(ndev, &cf);
> > +     if (unlikely(!skb))
> > +             return 0;

Same as above: handle the statistics even if the allocation fails.

> > +     nct6694_can_get_berr_counter(ndev, &bec);
> > +
> > +     switch (new_state) {
> > +     case CAN_STATE_ERROR_WARNING:
> > +             /* error warning state */
> > +             cf->can_id |= CAN_ERR_CRTL;
> > +             cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> > +                                                     CAN_ERR_CRTL_RX_WARNING;

Prefer an if/else here instead of the ternary operator. It is more readable.

> > +             cf->data[6] = bec.txerr;
> > +             cf->data[7] = bec.rxerr;
> > +             break;
> > +     case CAN_STATE_ERROR_PASSIVE:
> > +             /* error passive state */
> > +             cf->can_id |= CAN_ERR_CRTL;
> > +             cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> > +             if (bec.txerr > 127)
> > +                     cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> > +             cf->data[6] = bec.txerr;
> > +             cf->data[7] = bec.rxerr;
> > +             break;
> > +     case CAN_STATE_BUS_OFF:
> > +             /* bus-off state */
> > +             cf->can_id |= CAN_ERR_BUSOFF;
> > +             break;
> > +     default:
> > +             break;
> > +     }
> > +
> > +     stats->rx_packets++;
> > +     stats->rx_bytes += cf->can_dlc;

Do not increase the RX stats when you receive a CAN event.

> > +     netif_receive_skb(skb);
> > +
> > +     return 1;
> > +}
> > +
> > +static int nct6694_can_handle_state_errors(struct net_device *ndev,
> > +                                        unsigned char can_status)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     int work_done = 0;
> > +
> > +     if (can_status == NCT6694_CAN_EVT_ERROR_ACTIVE &&
> > +         priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> > +             netdev_dbg(ndev, "Error, entered active state\n");
> > +             work_done += nct6694_can_handle_state_change(ndev,
> > +                                                          CAN_STATE_ERROR_ACTIVE);
> > +     }
> > +
> > +     if (can_status == NCT6694_CAN_EVT_WARNING &&
> > +         priv->can.state != CAN_STATE_ERROR_WARNING) {
> > +             netdev_dbg(ndev, "Error, entered warning state\n");
> > +             work_done += nct6694_can_handle_state_change(ndev,
> > +                                                          CAN_STATE_ERROR_WARNING);
> > +     }
> > +
> > +     if (can_status == NCT6694_CAN_EVT_ERROR_PASSIVE &&
> > +         priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> > +             netdev_dbg(ndev, "Error, entered passive state\n");
> > +             work_done += nct6694_can_handle_state_change(ndev,
> > +                                                          CAN_STATE_ERROR_PASSIVE);
> > +     }
> > +
> > +     if (can_status == NCT6694_CAN_EVT_BUS_OFF &&
> > +         priv->can.state != CAN_STATE_BUS_OFF) {
> > +             netdev_dbg(ndev, "Error, entered bus-off state\n");
> > +             work_done += nct6694_can_handle_state_change(ndev,
> > +                                                          CAN_STATE_BUS_OFF);
> > +     }
> > +
> > +     return work_done;
> > +}
> > +
> > +static int nct6694_can_poll(struct net_device *ndev, int quota)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> > +     int can_idx = priv->can_idx;
> > +     int work_done = 0, ret;
> > +     u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
> > +     u8 bus_err, can_status;
> > +
> > +     scoped_guard(mutex, &priv->lock) {
> > +             ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                                    NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
> > +                                    sizeof(struct nct6694_can_event), evt);
> > +             if (ret < 0)
> > +                     return IRQ_NONE;
>
> propagate the error
>
> > +
> > +             if (can_idx) {
> > +                     bus_err = evt->err2;
> > +                     can_status = evt->status2;
> > +             } else {
> > +                     bus_err = evt->err1;
> > +                     can_status = evt->status1;
> > +             }
> > +     }
> > +
> > +     /* Handle bus state changes */
> > +     work_done += nct6694_can_handle_state_errors(ndev, can_status);
> > +
> > +     /* Handle lec errors on the bus */
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> > +             work_done += nct6694_can_handle_lec_err(ndev, bus_err);
> > +
> > +     /* Handle RX events */
> > +     work_done += nct6694_can_do_rx_poll(ndev, quota - work_done);
> > +     return work_done;
> > +}
> > +
> > +static void nct6694_can_tx_irq(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct net_device_stats *stats = &ndev->stats;
> > +
> > +     guard(mutex)(&priv->lock);
> > +     stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
> > +     stats->tx_packets++;
> > +     priv->tx_busy = false;
> > +     netif_wake_queue(ndev);
> > +}
> > +
> > +static irqreturn_t nct6694_can_irq(int irq, void *data)
> > +{
> > +     struct net_device *ndev = data;
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> > +     int can_idx = priv->can_idx;
> > +     int ret;
> > +     u8 mask_sts = NCT6694_CAN_EVENT_TX_EVT;
> > +     u8 tx_evt;
> > +
> > +     scoped_guard(mutex, &priv->lock) {
> > +             ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                                    NCT6694_CAN_CMD2_OFFSET(can_idx, mask_sts),
> > +                                    sizeof(struct nct6694_can_event), evt);
> > +             if (ret < 0)
> > +                     return IRQ_NONE;
> > +
> > +             tx_evt = can_idx ? evt->tx_evt2 : evt->tx_evt1;
> > +     }
> > +
> > +     if (tx_evt) {
> > +             nct6694_can_tx_irq(ndev);
> > +     } else {
> > +             ret = nct6694_can_poll(ndev, NCT6694_CAN_RX_QUOTA);
> > +             if (!ret)
> > +                     return IRQ_NONE;
> > +     }
> > +
> > +     return IRQ_HANDLED;
> > +}
> > +
> > +static int nct6694_can_start(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_setting *setting = (struct nct6694_can_setting *)priv->tx_buf;
> > +     const struct can_bittiming *n_bt = &priv->can.bittiming;
> > +     const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > +     int ret;
> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     memset(priv->tx_buf, 0, sizeof(struct nct6694_can_setting));

When you memset(), use this pattern:

          memset(setting, 0, sizeof(*setting));

Apply this throughout your driver.

> > +     setting->nbr = cpu_to_le32(n_bt->bitrate);
> > +     setting->dbr = cpu_to_le32(d_bt->bitrate);
> > +
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_MON);
> > +
> > +     if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
> > +         priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_NISO);
> > +
> > +     if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> > +             setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_CTRL1_LBCK);
> > +
> > +     ret = nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                             NCT6694_CAN_CMD0_OFFSET(priv->can_idx),
> > +                             sizeof(struct nct6694_can_setting), setting);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct6694_can_stop(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > +     netif_stop_queue(ndev);
> > +     free_irq(ndev->irq, ndev);
> > +     destroy_workqueue(priv->wq);
> > +     priv->wq = NULL;
> > +     nct6694_can_clean(ndev);
> > +     priv->can.state = CAN_STATE_STOPPED;
> > +     close_candev(ndev);
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> > +{
> > +     switch (mode) {
> > +     case CAN_MODE_START:
> > +             nct6694_can_clean(ndev);
> > +             nct6694_can_start(ndev);
> > +             netif_wake_queue(ndev);
> > +             break;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int nct6694_can_open(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     int ret;
> > +
> > +     ret = open_candev(ndev);
> > +     if (ret)
> > +             return ret;
> > +
> > +     ret = request_threaded_irq(ndev->irq, NULL,
> > +                                nct6694_can_irq, IRQF_ONESHOT,
> > +                                "nct6694_can", 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;
> > +     }
> > +
> > +     priv->tx_skb = NULL;
> > +     priv->tx_busy = false;
> > +
> > +     ret = nct6694_can_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:
> > +     close_candev(ndev);
> > +     return ret;
> > +}
> > +
> > +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> > +                                       struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > +     if (priv->tx_skb || priv->tx_busy) {
> > +             netdev_err(ndev, "hard_xmit called while tx busy\n");
> > +             return NETDEV_TX_BUSY;
> > +     }
> > +
> > +     if (can_dev_dropped_skb(ndev, skb))
> > +             return NETDEV_TX_OK;
>
> please drop first
>
> > +
> > +     netif_stop_queue(ndev);
> > +     priv->tx_skb = skb;
> > +     queue_work(priv->wq, &priv->tx_work);
> > +
> > +     return NETDEV_TX_OK;
> > +}
> > +
> > +static void nct6694_can_tx(struct net_device *ndev, struct canfd_frame *cf)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->tx_buf;
> > +     u32 txid = 0;
> > +
> > +     memset(xmit, 0, sizeof(struct nct6694_can_xmit));
> > +
> > +     if (priv->can_idx == 0)
> > +             xmit->tag = NCT6694_CAN_TAG_CAN0<;
> > +     else
> > +             xmit->tag = NCT6694_CAN_TAG_CAN1;
> > +
> > +     if (cf->can_id & CAN_EFF_FLAG) {
> > +             txid = cf->can_id & CAN_EFF_MASK;
> > +             /*
> > +              * In case the Extended ID frame is transmitted, the
> > +              * standard and extended part of the ID are swapped
> > +              * in the register, so swap them back to send the
> > +              * correct ID.
>
> You comment doesn't match the code.
>
> > +              */
> > +             xmit->flag |= NCT6694_CAN_FLAG_EFF;
> > +     } else {
> > +             txid = cf->can_id & CAN_SFF_MASK;
> > +     }
> > +
> > +     xmit->id = cpu_to_le32(txid);
> > +     xmit->dlc = cf->len;
> > +
> > +     if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
>
> No need to check ctrlmode
>
> > +         can_is_canfd_skb(priv->tx_skb)) {
> > +             xmit->flag |= NCT6694_CAN_FLAG_FD;
> > +             if (cf->flags & CANFD_BRS)
> > +                     xmit->flag |= NCT6694_CAN_FLAG_BRS;
> > +     }
> > +
> > +     if (cf->can_id & CAN_RTR_FLAG)
> > +             xmit->flag |= NCT6694_CAN_FLAG_RTR;
>
> you can move this into the !can_is_canfd_skb branch of the if
>
> > +
> > +     memcpy(xmit->data, cf->data, cf->len);
> > +
> > +     nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                       NCT6694_CAN_CMD10_OFFSET(1),
> > +                       sizeof(struct nct6694_can_xmit),
> > +                       xmit);
> > +}
> > +
> > +static void nct6694_can_tx_work(struct work_struct *work)
> > +{
> > +     struct nct6694_can_priv *priv = container_of(work,
> > +                                                  struct nct6694_can_priv,
> > +                                                  tx_work);
> > +     struct net_device *ndev = priv->ndev;
> > +     struct canfd_frame *cf;
> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     if (priv->tx_skb) {
> > +             if (priv->can.state == CAN_STATE_BUS_OFF) {
> > +                     nct6694_can_clean(ndev);
> > +             } else {
> > +                     cf = (struct canfd_frame *)priv->tx_skb->data;
> > +                     nct6694_can_tx(ndev, cf);
> > +                     priv->tx_busy = true;
> > +                     can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
> > +                     priv->tx_skb = NULL;
> > +             }
> > +     }
> > +}
> > +
> > +static const struct net_device_ops nct6694_can_netdev_ops = {
> > +     .ndo_open = nct6694_can_open,
> > +     .ndo_stop = nct6694_can_stop,
> > +     .ndo_start_xmit = nct6694_can_start_xmit,
> > +     .ndo_change_mtu = can_change_mtu,
> > +};
> > +
> > +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> > +     .get_ts_info = ethtool_op_get_ts_info,
> > +};
> > +
> > +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> > +{
> > +     struct nct6694_can_information *info = (struct nct6694_can_information *)priv->rx_buf;
> > +     int ret;
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                            NCT6694_CAN_CMD1_OFFSET,
> > +                            sizeof(struct nct6694_can_information),
> > +                            info);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return le32_to_cpu(info->can_clk);
> > +}
> > +
> > +static int nct6694_can_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_can_priv *priv;
> > +     struct net_device *ndev;
> > +     int ret, irq, can_clk;
> > +
> > +     irq = irq_create_mapping(nct6694->domain,
> > +                              NCT6694_IRQ_CAN1 + cell->id);
> > +     if (!irq)
> > +             return -EINVAL;
>
> propagate error value
>
> > +
> > +     ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> > +     if (!ndev)
> > +             return -ENOMEM;
> > +
> > +     ndev->irq = irq;
> > +     ndev->flags |= IFF_ECHO;
> > +     ndev->netdev_ops = &nct6694_can_netdev_ops;
> > +     ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> > +
> > +     priv = netdev_priv(ndev);
> > +     priv->nct6694 = nct6694;
> > +     priv->ndev = ndev;
> > +
> > +     priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                 sizeof(unsigned char), GFP_KERNEL);
>
> devm_kzalloc()
> > +     if (!priv->tx_buf) {
> > +             ret = -ENOMEM;
> > +             goto free_candev;
> > +     }
> > +
> > +     priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                 sizeof(unsigned char), GFP_KERNEL);
> devm_kzalloc()
>
> > +     if (!priv->rx_buf) {
> > +             ret = -ENOMEM;
> > +             goto free_candev;
> > +     }
> > +
> > +     can_clk = nct6694_can_get_clock(priv);
> > +     if (can_clk < 0) {
> > +             ret = -EIO;
>
> propagate the error value, don't overwrite it
>
> move the dev_err_probe() here.
>
> > +             goto free_candev;
> > +     }
> > +
> > +     mutex_init(&priv->lock);
> > +     INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
> > +
> > +     priv->can_idx = cell->id;
> > +     priv->can.state = CAN_STATE_STOPPED;
> > +     priv->can.clock.freq = can_clk;
> > +     priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > +     priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > +     priv->can.do_set_mode = nct6694_can_set_mode;
> > +     priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > +
> > +     priv->can.ctrlmode = CAN_CTRLMODE_FD;
> > +
> > +     priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK            |
> > +                                    CAN_CTRLMODE_LISTENONLY          |
> > +                                    CAN_CTRLMODE_FD                  |
> > +                                    CAN_CTRLMODE_FD_NON_ISO          |
> > +                                    CAN_CTRLMODE_BERR_REPORTING;
> > +
> > +     platform_set_drvdata(pdev, priv);
> > +     SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> > +
> > +     ret = register_candev(priv->ndev);
> > +     if (ret)
> > +             goto free_candev;
> > +
> > +     return 0;
> > +
> > +free_candev:
> > +     free_candev(ndev);
> > +     return dev_err_probe(&pdev->dev, ret, "Probe failed\n");
>
> Move the dev_err_probe() with an appropriate error message to where the
> error occurs. If malloc fails, the kernel already prints for you, so
> here it's only nct6694_can_get_clock() only.
>
> > +}
> > +
> > +static void nct6694_can_remove(struct platform_device *pdev)
> > +{
> > +     struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> > +
> > +     cancel_work_sync(&priv->tx_work);
> > +     unregister_candev(priv->ndev);
> > +     free_candev(priv->ndev);
> > +}
> > +
> > +static struct platform_driver nct6694_can_driver = {
> > +     .driver = {
> > +             .name   = DRVNAME,
> > +     },
> > +     .probe          = nct6694_can_probe,
> > +     .remove         = nct6694_can_remove,
> > +};
> > +
> > +module_platform_driver(nct6694_can_driver);
> > +
> > +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:nct6694-can");


Yours sincerely,
Vincent Mailhol

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
  2024-12-10 10:57   ` Mateusz Polchlopek
  2024-12-10 14:38   ` Krzysztof Kozlowski
@ 2024-12-11 16:44   ` Christophe JAILLET
  2024-12-12  7:01     ` Ming Yu
  2024-12-20 12:45   ` Linus Walleij
  3 siblings, 1 reply; 39+ messages in thread
From: Christophe JAILLET @ 2024-12-11 16:44 UTC (permalink / raw)
  To: Ming Yu
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, tmyu0, lee, linus.walleij,
	brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem,
	edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni

Le 10/12/2024 à 11:45, Ming Yu a écrit :
> 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 implements USB device functionality and shares the
> chip's peripherals as a child device.
> 
> 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.

...

> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		     u16 length, void *buf)
> +{
> +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> +	struct nct6694_response_header *response_header = nct6694->response_header;
> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);

Nitpick: This could be moved a few lines below, should it still comply 
with your coding style.

> +
> +	/* Send command packet to USB device */
> +	cmd_header->mod = mod;
> +	cmd_header->cmd = offset & 0xFF;
> +	cmd_header->sel = (offset >> 8) & 0xFF;
> +	cmd_header->hctrl = NCT6694_HCTRL_GET;
> +	cmd_header->len = length;
> +
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
> +			   cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive response packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
> +			   nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> +			   buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
> +	if (ret)
> +		return ret;
> +
> +	return nct6694_response_err_handling(nct6694, response_header->sts);
> +}
> +EXPORT_SYMBOL(nct6694_read_msg);
> +
> +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> +		      u16 length, void *buf)
> +{
> +	struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> +	struct nct6694_response_header *response_header = nct6694->response_header;
> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);

Nitpick: This could be moved a few lines below, should it still comply 
with your coding style.

> +
> +	/* Send command packet to USB device  */

Nitpick: double space before */

> +	cmd_header->mod = mod;
> +	cmd_header->cmd = offset & 0xFF;
> +	cmd_header->sel = (offset >> 8) & 0xFF;
> +	cmd_header->hctrl = NCT6694_HCTRL_SET;
> +	cmd_header->len = length;

...

> +static struct irq_chip nct6694_irq_chip = {

const?

> +	.name = "nct6694-irq",
> +	.flags = IRQCHIP_SKIP_SET_WAKE,
> +	.irq_bus_lock = nct6694_irq_lock,
> +	.irq_bus_sync_unlock = nct6694_irq_sync_unlock,
> +	.irq_enable = nct6694_irq_enable,
> +	.irq_disable = nct6694_irq_disable,
> +};

...

> +static int nct6694_usb_probe(struct usb_interface *iface,
> +			     const struct usb_device_id *id)
> +{
> +	struct usb_device *udev = interface_to_usbdev(iface);
> +	struct device *dev = &udev->dev;
> +	struct usb_host_interface *interface;
> +	struct usb_endpoint_descriptor *int_endpoint;
> +	struct nct6694 *nct6694;
> +	struct nct6694_cmd_header *cmd_header;
> +	struct nct6694_response_header *response_header;
> +	int pipe, maxp;
> +	int ret;
> +
> +	interface = iface->cur_altsetting;
> +
> +	int_endpoint = &interface->endpoint[0].desc;
> +	if (!usb_endpoint_is_int_in(int_endpoint))
> +		return -ENODEV;
> +
> +	nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> +	if (!nct6694)
> +		return -ENOMEM;
> +
> +	pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> +	maxp = usb_maxpacket(udev, pipe);
> +
> +	cmd_header = devm_kzalloc(dev, sizeof(*cmd_header),
> +				  GFP_KERNEL);
> +	if (!cmd_header)
> +		return -ENOMEM;
> +
> +	response_header = devm_kzalloc(dev, sizeof(*response_header),
> +				       GFP_KERNEL);
> +	if (!response_header)
> +		return -ENOMEM;
> +
> +	nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
> +					   sizeof(unsigned char), GFP_KERNEL);

Why for cmd_header and response_header we use a temp variable, while 
here we update directly nct6694->int_buffer?

It would save a few LoC do remove this temp var.

> +	if (!nct6694->int_buffer)
> +		return -ENOMEM;
> +
> +	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> +	if (!nct6694->int_in_urb)
> +		return -ENOMEM;

...

CJ


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 14:38   ` Krzysztof Kozlowski
@ 2024-12-12  5:27     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-12  5:27 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Krzysztof,

Thank you for your comments,

Krzysztof Kozlowski <krzk@kernel.org> 於 2024年12月10日 週二 下午10:38寫道:
>
> > +
> > +     dev_set_drvdata(dev, nct6694);
> > +     usb_set_intfdata(iface, nct6694);
> > +
> > +     ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
> > +     if (ret)
> > +             goto err_mfd;
> > +
> > +     dev_info(dev, "Probed device: (%04X:%04X)\n", id->idVendor, id->idProduct);
>
> Drop. Duplicating existing messages and interfaces. Your driver is
> supposed to be silent on success.
>

Okay, I will drop it in v4.

> > +     return 0;
> > +
> > +err_mfd:
> > +     usb_kill_urb(nct6694->int_in_urb);
> > +err_urb:
> > +     usb_free_urb(nct6694->int_in_urb);
> > +     return dev_err_probe(dev, ret, "Probe failed\n");
>
> No, this should go to individual call causing errors so this will be
> informative. Above is not informative at all and kernel already reports
> this, so drop.
>

Okay, I will drop it in v4.

> > +}
> > +
> > +static void nct6694_usb_disconnect(struct usb_interface *iface)
> > +{
> > +     struct usb_device *udev = interface_to_usbdev(iface);
> > +     struct nct6694 *nct6694 = usb_get_intfdata(iface);
>

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support
  2024-12-10 15:22   ` Guenter Roeck
@ 2024-12-12  6:26     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-12  6:26 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Guenter,

Thank you for your comments,

Guenter Roeck <linux@roeck-us.net> 於 2024年12月10日 週二 下午11:22寫道:
>
> > +static int nct6694_wdt_probe(struct platform_device *pdev)
> > +{
> ...
> > +     wdev->timeout = timeout;
> > +     wdev->pretimeout = pretimeout;
> > +     if (timeout < pretimeout) {
> > +             dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
> > +             wdev->pretimeout = 0;
> > +     }
> > +
> > +     wdev->min_timeout = 1;
> > +     wdev->max_timeout = 255;
> > +
> > +     mutex_init(&data->lock);
> > +
> > +     platform_set_drvdata(pdev, data);
> > +
> > +     /* Register watchdog timer device to WDT framework */
> > +     watchdog_set_drvdata(&data->wdev, data);
> > +     watchdog_init_timeout(&data->wdev, timeout, dev);
>
> This is pointless since timeout is pre-initialized with a value != 0.
> That means a value provided through devicetree will never be used
> unless the user sets timeout=0 as module parameter. But then the above
> check for pretimeout is useless.
>

Understood! I will drop it in v4.

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2024-12-10 12:46   ` Bartosz Golaszewski
@ 2024-12-12  6:58     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-12  6:58 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: tmyu0, lee, linus.walleij, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Bartosz,

Thank you for your comments,

Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年12月10日 週二 下午8:46寫道:
>
> Looks much better now. Please address one more issue I just noticed.
>
> > +
> > +       mutex_init(&data->irq_lock);
>
> This is never destroyed. Please use devm_mutex_init() preferably to
> not add remove(). Also, the other mutex doesn't seem to be initialized
> at all.
>

Understood! I will address the issue and include the missing part in
the next patch.
For other drivers that use mutex_init() without destroying it should
also be changed to devm_mutex_init(), right?

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-11 16:44   ` Christophe JAILLET
@ 2024-12-12  7:01     ` Ming Yu
  2024-12-12  7:13       ` Christophe JAILLET
  0 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-12  7:01 UTC (permalink / raw)
  To: Christophe JAILLET
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, tmyu0, lee, linus.walleij,
	brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem,
	edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni

Dear Christophe,

Thank you for your comments,

Christophe JAILLET <christophe.jaillet@wanadoo.fr> 於 2024年12月12日 週四 上午12:44寫道:
>
> > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> > +                  u16 length, void *buf)
> > +{
> > +     struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> > +     struct nct6694_response_header *response_header = nct6694->response_header;
> > +     struct usb_device *udev = nct6694->udev;
> > +     int tx_len, rx_len, ret;
> > +
> > +     guard(mutex)(&nct6694->access_lock);
>
> Nitpick: This could be moved a few lines below, should it still comply
> with your coding style.
>

I think the lock should be placed here to prevent the cmd_header from
being overwritten by another caller.
Could you share your perspective on this?

> > +
> > +     /* Send command packet to USB device */
> > +     cmd_header->mod = mod;
> > +     cmd_header->cmd = offset & 0xFF;
> > +     cmd_header->sel = (offset >> 8) & 0xFF;
> > +     cmd_header->hctrl = NCT6694_HCTRL_GET;
> > +     cmd_header->len = length;
> > +
> > +     ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
> > +                        cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
> > +                        nct6694->timeout);
> > +     if (ret)
> > +             return ret;
> > +
> > +     /* Receive response packet from USB device */
> > +     ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> > +                        response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
> > +                        nct6694->timeout);
> > +     if (ret)
> > +             return ret;
> > +
> > +     ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
> > +                        buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return nct6694_response_err_handling(nct6694, response_header->sts);
> > +}
> > +EXPORT_SYMBOL(nct6694_read_msg);
> > +
> > +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> > +                   u16 length, void *buf)
> > +{
> > +     struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
> > +     struct nct6694_response_header *response_header = nct6694->response_header;
> > +     struct usb_device *udev = nct6694->udev;
> > +     int tx_len, rx_len, ret;
> > +
> > +     guard(mutex)(&nct6694->access_lock);
>
> Nitpick: This could be moved a few lines below, should it still comply
> with your coding style.
>

I think the lock should be placed here to prevent the cmd_header from
being overwritten by another caller.
Could you share your perspective on this?

> > +
> > +     /* Send command packet to USB device  */
>
> Nitpick: double space before */
>

Fix it in v4.

> > +     cmd_header->mod = mod;
> > +     cmd_header->cmd = offset & 0xFF;
> > +     cmd_header->sel = (offset >> 8) & 0xFF;
> > +     cmd_header->hctrl = NCT6694_HCTRL_SET;
> > +     cmd_header->len = length;
>
> ...
>
> > +static struct irq_chip nct6694_irq_chip = {
>
> const?
>

Fix it in v4.

> > +     .name = "nct6694-irq",
> > +     .flags = IRQCHIP_SKIP_SET_WAKE,
> > +     .irq_bus_lock = nct6694_irq_lock,
> > +     .irq_bus_sync_unlock = nct6694_irq_sync_unlock,
> > +     .irq_enable = nct6694_irq_enable,
> > +     .irq_disable = nct6694_irq_disable,
> > +};
>
> ...
>
> > +static int nct6694_usb_probe(struct usb_interface *iface,
> > +                          const struct usb_device_id *id)
> > +{
> > +     struct usb_device *udev = interface_to_usbdev(iface);
> > +     struct device *dev = &udev->dev;
> > +     struct usb_host_interface *interface;
> > +     struct usb_endpoint_descriptor *int_endpoint;
> > +     struct nct6694 *nct6694;
> > +     struct nct6694_cmd_header *cmd_header;
> > +     struct nct6694_response_header *response_header;
> > +     int pipe, maxp;
> > +     int ret;
> > +
> > +     interface = iface->cur_altsetting;
> > +
> > +     int_endpoint = &interface->endpoint[0].desc;
> > +     if (!usb_endpoint_is_int_in(int_endpoint))
> > +             return -ENODEV;
> > +
> > +     nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> > +     if (!nct6694)
> > +             return -ENOMEM;
> > +
> > +     pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> > +     maxp = usb_maxpacket(udev, pipe);
> > +
> > +     cmd_header = devm_kzalloc(dev, sizeof(*cmd_header),
> > +                               GFP_KERNEL);
> > +     if (!cmd_header)
> > +             return -ENOMEM;
> > +
> > +     response_header = devm_kzalloc(dev, sizeof(*response_header),
> > +                                    GFP_KERNEL);
> > +     if (!response_header)
> > +             return -ENOMEM;
> > +
> > +     nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
> > +                                        sizeof(unsigned char), GFP_KERNEL);
>
> Why for cmd_header and response_header we use a temp variable, while
> here we update directly nct6694->int_buffer?
>
> It would save a few LoC do remove this temp var.
>

Fix it in v4.

> > +     if (!nct6694->int_buffer)
> > +             return -ENOMEM;
> > +
> > +     nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> > +     if (!nct6694->int_in_urb)
> > +             return -ENOMEM;
>
> ...
>

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2024-12-10 15:58   ` Guenter Roeck
@ 2024-12-12  7:09     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-12  7:09 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Guenter,

Thank you for your comments,

Guenter Roeck <linux@roeck-us.net> 於 2024年12月10日 週二 下午11:58寫道:
>
> On Tue, Dec 10, 2024 at 06:45:23PM +0800, Ming Yu wrote:
> > This driver supports Hardware monitor functionality for NCT6694 MFD
> > device based on USB interface.
> >
> > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
>
> checkpatch:
>
> WARNING: From:/Signed-off-by: email address mismatch: 'From: Ming Yu <a0282524688@gmail.com>' != 'Signed-off-by: Ming Yu <tmyu0@nuvoton.com>'
>

Fix it in v4.

...

> > +static inline long in_from_reg(u8 reg)
> > +{
> > +     return reg * 16;
> > +}
> > +
> > +static inline u8 in_to_reg(long val)
> > +{
> > +     if (val <= 0)
> > +             return 0;
>
> This is pointless since the calling code already clamps the value to [0, 2032].
>

Drop it in v4.

> > +     return val / 16;
>
> DIV_ROUND_CLOSEST() ?
>

Fix it in v4.

> > +}
> > +
> > +static inline long temp_from_reg(u8 reg)
> > +{
> > +     return reg * 1000;
>
> This always returns a positive value, even though the temperature is
> supposed to be signed. More on that below.
>

I will modify the return value to (sign_extend32(reg, 7) * 1000) in v4.

> > +}
> > +
> > +static inline u8 temp_to_reg(long val)
> > +{
> > +     return val / 1000;
>
> DIV_ROUND_CLOSEST() ?
>

Fix it in v4.

> > +}
...
> > +static int nct6694_in_read(struct device *dev, u32 attr, int channel,
> > +                        long *val)
> > +{
> > +     struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > +     unsigned char vin_en;
> > +     int ret;
> > +
> > +     guard(mutex)(&data->lock);
> > +
> > +     switch (attr) {
> > +     case hwmon_in_enable:
> > +             vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
> > +             *val = !!(vin_en & BIT(channel % 8)) ? 1 : 0;
>
> Drop "? 1 : 0"
>

Drop it in v4.

> > +
> > +             return 0;
> > +     case hwmon_in_input:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> > +                                    NCT6694_VIN_IDX(channel), 1,
> > +                                    data->xmit_buf);
>
> I am curious: Since the received data length is not returned, and the
> expected minimum length is not passed to nct6694_read_msg(), the driver
> has no means to check if the received data actually includes the expected
> values at the expected index. That doesn't matter here, but some of the
> returned data is located far into the buffer. Is it indeed not necessary
> for the driver to check if the received data is actually as long as
> expected ?
>

The fourth parameter is expected length of nct6694_read_msg(), which
in this case is 1.
I will add the data length validation code for handling the
response_header in both nct6694_read_msg() and nct6694_write_msg() in
the next patch.

> > +             if (ret)
> > +                     return ret;
...
> > +static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
> > +                          long *val)
> > +{
> > +     struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > +     unsigned char temp_en, temp_hyst;
> > +     int ret, int_part, frac_part;
> > +     signed char temp_max;
> > +
> > +     guard(mutex)(&data->lock);
> > +
> > +     switch (attr) {
> > +     case hwmon_temp_enable:
> > +             temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
> > +             *val = !!(temp_en & BIT(channel % 8)) ? 1 : 0;
>
> Drop "? 1 : 0"
>

Drop it in v4.

> > +
> > +             return 0;
> > +     case hwmon_temp_input:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> > +                                    NCT6694_TIN_IDX(channel), 2,
> > +                                    data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             int_part = sign_extend32(data->xmit_buf[0], 7);
> > +             frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
> > +             if (int_part < 0)
> > +                     *val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
> > +             else
> > +                     *val = int_part * 1000 + frac_part * 125;
> > +
> > +             return 0;
> > +     case hwmon_temp_max:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > +                                    NCT6694_HWMON_CMD2_OFFSET,
> > +                                    NCT6694_HWMON_CMD2_LEN,
> > +                                    data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
> > +
>
> If the value in NCT6694_TIN_HL() is signed, this will return a large positive
> value instead.
>

Understood! I will fix it in v4.

> > +             return 0;
> > +     case hwmon_temp_max_hyst:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > +                                    NCT6694_HWMON_CMD2_OFFSET,
> > +                                    NCT6694_HWMON_CMD2_LEN,
> > +                                    data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> > +             temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
> > +                                   data->xmit_buf[NCT6694_TIN_HYST(channel)]);
> > +             if (temp_max < 0)
>
> This suggests that the temperature limit is signed, suggesting in turn that
> temp_from_reg() is wrong.
>

Understood! I will fix it in v4.

> > +                     *val = temp_from_reg(temp_max + temp_hyst);
> > +             else
> > +                     *val = temp_from_reg(temp_max - temp_hyst);
> > +
> > +             return 0;
> > +     case hwmon_temp_max_alarm:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> > +                                    NCT6694_TIN_STS(channel / 8), 1,
> > +                                        data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = !!(data->xmit_buf[0] & BIT(channel % 8));
> > +
> > +             return 0;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
> > +                         long *val)
> > +{
> > +     struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > +     unsigned char fanin_en;
> > +     int ret;
> > +
> > +     guard(mutex)(&data->lock);
> > +
> > +     switch (attr) {
> > +     case hwmon_fan_enable:
> > +             fanin_en = data->hwmon_en[NCT6694_FIN_EN(channel / 8)];
> > +             *val = !!(fanin_en & BIT(channel % 8)) ? 1 : 0;
>
> Drop "? 1 : 0"
>

Drop it in v4.

> > +
> > +             return 0;
> > +     case hwmon_fan_input:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> > +                                    NCT6694_FIN_IDX(channel), 2,
> > +                                    data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = (data->xmit_buf[1] |
> > +                    (data->xmit_buf[0] << 8)) & 0xFFFF;
>
> The "& 0xffff" is pointless.
>

Fix it in v4.

> > +
> > +             return 0;
> > +     case hwmon_fan_min:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > +                                    NCT6694_HWMON_CMD2_OFFSET,
> > +                                    NCT6694_HWMON_CMD2_LEN,
> > +                                    data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = (data->xmit_buf[NCT6694_FIN_LL(channel)] |
> > +                     data->xmit_buf[NCT6694_FIN_HL(channel)] << 8) & 0xFFFF;
>
> Same here.
>

Fix it in v4.

> > +
> > +             return 0;
> > +     case hwmon_fan_min_alarm:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> > +                                    NCT6694_FIN_STS(channel / 8),
> > +                                    1, data->xmit_buf);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             *val = !!(data->xmit_buf[0] & BIT(channel % 8));
> > +
> > +             return 0;
> > +     default:
> > +             return -EOPNOTSUPP;
> > +     }
> > +}
> > +
> > +static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
> > +                         long *val)
> > +{
> > +     struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > +     unsigned char pwm_en;
> > +     int ret;
> > +
> > +     guard(mutex)(&data->lock);
> > +
> > +     switch (attr) {
> > +     case hwmon_pwm_enable:
> > +             pwm_en = data->hwmon_en[NCT6694_PWM_EN(channel / 8)];
> > +             *val = !!(pwm_en & BIT(channel % 8)) ? 1 : 0;
>
> Drop "? 1 : 0".
>

Drop it in v4.

> > +
...
> > +     case hwmon_temp_max_hyst:
> > +             ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > +                                    NCT6694_HWMON_CMD2_OFFSET,
> > +                                    NCT6694_HWMON_CMD2_LEN,
> > +                                    data->xmit_buf);
> > +
> > +             val = clamp_val(val, -127000, 127000);
> > +             temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> > +             temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
> > +                                          (temp_max - val / 1000);
>
> DIV_ROUND_CLOSEST() ?
>

Fix it in v4.

> > +             temp_hyst = clamp_val(temp_hyst, 0, 7);
> > +             data->xmit_buf[NCT6694_TIN_HYST(channel)] =
> > +                    (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
> > +                    FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
> > +
...
> > +
> > +static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
> > +{
> > +     int ret;
> > +
> > +     guard(mutex)(&data->lock);
> > +
>
> Not significant, but this is unnecessary because the function is only
> called once from probe.
>

Understood! I will drop it in the next patch.

> > +     /*
...
> > +static int nct6694_hwmon_probe(struct platform_device *pdev)
> > +{
> > +     struct nct6694_hwmon_data *data;
> > +     struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > +     struct device *hwmon_dev;
> > +     int ret;
> > +
> > +     data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > +     if (!data)
> > +             return -ENOMEM;
> > +
> > +     data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                   sizeof(unsigned char), GFP_KERNEL);
>
> Wondering ... why not just devm_kzalloc(..,. NCT6694_MAX_PACKET_SZ, ...) ?
> sizeof(unsigned char) is always 1, after all.
>

Okay, I'll fix it.

> > +     if (!data->xmit_buf)
> > +             return -ENOMEM;
> > +
> > +     data->nct6694 = nct6694;
> > +     mutex_init(&data->lock);
> > +     platform_set_drvdata(pdev, data);
>
> This is unnecessary unless I am missing something.
>

Drop it in v4.

> > +
> > +     ret = nct6694_hwmon_init(data);
> > +     if (ret)
> > +             return ret;
> > +
> > +     /* Register hwmon device to HWMON framework */
> > +     hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> > +                                                      "nct6694", data,
> > +                                                      &nct6694_chip_info,
> > +                                                      NULL);
> > +     return PTR_ERR_OR_ZERO(hwmon_dev);
> > +}
> > +
> > +static struct platform_driver nct6694_hwmon_driver = {
> > +     .driver = {
> > +             .name   = "nct6694-hwmon",
> > +     },
> > +     .probe          = nct6694_hwmon_probe,
> > +};
> > +
> > +module_platform_driver(nct6694_hwmon_driver);
> > +
> > +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:nct6694-hwmon");
> > --
> > 2.34.1
> >
> >

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-12  7:01     ` Ming Yu
@ 2024-12-12  7:13       ` Christophe JAILLET
  0 siblings, 0 replies; 39+ messages in thread
From: Christophe JAILLET @ 2024-12-12  7:13 UTC (permalink / raw)
  To: Ming Yu
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, tmyu0, lee, linus.walleij,
	brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem,
	edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni

Le 12/12/2024 à 08:01, Ming Yu a écrit :
> Dear Christophe,
> 
> Thank you for your comments,
> 
> Christophe JAILLET <christophe.jaillet-39ZsbGIQGT5GWvitb5QawA@public.gmane.org> 於 2024年12月12日 週四 上午12:44寫道:
>>
>>> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
>>> +                  u16 length, void *buf)
>>> +{
>>> +     struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
>>> +     struct nct6694_response_header *response_header = nct6694->response_header;
>>> +     struct usb_device *udev = nct6694->udev;
>>> +     int tx_len, rx_len, ret;
>>> +
>>> +     guard(mutex)(&nct6694->access_lock);
>>
>> Nitpick: This could be moved a few lines below, should it still comply
>> with your coding style.
>>
> 
> I think the lock should be placed here to prevent the cmd_header from
> being overwritten by another caller.
> Could you share your perspective on this?

You are right, I misread the code :(
(I though cmd_header was a local structure)

> 
>>> +
>>> +     /* Send command packet to USB device */
>>> +     cmd_header->mod = mod;
>>> +     cmd_header->cmd = offset & 0xFF;
>>> +     cmd_header->sel = (offset >> 8) & 0xFF;
>>> +     cmd_header->hctrl = NCT6694_HCTRL_GET;
>>> +     cmd_header->len = length;

CJ

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2024-12-10 10:45 ` [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
  2024-12-10 15:58   ` Guenter Roeck
@ 2024-12-12 16:10   ` Vincent Mailhol
  2024-12-16  7:04     ` Ming Yu
  1 sibling, 1 reply; 39+ messages in thread
From: Vincent Mailhol @ 2024-12-12 16:10 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

On 10/12/2024 at 19:45, Ming Yu wrote:
> This driver supports Hardware monitor functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
>  MAINTAINERS                   |   1 +
>  drivers/hwmon/Kconfig         |  10 +
>  drivers/hwmon/Makefile        |   1 +
>  drivers/hwmon/nct6694-hwmon.c | 768 ++++++++++++++++++++++++++++++++++
>  4 files changed, 780 insertions(+)
>  create mode 100644 drivers/hwmon/nct6694-hwmon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 496fe7d5a23f..d6414eea0463 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16546,6 +16546,7 @@ M:      Ming Yu <tmyu0@nuvoton.com>
>  L:     linux-kernel@vger.kernel.org
>  S:     Supported
>  F:     drivers/gpio/gpio-nct6694.c
> +F:     drivers/hwmon/nct6694-hwmon.c
>  F:     drivers/i2c/busses/i2c-nct6694.c
>  F:     drivers/mfd/nct6694.c
>  F:     drivers/net/can/nct6694_canfd.c
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index dd376602f3f1..df40986424bd 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1636,6 +1636,16 @@ config SENSORS_NCT6683
>           This driver can also be built as a module. If so, the module
>           will be called nct6683.
>
> +config SENSORS_NCT6694
> +       tristate "Nuvoton NCT6694 Hardware Monitor support"
> +       depends on MFD_NCT6694
> +       help
> +         Say Y here to support Nuvoton NCT6694 hardware monitoring
> +         functionality.
> +
> +         This driver can also be built as a module. If so, the module
> +         will be called nct6694-hwmon.
> +
>  config SENSORS_NCT6775_CORE
>         tristate
>         select REGMAP
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index b827b92f2a78..27a43e67cdb7 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -168,6 +168,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
>  obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
>  obj-$(CONFIG_SENSORS_MR75203)  += mr75203.o
>  obj-$(CONFIG_SENSORS_NCT6683)  += nct6683.o
> +obj-$(CONFIG_SENSORS_NCT6694)  += nct6694-hwmon.o
>  obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
>  nct6775-objs                   := nct6775-platform.o
>  obj-$(CONFIG_SENSORS_NCT6775)  += nct6775.o
> diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
> new file mode 100644
> index 000000000000..b2320d64090b
> --- /dev/null
> +++ b/drivers/hwmon/nct6694-hwmon.c
> @@ -0,0 +1,768 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/hwmon.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +/* Host interface */
> +#define NCT6694_RPT_MOD                        0xFF
> +#define NCT6694_HWMON_MOD              0x00
> +#define NCT6694_PWM_MOD                        0x01
> +
> +/* Report Channel */
> +#define NCT6694_VIN_IDX(x)             (0x00 + (x))
> +#define NCT6694_TIN_IDX(x)                     \
> +       ({ typeof(x) (_x) = (x);                \
> +        ((_x) < 10) ? (0x10 + ((_x) * 2)) :    \
> +        (0x30 + (((_x) - 10) * 2)); })
> +#define NCT6694_FIN_IDX(x)             (0x50 + ((x) * 2))
> +#define NCT6694_PWM_IDX(x)             (0x70 + (x))
> +#define NCT6694_VIN_STS(x)             (0x68 + (x))
> +#define NCT6694_TIN_STS(x)             (0x6A + (x))
> +#define NCT6694_FIN_STS(x)             (0x6E + (x))
> +
> +/* Message Channel*/
> +/* HWMON Command */
> +/* Command 00h */
> +#define NCT6694_HWMON_CMD0_LEN         0x40
> +#define NCT6694_HWMON_CMD0_OFFSET      0x0000  /* OFFSET = SEL|CMD */
> +#define NCT6694_VIN_EN(x)              (0x00 + (x))
> +#define NCT6694_TIN_EN(x)              (0x02 + (x))
> +#define NCT6694_FIN_EN(x)              (0x04 + (x))
> +#define NCT6694_PWM_EN(x)              (0x06 + (x))
> +#define NCT6694_PWM_FREQ_IDX(x)                (0x30 + (x))
> +/* Command 02h */
> +#define NCT6694_HWMON_CMD2_LEN         0x90
> +#define NCT6694_HWMON_CMD2_OFFSET      0x0002  /* OFFSET = SEL|CMD */
> +#define NCT6694_SMI_CTRL_IDX           0x00
> +#define NCT6694_VIN_HL(x)              (0x10 + ((x) * 2))
> +#define NCT6694_VIN_LL(x)              (0x11 + ((x) * 2))
> +#define NCT6694_TIN_HYST(x)            (0x30 + ((x) * 2))
> +#define NCT6694_TIN_HL(x)              (0x31 + ((x) * 2))
> +#define NCT6694_FIN_HL(x)              (0x70 + ((x) * 2))
> +#define NCT6694_FIN_LL(x)              (0x71 + ((x) * 2))
> +/* PWM Command */
> +#define NCT6694_PWM_CMD1_LEN           0x18
> +#define NCT6694_PWM_CMD1_OFFSET                0x0001
> +#define NCT6694_MAL_VAL(x)             (0x02 + (x))
> +
> +#define NCT6694_FREQ_FROM_REG(reg)     ((reg) * 25000 / 255)
> +#define NCT6694_FREQ_TO_REG(val)       \
> +       (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
> +
> +#define NCT6694_LSB_REG_MASK           GENMASK(7, 5)
> +#define NCT6694_TIN_HYST_MASK          GENMASK(7, 5)
> +
> +static inline long in_from_reg(u8 reg)
> +{
> +       return reg * 16;
> +}
> +
> +static inline u8 in_to_reg(long val)
> +{
> +       if (val <= 0)
> +               return 0;
> +       return val / 16;
> +}
> +
> +static inline long temp_from_reg(u8 reg)
> +{
> +       return reg * 1000;
> +}
> +
> +static inline u8 temp_to_reg(long val)
> +{
> +       return val / 1000;
> +}
> +
> +struct nct6694_hwmon_data {
> +       struct nct6694 *nct6694;
> +       struct mutex lock;
> +       unsigned char *xmit_buf;
> +       unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
> +};

A global comment on this series: do not declare your buffers as some
opaque unsigned char arrays. Instead, make it a structure (or an array
of structures if needed) using the little endian types for the
different fields.

You already applied this change to the CAN driver after I made a
comment, please do the same throughout the series.

The same applies with any other comments made by anyone else: do not
only apply to the patch where the comment is made, but apply it
broadly to the series.

Thank you.


Yours sincerely,
Vincent Mailhol

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-11  9:59   ` Marc Kleine-Budde
  2024-12-11 15:25     ` Vincent Mailhol
@ 2024-12-16  6:21     ` Ming Yu
  2024-12-23  9:03       ` Ming Yu
  1 sibling, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-16  6:21 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Marc,

Thank you for your comments,

Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年12月11日 週三 下午5:59寫道:
>
> On 10.12.2024 18:45:21, Ming Yu wrote:
> > This driver supports Socket CANfd functionality for NCT6694 MFD
> > device based on USB interface.
>
> Please use the rx-offload helper, otherwise the CAN frames might be
> revived out of order.
>

Understood. I'll make the modifications in the next patch.

> >
...
> > +
> > +struct __packed nct6694_can_event {
> > +     u8 err1;
> > +     u8 status1;
> > +     u8 tx_evt1;
> > +     u8 rx_evt1;
> > +     u8 rec1;
> > +     u8 tec1;
> > +     u8 reserved1[2];
> > +     u8 err2;
> > +     u8 status2;
> > +     u8 tx_evt2;
> > +     u8 rx_evt2;
> > +     u8 rec2;
> > +     u8 tec2;
> > +     u8 reserved2[2];
> > +};
>
> Create an extra struct that only describes a channel
>
> struct __packed nct6694_can_event_channel {
>         u8 err;
>         u8 status;
>         u8 tx_evt;
>         u8 rx_evt;
>         u8 rec;
>         u8 tec;
>         u8 reserved[2];
> }
>
> and put an array of 2 into struct __packed nct6694_can_event.
>

Fix it in v4.

> > +
> > +struct __packed nct6694_can_xmit {
> > +     u8 tag;
> > +     u8 flag;
> > +     u8 reserved;
> > +     u8 dlc;
> > +     __le32 id;
> > +     u8 data[64];
> > +     u8 msg_buf[72];
>
> Why is the message so long? What's in the msg_buf?
>

It's deprecated, I'll drop it in v4.

> > +};
> > +
> > +struct nct6694_can_priv {
> > +     struct can_priv can;    /* must be the first member */
> > +     struct net_device *ndev;
> > +     struct nct6694 *nct6694;
> > +     struct mutex lock;
>
> What does lock protect?
>

The lock is used to protect tx_buf and rx_buf for each CAN device.

> > +     struct sk_buff *tx_skb;
> > +     struct workqueue_struct *wq;
> > +     struct work_struct tx_work;
> > +     unsigned char *tx_buf;
> void *
> > +     unsigned char *rx_buf;
> void *
> > +     unsigned char can_idx;
> > +     bool tx_busy;
>
> IMHO it makes no sense to have tx_skb and tx_busy
>

Okay! I will revisit these to evaluate whether they are still necessary.

> > +};
> > +
...
> > +static void nct6694_can_read_fifo(struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->rx_buf;
> > +     struct net_device_stats *stats = &ndev->stats;
> > +     struct canfd_frame *cf;
> > +     struct sk_buff *skb;
> > +     int can_idx = priv->can_idx;
> > +     u32 id;
> > +     int ret;
> > +     u8 fd_format = 0;
> bool - no need to init

Fix it in v4.

> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                            NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
> > +                            sizeof(struct nct6694_can_xmit), xmit);
> > +     if (ret < 0)
> > +             return;
> > +
> > +     /* Check type of frame and create skb */
> > +     fd_format = xmit->flag & NCT6694_CAN_FLAG_FD;
> > +     if (fd_format)
> > +             skb = alloc_canfd_skb(ndev, &cf);
> > +     else
> > +             skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> > +
> > +     if (!skb) {
> > +             stats->rx_dropped++;
> > +             return;
> > +     }
> > +
> > +     cf->len = xmit->dlc;
>
> what does xmit->dlc contain? The DLC or the length?
>

It reflects the actual data length.

> > +
> > +     /* Get ID and set flag by its type(Standard ID format or Ext ID format) */
> > +     id = le32_to_cpu(xmit->id);
> > +     if (xmit->flag & NCT6694_CAN_FLAG_EFF) {
> > +             /*
> > +              * In case the Extended ID frame is received, the standard
> > +              * and extended part of the ID are swapped in the register,
> > +              * so swap them back to obtain the correct ID.
> > +              */
>
> You comment doesn't match the code.
>

Fix it in v4.

> > +             id |= CAN_EFF_FLAG;
> > +     }
> > +
> > +     cf->can_id = id;
> > +
> > +     /* Set ESI flag */
> > +     if (xmit->flag & NCT6694_CAN_FLAG_ERR) {
> > +             cf->flags |= CANFD_ESI;
> > +             netdev_dbg(ndev, "ESI Error\n");
> > +     }
> > +
> > +     /* Set RTR and BRS */
> > +     if (!fd_format && (xmit->flag & NCT6694_CAN_FLAG_RTR)) {
> > +             cf->can_id |= CAN_RTR_FLAG;
> > +     } else {
> > +             if (xmit->flag & NCT6694_CAN_FLAG_BRS)
> > +                     cf->flags |= CANFD_BRS;
> > +
> > +             memcpy(cf->data, xmit->data, cf->len);
> > +
> > +             stats->rx_bytes += cf->len;
> > +     }
> > +
> > +     stats->rx_packets++;
> > +
> > +     netif_receive_skb(skb);
> > +}
...
> > +static int nct6694_can_poll(struct net_device *ndev, int quota)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_event *evt = (struct nct6694_can_event *)priv->rx_buf;
> > +     int can_idx = priv->can_idx;
> > +     int work_done = 0, ret;
> > +     u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
> > +     u8 bus_err, can_status;
> > +
> > +     scoped_guard(mutex, &priv->lock) {
> > +             ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                                    NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
> > +                                    sizeof(struct nct6694_can_event), evt);
> > +             if (ret < 0)
> > +                     return IRQ_NONE;
>
> propagate the error
>

Fix it in v4.

> > +
> > +             if (can_idx) {
> > +                     bus_err = evt->err2;
> > +                     can_status = evt->status2;
> > +             } else {
> > +                     bus_err = evt->err1;
> > +                     can_status = evt->status1;
> > +             }
> > +     }
> > +
...
> > +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> > +                                       struct net_device *ndev)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > +     if (priv->tx_skb || priv->tx_busy) {
> > +             netdev_err(ndev, "hard_xmit called while tx busy\n");
> > +             return NETDEV_TX_BUSY;
> > +     }
> > +
> > +     if (can_dev_dropped_skb(ndev, skb))
> > +             return NETDEV_TX_OK;
>
> please drop first
>

Fix it in v4.

> > +
> > +     netif_stop_queue(ndev);
> > +     priv->tx_skb = skb;
> > +     queue_work(priv->wq, &priv->tx_work);
> > +
> > +     return NETDEV_TX_OK;
> > +}
> > +
> > +static void nct6694_can_tx(struct net_device *ndev, struct canfd_frame *cf)
> > +{
> > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +     struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->tx_buf;
> > +     u32 txid = 0;
> > +
> > +     memset(xmit, 0, sizeof(struct nct6694_can_xmit));
> > +
> > +     if (priv->can_idx == 0)
> > +             xmit->tag = NCT6694_CAN_TAG_CAN0<;
> > +     else
> > +             xmit->tag = NCT6694_CAN_TAG_CAN1;
> > +
> > +     if (cf->can_id & CAN_EFF_FLAG) {
> > +             txid = cf->can_id & CAN_EFF_MASK;
> > +             /*
> > +              * In case the Extended ID frame is transmitted, the
> > +              * standard and extended part of the ID are swapped
> > +              * in the register, so swap them back to send the
> > +              * correct ID.
>
> You comment doesn't match the code.
>

Fix it in v4.

> > +              */
> > +             xmit->flag |= NCT6694_CAN_FLAG_EFF;
> > +     } else {
> > +             txid = cf->can_id & CAN_SFF_MASK;
> > +     }
> > +
> > +     xmit->id = cpu_to_le32(txid);
> > +     xmit->dlc = cf->len;
> > +
> > +     if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
>
> No need to check ctrlmode
>

Fix it in v4.

> > +         can_is_canfd_skb(priv->tx_skb)) {
> > +             xmit->flag |= NCT6694_CAN_FLAG_FD;
> > +             if (cf->flags & CANFD_BRS)
> > +                     xmit->flag |= NCT6694_CAN_FLAG_BRS;
> > +     }
> > +
> > +     if (cf->can_id & CAN_RTR_FLAG)
> > +             xmit->flag |= NCT6694_CAN_FLAG_RTR;
>
> you can move this into the !can_is_canfd_skb branch of the if
>

Fix it in v4.

> > +
> > +     memcpy(xmit->data, cf->data, cf->len);
> > +
> > +     nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                       NCT6694_CAN_CMD10_OFFSET(1),
> > +                       sizeof(struct nct6694_can_xmit),
> > +                       xmit);
> > +}
> > +
> > +static void nct6694_can_tx_work(struct work_struct *work)
> > +{
> > +     struct nct6694_can_priv *priv = container_of(work,
> > +                                                  struct nct6694_can_priv,
> > +                                                  tx_work);
> > +     struct net_device *ndev = priv->ndev;
> > +     struct canfd_frame *cf;
> > +
> > +     guard(mutex)(&priv->lock);
> > +
> > +     if (priv->tx_skb) {
> > +             if (priv->can.state == CAN_STATE_BUS_OFF) {
> > +                     nct6694_can_clean(ndev);
> > +             } else {
> > +                     cf = (struct canfd_frame *)priv->tx_skb->data;
> > +                     nct6694_can_tx(ndev, cf);
> > +                     priv->tx_busy = true;
> > +                     can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
> > +                     priv->tx_skb = NULL;
> > +             }
> > +     }
> > +}
> > +
> > +static const struct net_device_ops nct6694_can_netdev_ops = {
> > +     .ndo_open = nct6694_can_open,
> > +     .ndo_stop = nct6694_can_stop,
> > +     .ndo_start_xmit = nct6694_can_start_xmit,
> > +     .ndo_change_mtu = can_change_mtu,
> > +};
> > +
> > +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> > +     .get_ts_info = ethtool_op_get_ts_info,
> > +};
> > +
> > +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> > +{
> > +     struct nct6694_can_information *info = (struct nct6694_can_information *)priv->rx_buf;
> > +     int ret;
> > +
> > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > +                            NCT6694_CAN_CMD1_OFFSET,
> > +                            sizeof(struct nct6694_can_information),
> > +                            info);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return le32_to_cpu(info->can_clk);
> > +}
> > +
> > +static int nct6694_can_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_can_priv *priv;
> > +     struct net_device *ndev;
> > +     int ret, irq, can_clk;
> > +
> > +     irq = irq_create_mapping(nct6694->domain,
> > +                              NCT6694_IRQ_CAN1 + cell->id);
> > +     if (!irq)
> > +             return -EINVAL;
>
> propagate error value
>

Fix it in v4.

> > +
> > +     ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> > +     if (!ndev)
> > +             return -ENOMEM;
> > +
> > +     ndev->irq = irq;
> > +     ndev->flags |= IFF_ECHO;
> > +     ndev->netdev_ops = &nct6694_can_netdev_ops;
> > +     ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> > +
> > +     priv = netdev_priv(ndev);
> > +     priv->nct6694 = nct6694;
> > +     priv->ndev = ndev;
> > +
> > +     priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                 sizeof(unsigned char), GFP_KERNEL);
>
> devm_kzalloc()
> > +     if (!priv->tx_buf) {
> > +             ret = -ENOMEM;
> > +             goto free_candev;
> > +     }
> > +
> > +     priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                 sizeof(unsigned char), GFP_KERNEL);
> devm_kzalloc()
>

Fix it in v4.

> > +     if (!priv->rx_buf) {
> > +             ret = -ENOMEM;
> > +             goto free_candev;
> > +     }
> > +
> > +     can_clk = nct6694_can_get_clock(priv);
> > +     if (can_clk < 0) {
> > +             ret = -EIO;
>
> propagate the error value, don't overwrite it
>
> move the dev_err_probe() here.
>

Fix it in v4.

> > +             goto free_candev;
> > +     }
> > +
> > +     mutex_init(&priv->lock);
> > +     INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
> > +
> > +     priv->can_idx = cell->id;
> > +     priv->can.state = CAN_STATE_STOPPED;
> > +     priv->can.clock.freq = can_clk;
> > +     priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > +     priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > +     priv->can.do_set_mode = nct6694_can_set_mode;
> > +     priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > +
> > +     priv->can.ctrlmode = CAN_CTRLMODE_FD;
> > +
> > +     priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK            |
> > +                                    CAN_CTRLMODE_LISTENONLY          |
> > +                                    CAN_CTRLMODE_FD                  |
> > +                                    CAN_CTRLMODE_FD_NON_ISO          |
> > +                                    CAN_CTRLMODE_BERR_REPORTING;
> > +
> > +     platform_set_drvdata(pdev, priv);
> > +     SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> > +
> > +     ret = register_candev(priv->ndev);
> > +     if (ret)
> > +             goto free_candev;
> > +
> > +     return 0;
> > +
> > +free_candev:
> > +     free_candev(ndev);
> > +     return dev_err_probe(&pdev->dev, ret, "Probe failed\n");
>
> Move the dev_err_probe() with an appropriate error message to where the
> error occurs. If malloc fails, the kernel already prints for you, so
> here it's only nct6694_can_get_clock() only.
>

Understood. I'll make the modifications in the next patch.

> > +}
> > +
> > +static void nct6694_can_remove(struct platform_device *pdev)
> > +{
> > +     struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> > +
> > +     cancel_work_sync(&priv->tx_work);
> > +     unregister_candev(priv->ndev);
> > +     free_candev(priv->ndev);
> > +}
> > +
> > +static struct platform_driver nct6694_can_driver = {
> > +     .driver = {
> > +             .name   = DRVNAME,
> > +     },
> > +     .probe          = nct6694_can_probe,
> > +     .remove         = nct6694_can_remove,
> > +};
> > +
> > +module_platform_driver(nct6694_can_driver);
> > +
> > +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:nct6694-can");
> > --
> > 2.34.1
> >
> >
> >
>
> regards,
> Marc
>
> --
> Pengutronix e.K.                 | Marc Kleine-Budde          |
> Embedded Linux                   | https://www.pengutronix.de |
> Vertretung Nürnberg              | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax:   +49-5121-206917-9   |

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-11 15:25     ` Vincent Mailhol
@ 2024-12-16  6:58       ` Ming Yu
  2024-12-16 17:14         ` Vincent Mailhol
  0 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-16  6:58 UTC (permalink / raw)
  To: Vincent Mailhol
  Cc: Marc Kleine-Budde, tmyu0, lee, linus.walleij, brgl, andi.shyti,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Vincent,

Thank you for your comments,

Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年12月11日 週三 下午11:25寫道:
>
...
> > > +/* Host interface */
> > > +#define NCT6694_CAN_MOD                      0x05
> > > +
> > > +/* Message Channel*/
> > > +/* Command 00h */
>
> Instead of this comment, explain what the command 00h does.
>
> Also better to give a memorable name instead of CMD0. For example:
> CMD_TX, CMD_RX…
>
> If possible, make it match the structure names.
>

Okay, I'll make the modifications in the next patch.

> > > +#define NCT6694_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000)
> > > +#define NCT6694_CAN_CTRL1_MON                BIT(0)
> > > +#define NCT6694_CAN_CTRL1_NISO               BIT(1)
> > > +#define NCT6694_CAN_CTRL1_LBCK               BIT(2)
> > > +
> > > +/* Command 01h */
> > > +#define NCT6694_CAN_CMD1_OFFSET              0x0001
> > > +
> > > +/* Command 02h */
> > > +#define NCT6694_CAN_CMD2_OFFSET(idx, mask)                   \
> > > +     ({ typeof(mask) mask_ = (mask);                         \
> > > +        idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) :        \
> > > +              ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
> > > +
> > > +#define NCT6694_CAN_EVENT_ERR                BIT(0)
> > > +#define NCT6694_CAN_EVENT_STATUS     BIT(1)
> > > +#define NCT6694_CAN_EVENT_TX_EVT     BIT(2)
> > > +#define NCT6694_CAN_EVENT_RX_EVT     BIT(3)
> > > +#define NCT6694_CAN_EVENT_REC                BIT(4)
> > > +#define NCT6694_CAN_EVENT_TEC                BIT(5)
> > > +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY        BIT(7)  /* Read-clear */
> > > +#define NCT6694_CAN_EVT_RX_DATA_LOST BIT(5)  /* Read-clear */
> > > +#define NCT6694_CAN_EVT_RX_HALF_FULL BIT(6)  /* Read-clear */
> > > +#define NCT6694_CAN_EVT_RX_DATA_IN   BIT(7)
> > > +
> > > +/* Command 10h */
> > > +#define NCT6694_CAN_CMD10_OFFSET(buf_cnt)    \
> > > +     (((buf_cnt) & 0xFF) << 8 | 0x10)
> > > +#define NCT6694_CAN_TAG_CAN0         0xC0
> > > +#define NCT6694_CAN_TAG_CAN1         0xC1
> > > +#define NCT6694_CAN_FLAG_EFF         BIT(0)
> > > +#define NCT6694_CAN_FLAG_RTR         BIT(1)
> > > +#define NCT6694_CAN_FLAG_FD          BIT(2)
> > > +#define NCT6694_CAN_FLAG_BRS         BIT(3)
> > > +#define NCT6694_CAN_FLAG_ERR         BIT(4)
> > > +
> > > +/* Command 11h */
> > > +#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt)                       \
> > > +     ({ typeof(buf_cnt) buf_cnt_ = (buf_cnt);                \
> > > +        idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) :     \
> > > +              ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })
>
> Simplify this. Do something like:
>
>   #define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt) \
>           (idx ? 0x80 : 0x00) | \
>           (buf_cnt & 0xFF)) << 8 | 0x11) \
>
> (apply this also to NCT6694_CAN_CMD2_OFFSET())
>

Understood! I will fix these in v4.

> > > +#define NCT6694_CAN_RX_QUOTA         64
> > > +
> > > +enum nct6694_event_err {
> > > +     NCT6694_CAN_EVT_NO_ERROR,
> >                       ^^^ add _ERR_
> > > +     NCT6694_CAN_EVT_CRC_ERROR,
> > > +     NCT6694_CAN_EVT_STUFF_ERROR,
> > > +     NCT6694_CAN_EVT_ACK_ERROR,
> > > +     NCT6694_CAN_EVT_FORM_ERROR,
> > > +     NCT6694_CAN_EVT_BIT_ERROR,
> > > +     NCT6694_CAN_EVT_TIMEOUT_ERROR,
> > > +     NCT6694_CAN_EVT_UNKNOWN_ERROR,
> > > +};
> > > +
> > > +enum nct6694_event_status {
> > > +     NCT6694_CAN_EVT_ERROR_ACTIVE,
> >                       ^^^ add _STATUS_
> > > +     NCT6694_CAN_EVT_ERROR_PASSIVE,
> > > +     NCT6694_CAN_EVT_BUS_OFF,
> > > +     NCT6694_CAN_EVT_WARNING,
> > > +};
> > > +
> > > +struct __packed nct6694_can_setting {
> > > +     __le32 nbr;
> > > +     __le32 dbr;
> > > +     u8 active;
> > > +     u8 reserved[3];
> > > +     __le16 ctrl1;
> > > +     __le16 ctrl2;
> > > +     __le32 nbtp;
> > > +     __le32 dbtp;
> > > +};
> > > +
> > > +struct __packed nct6694_can_information {
> > > +     u8 tx_fifo_cnt;
> > > +     u8 rx_fifo_cnt;
> > > +     __le16 reserved;
> > u8 reserved[2];
> > > +     __le32 can_clk;
> > > +};
> > > +
> > > +struct __packed nct6694_can_event {
> > > +     u8 err1;
> > > +     u8 status1;
> > > +     u8 tx_evt1;
> > > +     u8 rx_evt1;
> > > +     u8 rec1;
> > > +     u8 tec1;
> > > +     u8 reserved1[2];
> > > +     u8 err2;
> > > +     u8 status2;
> > > +     u8 tx_evt2;
> > > +     u8 rx_evt2;
> > > +     u8 rec2;
> > > +     u8 tec2;
> > > +     u8 reserved2[2];
> > > +};
> >
> > Create an extra struct that only describes a channel
> >
> > struct __packed nct6694_can_event_channel {
> >         u8 err;
> >         u8 status;
> >         u8 tx_evt;
> >         u8 rx_evt;
> >         u8 rec;
> >         u8 tec;
> >         u8 reserved[2];
> > }
> >
> > and put an array of 2 into struct __packed nct6694_can_event.
> >
> > > +
> > > +struct __packed nct6694_can_xmit {
>
> Is this struct for both TX and RX? If so, name it something like
>
>   struct nct6694_can_frame
>
> The term xmit is only used for transmission, not for reception.
>

Okay, I'll make the modifications in the next patch.

> > > +     u8 tag;
> > > +     u8 flag;
> > > +     u8 reserved;
> > > +     u8 dlc;
> > > +     __le32 id;
> > > +     u8 data[64];
> > > +     u8 msg_buf[72];
> >
> > Why is the message so long? What's in the msg_buf?
> >
> > > +};
> > > +
> > > +struct nct6694_can_priv {
> > > +     struct can_priv can;    /* must be the first member */
> > > +     struct net_device *ndev;
> > > +     struct nct6694 *nct6694;
> > > +     struct mutex lock;
> >
> > What does lock protect?
>
> +1
>
> mutexes are good if you want to keep the lock for a long time.
>
> For short period, spinlock are more performant:
>
>           spinlock_t lock;
>

The lock ensures that data accessed by nct6694_read_msg() and
nct6694_write_msg() is not overwritten.

Since nct6694_read_msg() and nct6694_write_msg() use usb_bulk_msg(),
which may cause the process  to sleep, spinlock is not used.

> > > +     struct sk_buff *tx_skb;
> > > +     struct workqueue_struct *wq;
> > > +     struct work_struct tx_work;
> > > +     unsigned char *tx_buf;
> > void *
> > > +     unsigned char *rx_buf;
> > void *
>
> Rather than void*, tx_buf and rx_buf can be replaced by an union:
>
>   union nct6694_can_rx {
>           struct nct6694_can_event event;
>           struct nct6694_can_xmit xmit;
>           struct nct6694_can_information info;
>   };
>
> (same for nct6694_can_tx)
>
> Then in struct nct6694_can_priv, you will just have:
>
>   union nct6694_can_tx tx;
>   union nct6694_can_rx rx;
>
> With this,
>
>   NCT6694_MAX_PACKET_SZ
>
> can most likely be replaced by sizeof(union nct6694_can_rx) or
> sizeof(union nct6694_can_tx).
>

Okay, I'll make the modifications in the next patch.

> > > +     unsigned char can_idx;
> > > +     bool tx_busy;
> >
...
> > > +static void nct6694_can_read_fifo(struct net_device *ndev)
> > > +{
> > > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > +     struct nct6694_can_xmit *xmit = (struct nct6694_can_xmit *)priv->rx_buf;
> > > +     struct net_device_stats *stats = &ndev->stats;
> > > +     struct canfd_frame *cf;
> > > +     struct sk_buff *skb;
> > > +     int can_idx = priv->can_idx;
> > > +     u32 id;
> > > +     int ret;
> > > +     u8 fd_format = 0;
> > bool - no need to init
> > > +
> > > +     guard(mutex)(&priv->lock);
> > > +
> > > +     ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> > > +                            NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
> > > +                            sizeof(struct nct6694_can_xmit), xmit);
> > > +     if (ret < 0)
> > > +             return;
> > > +
> > > +     /* Check type of frame and create skb */
> > > +     fd_format = xmit->flag & NCT6694_CAN_FLAG_FD;
> > > +     if (fd_format)
> > > +             skb = alloc_canfd_skb(ndev, &cf);
> > > +     else
> > > +             skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> > > +
> > > +     if (!skb) {
> > > +             stats->rx_dropped++;
> > > +             return;
> > > +     }
> > > +
> > > +     cf->len = xmit->dlc;
> >
> > what does xmit->dlc contain? The DLC or the length?
>
> +1
>
> Also, do not trust the device data. Even if SPI attacks are less
> common, make sure to sanitize this length.
>
>   cf->len = canfd_sanitize_len(xmit->dlc);
>
> Or
>
>   cf->len = canfd_sanitize_len(xmit->dlc);
>
> if xmit->dlc is in fact a DLC.
>

Excuse me, the xmit->dlc is actual data length.
Does it need to be fixed?

> > > +
...
> > > +static int nct6694_can_handle_lec_err(struct net_device *ndev, u8 bus_err)
> > > +{
> > > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > +     struct net_device_stats *stats = &ndev->stats;
> > > +     struct can_frame *cf;
> > > +     struct sk_buff *skb;
> > > +
> > > +     if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
> > > +             return 0;
> > > +
> > > +     priv->can.can_stats.bus_error++;
> > > +     stats->rx_errors++;
> > > +
> > > +     /* Propagate the error condition to the CAN stack. */
> > > +     skb = alloc_can_err_skb(ndev, &cf);
> > > +
> > > +     if (unlikely(!skb))
> > > +             return 0;
>
> Do not exit if the memory allocation fails. Instead, do the error
> handling to increase the statistics.
>
> Look at what other CAN drivers are doing.
>

Okay, I'll make the modifications in the next patch.

> > > +     /* Read the error counter register and check for new errors. */
> > > +     cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> > > +
> > > +     switch (bus_err) {
> > > +     case NCT6694_CAN_EVT_CRC_ERROR:
> > > +             cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_STUFF_ERROR:
> > > +             cf->data[2] |= CAN_ERR_PROT_STUFF;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_ACK_ERROR:
> > > +             cf->data[3] = CAN_ERR_PROT_LOC_ACK;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_FORM_ERROR:
> > > +             cf->data[2] |= CAN_ERR_PROT_FORM;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_BIT_ERROR:
> > > +             cf->data[2] |= CAN_ERR_PROT_BIT |
> > > +                            CAN_ERR_PROT_BIT0 |
> > > +                            CAN_ERR_PROT_BIT1;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_TIMEOUT_ERROR:
> > > +             cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> > > +             break;
> > > +
> > > +     case NCT6694_CAN_EVT_UNKNOWN_ERROR:
> > > +             cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> > > +             /*
> > > +              * It means 'unspecified'(the value is '0').
> > > +              * But it is not sure if it's ok to send an error package
> > > +              * without specific error bit.
> > > +              */
> > > +             break;
> > > +
> > > +     default:
> > > +             break;
> > > +     }
> > > +
> > > +     /* Reset the error counter, ack the IRQ and re-enable the counter. */
> > > +     stats->rx_packets++;
> > > +     stats->rx_bytes += cf->can_dlc;
>
> The CAN error frames are not regular packets. Do not increase the RX
> stats. Instead, increase the error stats.
>

Okay, I'll make the modifications in the next patch.

> > > +     netif_receive_skb(skb);
> > > +
> > > +     return 1;
> > > +}
> > > +
> > > +static int nct6694_can_handle_state_change(struct net_device *ndev,
> > > +                                        enum can_state new_state)
> > > +{
> > > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > +     struct net_device_stats *stats = &ndev->stats;
> > > +     struct can_frame *cf;
> > > +     struct sk_buff *skb;
> > > +     struct can_berr_counter bec;
> > > +
> > > +     switch (new_state) {
> > > +     case CAN_STATE_ERROR_ACTIVE:
> > > +             priv->can.can_stats.error_warning++;
> > > +             priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > > +             break;
> > > +     case CAN_STATE_ERROR_WARNING:
> > > +             priv->can.can_stats.error_warning++;
> > > +             priv->can.state = CAN_STATE_ERROR_WARNING;
> > > +             break;
> > > +     case CAN_STATE_ERROR_PASSIVE:
> > > +             priv->can.can_stats.error_passive++;
> > > +             priv->can.state = CAN_STATE_ERROR_PASSIVE;
> > > +             break;
> > > +     case CAN_STATE_BUS_OFF:
> > > +             priv->can.state = CAN_STATE_BUS_OFF;
> > > +             priv->can.can_stats.bus_off++;
> > > +             can_bus_off(ndev);
> > > +             break;
> > > +     default:
> > > +             break;
> > > +     }
> > > +
> > > +     /* propagate the error condition to the CAN stack */
> > > +     skb = alloc_can_err_skb(ndev, &cf);
> > > +     if (unlikely(!skb))
> > > +             return 0;
>
> Same as above: handle the statistics even if the allocation fails.
>

Okay, I'll make the modifications in the next patch.

> > > +     nct6694_can_get_berr_counter(ndev, &bec);
> > > +
> > > +     switch (new_state) {
> > > +     case CAN_STATE_ERROR_WARNING:
> > > +             /* error warning state */
> > > +             cf->can_id |= CAN_ERR_CRTL;
> > > +             cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> > > +                                                     CAN_ERR_CRTL_RX_WARNING;
>
> Prefer an if/else here instead of the ternary operator. It is more readable.
>

Fix it in v4.

> > > +             cf->data[6] = bec.txerr;
> > > +             cf->data[7] = bec.rxerr;
> > > +             break;
> > > +     case CAN_STATE_ERROR_PASSIVE:
> > > +             /* error passive state */
> > > +             cf->can_id |= CAN_ERR_CRTL;
> > > +             cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> > > +             if (bec.txerr > 127)
> > > +                     cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> > > +             cf->data[6] = bec.txerr;
> > > +             cf->data[7] = bec.rxerr;
> > > +             break;
> > > +     case CAN_STATE_BUS_OFF:
> > > +             /* bus-off state */
> > > +             cf->can_id |= CAN_ERR_BUSOFF;
> > > +             break;
> > > +     default:
> > > +             break;
> > > +     }
> > > +
> > > +     stats->rx_packets++;
> > > +     stats->rx_bytes += cf->can_dlc;
>
> Do not increase the RX stats when you receive a CAN event.
>

Okay, I'll make the modifications in the next patch.

> > > +     netif_receive_skb(skb);
> > > +
> > > +     return 1;
> > > +}
...
> > > +static int nct6694_can_start(struct net_device *ndev)
> > > +{
> > > +     struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > +     struct nct6694_can_setting *setting = (struct nct6694_can_setting *)priv->tx_buf;
> > > +     const struct can_bittiming *n_bt = &priv->can.bittiming;
> > > +     const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > > +     int ret;
> > > +
> > > +     guard(mutex)(&priv->lock);
> > > +
> > > +     memset(priv->tx_buf, 0, sizeof(struct nct6694_can_setting));
>
> When you memset(), use this pattern:
>
>           memset(setting, 0, sizeof(*setting));
>
> Apply this throughout your driver.
>

Okay, I'll make the modifications in the next patch.

> > > +     setting->nbr = cpu_to_le32(n_bt->bitrate);
> > > +     setting->dbr = cpu_to_le32(d_bt->bitrate);
> > > +
...
> > > +module_platform_driver(nct6694_can_driver);
> > > +
> > > +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> > > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > > +MODULE_LICENSE("GPL");
> > > +MODULE_ALIAS("platform:nct6694-can");
>
>
> Yours sincerely,
> Vincent Mailhol

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2024-12-12 16:10   ` Vincent Mailhol
@ 2024-12-16  7:04     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-16  7:04 UTC (permalink / raw)
  To: Vincent Mailhol
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Vincent,

Thank you for your comments,
I understand, I will make the modifications for these drivers,

Thanks,
Ming

Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年12月13日 週五 上午12:10寫道:
>
...
> > +struct nct6694_hwmon_data {
> > +       struct nct6694 *nct6694;
> > +       struct mutex lock;
> > +       unsigned char *xmit_buf;
> > +       unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
> > +};
>
> A global comment on this series: do not declare your buffers as some
> opaque unsigned char arrays. Instead, make it a structure (or an array
> of structures if needed) using the little endian types for the
> different fields.
>
> You already applied this change to the CAN driver after I made a
> comment, please do the same throughout the series.
>
> The same applies with any other comments made by anyone else: do not
> only apply to the patch where the comment is made, but apply it
> broadly to the series.
>
> Thank you.
>
>
> Yours sincerely,
> Vincent Mailhol

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support
  2024-12-10 10:45 ` [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
@ 2024-12-16 10:41   ` Alexandre Belloni
  2024-12-23  1:50     ` Ming Yu
  0 siblings, 1 reply; 39+ messages in thread
From: Alexandre Belloni @ 2024-12-16 10:41 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

On 10/12/2024 18:45:24+0800, Ming Yu wrote:
> +static int nct6694_rtc_probe(struct platform_device *pdev)
> +{
> +	struct nct6694_rtc_data *data;
> +	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> +	int ret, irq;
> +
> +	irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
> +	if (!irq)
> +		return -EINVAL;
> +
> +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> +				      sizeof(unsigned char), GFP_KERNEL);
> +	if (!data->xmit_buf)
> +		return -ENOMEM;
> +
> +	data->rtc = devm_rtc_allocate_device(&pdev->dev);
> +	if (IS_ERR(data->rtc))
> +		return PTR_ERR(data->rtc);
> +
> +	data->nct6694 = nct6694;
> +	data->rtc->ops = &nct6694_rtc_ops;
> +	data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
> +	data->rtc->range_max = RTC_TIMESTAMP_END_2099;
> +
> +	mutex_init(&data->lock);

You should use rtc_lock/rtc_unlock instead of having your own lock. The
core will take and release the lock appropriately before calling the
rtc_ops so you only have to do it in the irq handler.

> +
> +	device_set_wakeup_capable(&pdev->dev, 1);

This will cause a memory leak later on, see the discussion here:

https://lore.kernel.org/linux-rtc/a88475b6-08bf-4c7c-ad63-efd1f29307e3@pf.is.s.u-tokyo.ac.jp/T/#mf51fdce6036efa3ea12fe75bd5126d4ac0c6813e

> +
> +	platform_set_drvdata(pdev, data);
> +
> +	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
> +					nct6694_irq, IRQF_ONESHOT,
> +					"nct6694-rtc", data);
> +	if (ret < 0)
> +		return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
> +
> +	/* Register rtc device to RTC framework */
> +	return devm_rtc_register_device(data->rtc);
> +}
> +
> +static struct platform_driver nct6694_rtc_driver = {
> +	.driver = {
> +		.name	= "nct6694-rtc",
> +	},
> +	.probe		= nct6694_rtc_probe,
> +};
> +
> +module_platform_driver(nct6694_rtc_driver);
> +
> +MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-rtc");
> -- 
> 2.34.1
> 

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-16  6:58       ` Ming Yu
@ 2024-12-16 17:14         ` Vincent Mailhol
  0 siblings, 0 replies; 39+ messages in thread
From: Vincent Mailhol @ 2024-12-16 17:14 UTC (permalink / raw)
  To: Ming Yu
  Cc: Marc Kleine-Budde, tmyu0, lee, linus.walleij, brgl, andi.shyti,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

On 16/12/2024 at 15:58, Ming Yu wrote:
> Dear Vincent,
> 
> Thank you for your comments,
> 
> Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年12月11日 週三 下午11:25寫道:

(...)

>>>> +     cf->len = xmit->dlc;
>>>
>>> what does xmit->dlc contain? The DLC or the length?
>>
>> +1
>>
>> Also, do not trust the device data. Even if SPI attacks are less
>> common, make sure to sanitize this length.
>>
>>   cf->len = canfd_sanitize_len(xmit->dlc);
>>
>> Or
>>
>>   cf->len = canfd_sanitize_len(xmit->dlc);
>>
>> if xmit->dlc is in fact a DLC.
>>
> 
> Excuse me, the xmit->dlc is actual data length.
> Does it need to be fixed?

Yes, name is xmit->len. DLC has a different meaning.


Yours sincerely,
Vincent Mailhol


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2024-12-10 10:45 ` [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
  2024-12-10 12:46   ` Bartosz Golaszewski
@ 2024-12-20 12:41   ` Linus Walleij
  2024-12-23  2:02     ` Ming Yu
  1 sibling, 1 reply; 39+ messages in thread
From: Linus Walleij @ 2024-12-20 12:41 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Hi Ming,

thanks for your patch!

Some nits below:

On Tue, Dec 10, 2024 at 11:46 AM Ming Yu <a0282524688@gmail.com> wrote:

> This driver supports GPIO and IRQ functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
(...)
> +#include <linux/gpio/driver.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>

#include <linux/bits.h>
is missing, include it explicitly.

> +       return !(BIT(offset) & data->xmit_buf);

Here you use the BIT() macro from <linux/bits.h>

> +static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
> +{
> +       struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> +       int ret;
> +
> +       guard(mutex)(&data->lock);
> +
> +       ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> +                              NCT6694_GPO_DIR + data->group,
> +                              NCT6694_GPIO_LEN, &data->xmit_buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       data->xmit_buf &= ~(1 << offset);

data->xmit_buf &= ~BIT(offset);

> +static int nct6694_direction_output(struct gpio_chip *gpio,
> +                                   unsigned int offset, int val)
> +{
> +       struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> +       int ret;
> +
> +       guard(mutex)(&data->lock);
> +
> +       /* Set direction to output */
> +       ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> +                              NCT6694_GPO_DIR + data->group,
> +                              NCT6694_GPIO_LEN, &data->xmit_buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       data->xmit_buf |= (1 << offset);

data->xmit_buf |= BIT(offset);

> +       if (val)
> +               data->xmit_buf |= (1 << offset);
> +       else
> +               data->xmit_buf &= ~(1 << offset);

Same

> +static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
> +                             int val)
> +{
(...)
> +       if (val)
> +               data->xmit_buf |= (1 << offset);
> +       else
> +               data->xmit_buf &= ~(1 << offset);

Same

> +static irqreturn_t nct6694_irq_handler(int irq, void *priv)
> +{
> +       struct nct6694_gpio_data *data = priv;
> +       unsigned char status;
> +
> +       guard(mutex)(&data->lock);
> +
> +       nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> +                        NCT6694_GPI_STS + data->group,
> +                        NCT6694_GPIO_LEN, &data->xmit_buf);
> +
> +       status = data->xmit_buf;
> +
> +       while (status) {
> +               int bit = __ffs(status);
> +
> +               data->xmit_buf = BIT(bit);
> +               handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
> +               status &= ~(1 << bit);

Same

Just use BIT() consistently please.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
                     ` (2 preceding siblings ...)
  2024-12-11 16:44   ` Christophe JAILLET
@ 2024-12-20 12:45   ` Linus Walleij
  2024-12-23  2:04     ` Ming Yu
  3 siblings, 1 reply; 39+ messages in thread
From: Linus Walleij @ 2024-12-20 12:45 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Hi Ming,

thanks for your patch!

On Tue, Dec 10, 2024 at 11:45 AM Ming Yu <a0282524688@gmail.com> wrote:

> 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 implements USB device functionality and shares the
> chip's peripherals as a child device.
>
> 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.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
(...)
> +       while (*int_status) {
> +               int irq = __ffs(*int_status);
> +
> +               if (*int_status & (1 << irq))
> +                       generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> +
> +               *int_status &= ~(1 << irq);

What about doing what you do in the GPIO driver and
#include <linux/bits.h>

And search and replace "(1 << irq)" with BIT(irq)?

PS the main reason we do this is because

int a = (1 << 31);

becomes a negative number on 32bit machines, and
can lead to confusing side effects. BIT() always work
on unsigned.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support
  2024-12-16 10:41   ` Alexandre Belloni
@ 2024-12-23  1:50     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-23  1:50 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc

Dear Alexandre,

Thank you for your comments,

Alexandre Belloni <alexandre.belloni@bootlin.com> 於 2024年12月16日 週一 下午6:42寫道:
>
> On 10/12/2024 18:45:24+0800, Ming Yu wrote:
> > +static int nct6694_rtc_probe(struct platform_device *pdev)
> > +{
> > +     struct nct6694_rtc_data *data;
> > +     struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > +     int ret, irq;
> > +
> > +     irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
> > +     if (!irq)
> > +             return -EINVAL;
> > +
> > +     data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > +     if (!data)
> > +             return -ENOMEM;
> > +
> > +     data->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> > +                                   sizeof(unsigned char), GFP_KERNEL);
> > +     if (!data->xmit_buf)
> > +             return -ENOMEM;
> > +
> > +     data->rtc = devm_rtc_allocate_device(&pdev->dev);
> > +     if (IS_ERR(data->rtc))
> > +             return PTR_ERR(data->rtc);
> > +
> > +     data->nct6694 = nct6694;
> > +     data->rtc->ops = &nct6694_rtc_ops;
> > +     data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
> > +     data->rtc->range_max = RTC_TIMESTAMP_END_2099;
> > +
> > +     mutex_init(&data->lock);
>
> You should use rtc_lock/rtc_unlock instead of having your own lock. The
> core will take and release the lock appropriately before calling the
> rtc_ops so you only have to do it in the irq handler.
>

Understood. I will make the modifications in the next patch.

> > +
> > +     device_set_wakeup_capable(&pdev->dev, 1);
>
> This will cause a memory leak later on, see the discussion here:
>
> https://lore.kernel.org/linux-rtc/a88475b6-08bf-4c7c-ad63-efd1f29307e3@pf.is.s.u-tokyo.ac.jp/T/#mf51fdce6036efa3ea12fe75bd5126d4ac0c6813e
>

Okay! I will drop it and add device_init_wakeup() in the next patch.

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2024-12-20 12:41   ` Linus Walleij
@ 2024-12-23  2:02     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-23  2:02 UTC (permalink / raw)
  To: Linus Walleij
  Cc: tmyu0, lee, brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Linus,

Thank you for your comments,

Linus Walleij <linus.walleij@linaro.org> 於 2024年12月20日 週五 下午8:42寫道:
>
> Hi Ming,
>
> thanks for your patch!
>
> Some nits below:
>
> On Tue, Dec 10, 2024 at 11:46 AM Ming Yu <a0282524688@gmail.com> wrote:
>
> > This driver supports GPIO and IRQ functionality for NCT6694 MFD
> > device based on USB interface.
> >
> > Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> (...)
> > +#include <linux/gpio/driver.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
>
> #include <linux/bits.h>
> is missing, include it explicitly.
>
> > +       return !(BIT(offset) & data->xmit_buf);
>
> Here you use the BIT() macro from <linux/bits.h>
>

Understood. I will add the header in the next patch.

> > +static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
> > +{
> > +       struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> > +       int ret;
> > +
> > +       guard(mutex)(&data->lock);
> > +
> > +       ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> > +                              NCT6694_GPO_DIR + data->group,
> > +                              NCT6694_GPIO_LEN, &data->xmit_buf);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       data->xmit_buf &= ~(1 << offset);
>
> data->xmit_buf &= ~BIT(offset);
>

Okay! Fix it in the v4.

> > +static int nct6694_direction_output(struct gpio_chip *gpio,
> > +                                   unsigned int offset, int val)
> > +{
> > +       struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> > +       int ret;
> > +
> > +       guard(mutex)(&data->lock);
> > +
> > +       /* Set direction to output */
> > +       ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> > +                              NCT6694_GPO_DIR + data->group,
> > +                              NCT6694_GPIO_LEN, &data->xmit_buf);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       data->xmit_buf |= (1 << offset);
>
> data->xmit_buf |= BIT(offset);
>

Okay! Fix it in the v4.

> > +       if (val)
> > +               data->xmit_buf |= (1 << offset);
> > +       else
> > +               data->xmit_buf &= ~(1 << offset);
>
> Same
>

Okay! Fix it in the v4.

> > +static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
> > +                             int val)
> > +{
> (...)
> > +       if (val)
> > +               data->xmit_buf |= (1 << offset);
> > +       else
> > +               data->xmit_buf &= ~(1 << offset);
>
> Same
>

Okay! Fix it in the v4.

> > +static irqreturn_t nct6694_irq_handler(int irq, void *priv)
> > +{
> > +       struct nct6694_gpio_data *data = priv;
> > +       unsigned char status;
> > +
> > +       guard(mutex)(&data->lock);
> > +
> > +       nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> > +                        NCT6694_GPI_STS + data->group,
> > +                        NCT6694_GPIO_LEN, &data->xmit_buf);
> > +
> > +       status = data->xmit_buf;
> > +
> > +       while (status) {
> > +               int bit = __ffs(status);
> > +
> > +               data->xmit_buf = BIT(bit);
> > +               handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
> > +               status &= ~(1 << bit);
>
> Same
>
> Just use BIT() consistently please.
>

Okay! Fix it in the v4.

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694
  2024-12-20 12:45   ` Linus Walleij
@ 2024-12-23  2:04     ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-23  2:04 UTC (permalink / raw)
  To: Linus Walleij
  Cc: tmyu0, lee, brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Linus,

Thank you for your reply,

Linus Walleij <linus.walleij@linaro.org> 於 2024年12月20日 週五 下午8:45寫道:
>
> Hi Ming,
>
> thanks for your patch!
>
...
> > +       while (*int_status) {
> > +               int irq = __ffs(*int_status);
> > +
> > +               if (*int_status & (1 << irq))
> > +                       generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> > +
> > +               *int_status &= ~(1 << irq);
>
> What about doing what you do in the GPIO driver and
> #include <linux/bits.h>
>
> And search and replace "(1 << irq)" with BIT(irq)?
>
> PS the main reason we do this is because
>
> int a = (1 << 31);
>
> becomes a negative number on 32bit machines, and
> can lead to confusing side effects. BIT() always work
> on unsigned.
>

Understood! I will make the modifications in the v4.

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support
  2024-12-16  6:21     ` Ming Yu
@ 2024-12-23  9:03       ` Ming Yu
  0 siblings, 0 replies; 39+ messages in thread
From: Ming Yu @ 2024-12-23  9:03 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Hi Marc,

> > > +struct nct6694_can_priv {
> > > +     struct can_priv can;    /* must be the first member */
> > > +     struct net_device *ndev;
> > > +     struct nct6694 *nct6694;
> > > +     struct mutex lock;
> >
> > What does lock protect?
> >
>
> The lock is used to protect tx_buf and rx_buf for each CAN device.
>
> > > +     struct sk_buff *tx_skb;
> > > +     struct workqueue_struct *wq;
> > > +     struct work_struct tx_work;
> > > +     unsigned char *tx_buf;
> > void *
> > > +     unsigned char *rx_buf;
> > void *
> > > +     unsigned char can_idx;
> > > +     bool tx_busy;
> >
> > IMHO it makes no sense to have tx_skb and tx_busy
> >
>
> Okay! I will revisit these to evaluate whether they are still necessary.
>
> > > +};
> > > +

I think there needs to be a tx_skb to record the skb passed by
start_xmit(), otherwise it can't handle the can_frame in tx_work. If
this is not necessary, could you please explain?
In addition, the tx flow is based on the implementation in
https://elixir.bootlin.com/linux/v6.12.6/source/drivers/net/can/spi/mcp251x.c

Thanks,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support
  2024-12-10 10:45 ` [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2024-12-26  0:43   ` Andi Shyti
  2024-12-26  2:06     ` Ming Yu
  0 siblings, 1 reply; 39+ messages in thread
From: Andi Shyti @ 2024-12-26  0:43 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Hi Ming,

> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 I2C adapter driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +/* Host interface */

What does it mean "Host interface"?

> +#define NCT6694_I2C_MOD		0x03
> +
> +/* Message Channel*/
> +/* Command 00h */

This comments are meaningless, either make them clearer or remove
them.

> +#define NCT6694_I2C_CMD0_OFFSET	0x0000	/* OFFSET = SEL|CMD */

I find this comment quite meaningless. Can you please make it
clearer?

> +#define NCT6694_I2C_CMD0_LEN	0x90
> +
> +enum i2c_baudrate {
> +	I2C_BR_25K = 0,
> +	I2C_BR_50K,
> +	I2C_BR_100K,
> +	I2C_BR_200K,
> +	I2C_BR_400K,
> +	I2C_BR_800K,
> +	I2C_BR_1M
> +};
> +
> +struct __packed nct6694_i2c_cmd0 {
> +	u8 port;
> +	u8 br;
> +	u8 addr;
> +	u8 w_cnt;
> +	u8 r_cnt;
> +	u8 rsv[11];
> +	u8 write_data[0x40];
> +	u8 read_data[0x40];
> +};
> +
> +struct nct6694_i2c_data {
> +	struct nct6694 *nct6694;
> +	struct i2c_adapter adapter;
> +	unsigned char *xmit_buf;

why isn't this a nct6694_i2c_cmd0 type?

> +	unsigned char port;
> +	unsigned char br;
> +};
> +
> +static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> +	struct nct6694_i2c_data *data = adap->algo_data;
> +	struct nct6694_i2c_cmd0 *cmd = (struct nct6694_i2c_cmd0 *)data->xmit_buf;
> +	int ret, i;
> +
> +	for (i = 0; i < num ; i++) {
> +		struct i2c_msg *msg_temp = &msgs[i];
> +
> +		memset(data->xmit_buf, 0, sizeof(struct nct6694_i2c_cmd0));
> +
> +		if (msg_temp->len > 64)
> +			return -EPROTO;
> +		cmd->port = data->port;
> +		cmd->br = data->br;
> +		cmd->addr = i2c_8bit_addr_from_msg(msg_temp);
> +		if (msg_temp->flags & I2C_M_RD) {
> +			cmd->r_cnt = msg_temp->len;
> +			ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
> +						NCT6694_I2C_CMD0_OFFSET,
> +						NCT6694_I2C_CMD0_LEN,
> +						cmd);
> +			if (ret < 0)
> +				return 0;

why not return ret?

> +
> +			memcpy(msg_temp->buf, cmd->read_data, msg_temp->len);
> +		} else {
> +			cmd->w_cnt = msg_temp->len;
> +			memcpy(cmd->write_data, msg_temp->buf, msg_temp->len);
> +			ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
> +						NCT6694_I2C_CMD0_OFFSET,
> +						NCT6694_I2C_CMD0_LEN,
> +						cmd);
> +			if (ret < 0)
> +				return 0;
> +		}
> +	}
> +
> +	return num;
> +}
> +
> +static u32 nct6694_func(struct i2c_adapter *adapter)
> +{
> +	return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);

parenthesis are not needed.

> +}

...

> +static struct platform_driver nct6694_i2c_driver = {
> +	.driver = {
> +		.name	= "nct6694-i2c",
> +	},
> +	.probe		= nct6694_i2c_probe,
> +	.remove		= nct6694_i2c_remove,
> +};
> +
> +module_platform_driver(nct6694_i2c_driver);

what I meant in v1 is to try using module_auxiliary_driver().
Check, e.g., i2c-ljca.c or i2c-keba.c.

Andi

> +MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-i2c");
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support
  2024-12-26  0:43   ` Andi Shyti
@ 2024-12-26  2:06     ` Ming Yu
  2024-12-26  9:25       ` Andi Shyti
  0 siblings, 1 reply; 39+ messages in thread
From: Ming Yu @ 2024-12-26  2:06 UTC (permalink / raw)
  To: Andi Shyti
  Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Dear Andi,

Thank you for your comments,

Andi Shyti <andi.shyti@kernel.org> 於 2024年12月26日 週四 上午8:43寫道:
>
> > +#include <linux/i2c.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +
> > +/* Host interface */
>
> What does it mean "Host interface"?
>
> > +#define NCT6694_I2C_MOD              0x03
> > +
> > +/* Message Channel*/
> > +/* Command 00h */
>
> This comments are meaningless, either make them clearer or remove
> them.
>
> > +#define NCT6694_I2C_CMD0_OFFSET      0x0000  /* OFFSET = SEL|CMD */
>
> I find this comment quite meaningless. Can you please make it
> clearer?
>

I have already updated these structures and comments following
suggestions from other reviewers, and I plan to include the changes in
the next patch submission.

> > +#define NCT6694_I2C_CMD0_LEN 0x90
> > +
> > +enum i2c_baudrate {
> > +     I2C_BR_25K = 0,
> > +     I2C_BR_50K,
> > +     I2C_BR_100K,
> > +     I2C_BR_200K,
> > +     I2C_BR_400K,
> > +     I2C_BR_800K,
> > +     I2C_BR_1M
> > +};
> > +
> > +struct __packed nct6694_i2c_cmd0 {
> > +     u8 port;
> > +     u8 br;
> > +     u8 addr;
> > +     u8 w_cnt;
> > +     u8 r_cnt;
> > +     u8 rsv[11];
> > +     u8 write_data[0x40];
> > +     u8 read_data[0x40];
> > +};
> > +
> > +struct nct6694_i2c_data {
> > +     struct nct6694 *nct6694;
> > +     struct i2c_adapter adapter;
> > +     unsigned char *xmit_buf;
>
> why isn't this a nct6694_i2c_cmd0 type?
>

Fix it in v4.

> > +     unsigned char port;
> > +     unsigned char br;
> > +};
> > +
> > +static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> > +{
> > +     struct nct6694_i2c_data *data = adap->algo_data;
> > +     struct nct6694_i2c_cmd0 *cmd = (struct nct6694_i2c_cmd0 *)data->xmit_buf;
> > +     int ret, i;
> > +
> > +     for (i = 0; i < num ; i++) {
> > +             struct i2c_msg *msg_temp = &msgs[i];
> > +
> > +             memset(data->xmit_buf, 0, sizeof(struct nct6694_i2c_cmd0));
> > +
> > +             if (msg_temp->len > 64)
> > +                     return -EPROTO;
> > +             cmd->port = data->port;
> > +             cmd->br = data->br;
> > +             cmd->addr = i2c_8bit_addr_from_msg(msg_temp);
> > +             if (msg_temp->flags & I2C_M_RD) {
> > +                     cmd->r_cnt = msg_temp->len;
> > +                     ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
> > +                                             NCT6694_I2C_CMD0_OFFSET,
> > +                                             NCT6694_I2C_CMD0_LEN,
> > +                                             cmd);
> > +                     if (ret < 0)
> > +                             return 0;
>
> why not return ret?
>

Fix it in v4.

> > +
> > +                     memcpy(msg_temp->buf, cmd->read_data, msg_temp->len);
> > +             } else {
> > +                     cmd->w_cnt = msg_temp->len;
> > +                     memcpy(cmd->write_data, msg_temp->buf, msg_temp->len);
> > +                     ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
> > +                                             NCT6694_I2C_CMD0_OFFSET,
> > +                                             NCT6694_I2C_CMD0_LEN,
> > +                                             cmd);
> > +                     if (ret < 0)
> > +                             return 0;
> > +             }
> > +     }
> > +
> > +     return num;
> > +}
> > +
> > +static u32 nct6694_func(struct i2c_adapter *adapter)
> > +{
> > +     return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
>
> parenthesis are not needed.
>

Fix it in v4.

> > +}
>
> ...
>
> > +static struct platform_driver nct6694_i2c_driver = {
> > +     .driver = {
> > +             .name   = "nct6694-i2c",
> > +     },
> > +     .probe          = nct6694_i2c_probe,
> > +     .remove         = nct6694_i2c_remove,
> > +};
> > +
> > +module_platform_driver(nct6694_i2c_driver);
>
> what I meant in v1 is to try using module_auxiliary_driver().
> Check, e.g., i2c-ljca.c or i2c-keba.c.
>

I think the NCT6694  is an MCU-based device, and the current
implementation is as an MFD driver. Are you suggesting it should
instead be implemented as an auxiliary device driver? If so, would
that mean all related drivers need to be revised accordingly?

Best regards,
Ming

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support
  2024-12-26  2:06     ` Ming Yu
@ 2024-12-26  9:25       ` Andi Shyti
  0 siblings, 0 replies; 39+ messages in thread
From: Andi Shyti @ 2024-12-26  9:25 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc

Hi Ming,

> > > +static struct platform_driver nct6694_i2c_driver = {
> > > +     .driver = {
> > > +             .name   = "nct6694-i2c",
> > > +     },
> > > +     .probe          = nct6694_i2c_probe,
> > > +     .remove         = nct6694_i2c_remove,
> > > +};
> > > +
> > > +module_platform_driver(nct6694_i2c_driver);
> >
> > what I meant in v1 is to try using module_auxiliary_driver().
> > Check, e.g., i2c-ljca.c or i2c-keba.c.
> 
> I think the NCT6694  is an MCU-based device, and the current
> implementation is as an MFD driver. Are you suggesting it should
> instead be implemented as an auxiliary device driver? If so, would
> that mean all related drivers need to be revised accordingly?

No worries, module_platform_driver() is also fine.

Andi

^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2024-12-26  9:25 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-10 10:45 [PATCH v3 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
2024-12-10 10:45 ` [PATCH v3 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-12-10 10:57   ` Mateusz Polchlopek
2024-12-11  1:56     ` Jakub Kicinski
2024-12-11  7:16       ` Mateusz Polchlopek
2024-12-10 14:38   ` Krzysztof Kozlowski
2024-12-12  5:27     ` Ming Yu
2024-12-11 16:44   ` Christophe JAILLET
2024-12-12  7:01     ` Ming Yu
2024-12-12  7:13       ` Christophe JAILLET
2024-12-20 12:45   ` Linus Walleij
2024-12-23  2:04     ` Ming Yu
2024-12-10 10:45 ` [PATCH v3 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
2024-12-10 12:46   ` Bartosz Golaszewski
2024-12-12  6:58     ` Ming Yu
2024-12-20 12:41   ` Linus Walleij
2024-12-23  2:02     ` Ming Yu
2024-12-10 10:45 ` [PATCH v3 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
2024-12-26  0:43   ` Andi Shyti
2024-12-26  2:06     ` Ming Yu
2024-12-26  9:25       ` Andi Shyti
2024-12-10 10:45 ` [PATCH v3 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
2024-12-11  9:59   ` Marc Kleine-Budde
2024-12-11 15:25     ` Vincent Mailhol
2024-12-16  6:58       ` Ming Yu
2024-12-16 17:14         ` Vincent Mailhol
2024-12-16  6:21     ` Ming Yu
2024-12-23  9:03       ` Ming Yu
2024-12-10 10:45 ` [PATCH v3 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
2024-12-10 15:22   ` Guenter Roeck
2024-12-12  6:26     ` Ming Yu
2024-12-10 10:45 ` [PATCH v3 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-12-10 15:58   ` Guenter Roeck
2024-12-12  7:09     ` Ming Yu
2024-12-12 16:10   ` Vincent Mailhol
2024-12-16  7:04     ` Ming Yu
2024-12-10 10:45 ` [PATCH v3 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
2024-12-16 10:41   ` Alexandre Belloni
2024-12-23  1:50     ` Ming Yu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).