linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers
@ 2025-06-04  4:14 a0282524688
  2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
                   ` (6 more replies)
  0 siblings, 7 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

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

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

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

Changes since version 11:
- Use platform_device's id to replace IDA
- Modify the irq_domain_add_simple() to irq_domain_create_simple() in
  nct6694.c
- Update struct data_bittiming_params related part in nct6694_canfd.c
- Fix the typo in the header in nct6694-hwmon.c

Changes since version 10:
- Add change log for each patch
- Fix mfd_cell to MFD_CELL_NAME() in nct6694.c
- Implement IDA to allocate id in gpio-nct6694.c, i2c-nct6694.c,
  nct6694_canfd.c and nct6694_wdt.c
- Add header <linux/bitfield.h> in nct6694_canfd.c
- Add support to config tdc in nct6694_canfd.c
- Add module parameters to configure WDT's timeout and pretimeout value
  in nct6694_wdt.c

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

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

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

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

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

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

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

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

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


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

 MAINTAINERS                         |  12 +
 drivers/gpio/Kconfig                |  12 +
 drivers/gpio/Makefile               |   1 +
 drivers/gpio/gpio-nct6694.c         | 479 ++++++++++++++
 drivers/hwmon/Kconfig               |  10 +
 drivers/hwmon/Makefile              |   1 +
 drivers/hwmon/nct6694-hwmon.c       | 949 ++++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig          |  10 +
 drivers/i2c/busses/Makefile         |   1 +
 drivers/i2c/busses/i2c-nct6694.c    | 174 +++++
 drivers/mfd/Kconfig                 |  15 +
 drivers/mfd/Makefile                |   2 +
 drivers/mfd/nct6694.c               | 386 +++++++++++
 drivers/net/can/usb/Kconfig         |  11 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 820 ++++++++++++++++++++++++
 drivers/rtc/Kconfig                 |  10 +
 drivers/rtc/Makefile                |   1 +
 drivers/rtc/rtc-nct6694.c           | 297 +++++++++
 drivers/watchdog/Kconfig            |  11 +
 drivers/watchdog/Makefile           |   1 +
 drivers/watchdog/nct6694_wdt.c      | 291 +++++++++
 include/linux/mfd/nct6694.h         |  98 +++
 23 files changed, 3593 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c
 create mode 100644 drivers/hwmon/nct6694-hwmon.c
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c
 create mode 100644 drivers/rtc/rtc-nct6694.c
 create mode 100644 drivers/watchdog/nct6694_wdt.c
 create mode 100644 include/linux/mfd/nct6694.h

-- 
2.34.1


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

* [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04 10:11   ` Oliver Neukum
  2025-06-12 14:00   ` Lee Jones
  2025-06-04  4:14 ` [PATCH v12 2/7] gpio: Add Nuvoton NCT6694 GPIO support a0282524688
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

The Nuvoton NCT6694 provides an USB interface to the host to
access its features.

Sub-devices can use the USB functions nct6694_read_msg() and
nct6694_write_msg() to issue a command. They can also request
interrupt that will be called when the USB device receives its
interrupt pipe.

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Modify the irq_domain_add_simple() to irq_domain_create_simple()
- Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA
  in sub-drivers

Changes since version 10:
- Add change log for the patch
- Fix mfd_cell to MFD_CELL_NAME()
- Remove unnecessary blank line

Changes since version 9:
- Add KernelDoc to exported functions

Changes since version 8:
- Modify the signed-off-by with my work address
- Rename all MFD cell names to "nct6694-xxx"
- Fix some comments in nct6694.c and in nct6694.h

Changes since version 7:
- Add error handling for devm_mutex_init()

Changes since version 6:

Changes since version 5:
- Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC()
- Drop unnecessary macros

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Add header <linux/bits.h> and use BIT macro
- Modify mutex_init() to devm_mutex_init()

Changes since version 2:

Changes since version 1:
- Implement IRQ domain to handle IRQ demux
- Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API
- Add command structure
- Fix USB functions
- Sort each driver's header files alphabetically

 MAINTAINERS                 |   6 +
 drivers/mfd/Kconfig         |  15 ++
 drivers/mfd/Makefile        |   2 +
 drivers/mfd/nct6694.c       | 386 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/nct6694.h |  98 +++++++++
 5 files changed, 507 insertions(+)
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 include/linux/mfd/nct6694.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 98201e1f4ab5..29d2d05bac22 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17679,6 +17679,12 @@ F:	drivers/nubus/
 F:	include/linux/nubus.h
 F:	include/uapi/linux/nubus.h
 
+NUVOTON NCT6694 MFD DRIVER
+M:	Ming Yu <tmyu0@nuvoton.com>
+S:	Supported
+F:	drivers/mfd/nct6694.c
+F:	include/linux/mfd/nct6694.h
+
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
 M:	Antonino Daplas <adaplas@gmail.com>
 L:	linux-fbdev@vger.kernel.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 96992af22565..489c1950f1ac 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1078,6 +1078,21 @@ config MFD_MENF21BMC
 	  This driver can also be built as a module. If so the module
 	  will be called menf21bmc.
 
+config MFD_NCT6694
+	tristate "Nuvoton NCT6694 support"
+	select MFD_CORE
+	depends on USB
+	help
+	  This enables support for the Nuvoton USB device NCT6694, which shares
+	  peripherals.
+	  The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
+	  6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
+	  PWM, and RTC.
+	  This driver provides core APIs to access the NCT6694 hardware
+	  monitoring and control features.
+	  Additional drivers must be enabled to utilize the specific
+	  functionalities of the device.
+
 config MFD_OCELOT
 	tristate "Microsemi Ocelot External Control Support"
 	depends on SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5e5cc279af60..a96204d938fc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
 obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
 obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
 
+obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
+
 obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
 
 ocelot-soc-objs			:= ocelot-core.o ocelot-spi.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
new file mode 100644
index 000000000000..82d378ee47ed
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 core driver using USB interface to provide
+ * access to the NCT6694 hardware monitoring and control features.
+ *
+ * The NCT6694 is an integrated controller that provides GPIO, I2C,
+ * CAN, WDT, HWMON and RTC management.
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+static const struct mfd_cell nct6694_devs[] = {
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
+	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
+
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
+	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
+
+	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
+	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
+
+	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
+	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
+
+	MFD_CELL_NAME("nct6694-hwmon"),
+
+	MFD_CELL_NAME("nct6694-rtc"),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+{
+	switch (err_status) {
+	case NCT6694_NO_ERROR:
+		return 0;
+	case NCT6694_NOT_SUPPORT_ERROR:
+		dev_err(nct6694->dev, "Command is not supported!\n");
+		break;
+	case NCT6694_NO_RESPONSE_ERROR:
+		dev_warn(nct6694->dev, "Command received no response!\n");
+		break;
+	case NCT6694_TIMEOUT_ERROR:
+		dev_warn(nct6694->dev, "Command timed out!\n");
+		break;
+	case NCT6694_PENDING:
+		dev_err(nct6694->dev, "Command is pending!\n");
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return -EIO;
+}
+
+/**
+ * nct6694_read_msg() - Read message from NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer to store the response data
+ *
+ * Sends a command to the NCT6694 device and reads the response.
+ * The command header is specified in @cmd_hd, and the response
+ * data is stored in @buf.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+	union nct6694_usb_msg *msg = nct6694->usb_msg;
+	struct usb_device *udev = nct6694->udev;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+	msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+
+	/* Send command packet to USB device */
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	/* Receive data packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	if (rx_len != le16_to_cpu(cmd_hd->len)) {
+		dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
+			le16_to_cpu(cmd_hd->len), rx_len);
+		return -EIO;
+	}
+
+	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_read_msg);
+
+/**
+ * nct6694_write_msg() - Write message to NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer containing the data to be sent
+ *
+ * Sends a command to the NCT6694 device and writes the data
+ * from @buf. The command header is specified in @cmd_hd.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+	union nct6694_usb_msg *msg = nct6694->usb_msg;
+	struct usb_device *udev = nct6694->udev;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+	msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+
+	/* Send command packet to USB device */
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	/* Send data packet to USB device */
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
+			   le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	/* Receive data packet from USB device */
+	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+	if (ret)
+		return ret;
+
+	if (rx_len != le16_to_cpu(cmd_hd->len)) {
+		dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
+			le16_to_cpu(cmd_hd->len), rx_len);
+		return -EIO;
+	}
+
+	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+	struct nct6694 *nct6694 = urb->context;
+	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);
+
+		generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+		*int_status &= ~BIT(irq);
+	}
+
+resubmit:
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret)
+		dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe",  ERR_PTR(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);
+	irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+	nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+	irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+	nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static const 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 usb_endpoint_descriptor *int_endpoint;
+	struct usb_host_interface *interface;
+	struct device *dev = &iface->dev;
+	struct nct6694 *nct6694;
+	int pipe, maxp;
+	int ret;
+
+	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);
+
+	nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
+	if (!nct6694->usb_msg)
+		return -ENOMEM;
+
+	nct6694->int_buffer = devm_kzalloc(dev, maxp, 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_create_simple(NULL, NCT6694_NR_IRQS, 0,
+						   &nct6694_irq_domain_ops,
+						   nct6694);
+	if (!nct6694->domain) {
+		ret = -ENODEV;
+		goto err_urb;
+	}
+
+	nct6694->dev = dev;
+	nct6694->udev = udev;
+
+	ret = devm_mutex_init(dev, &nct6694->access_lock);
+	if (ret)
+		goto err_irq;
+
+	ret = devm_mutex_init(dev, &nct6694->irq_lock);
+	if (ret)
+		goto err_irq;
+
+	interface = iface->cur_altsetting;
+
+	int_endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(int_endpoint)) {
+		ret = -ENODEV;
+		goto err_irq;
+	}
+
+	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_irq;
+
+	usb_set_intfdata(iface, nct6694);
+
+	ret = devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL);
+	if (ret)
+		goto err_mfd;
+
+	return 0;
+
+err_mfd:
+	usb_kill_urb(nct6694->int_in_urb);
+err_irq:
+	irq_domain_remove(nct6694->domain);
+err_urb:
+	usb_free_urb(nct6694->int_in_urb);
+	return ret;
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+	struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+	usb_kill_urb(nct6694->int_in_urb);
+	irq_domain_remove(nct6694->domain);
+	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("Nuvoton NCT6694 core driver");
+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..5e172609be3f
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 USB transaction and data structure.
+ */
+
+#ifndef __MFD_NCT6694_H
+#define __MFD_NCT6694_H
+
+#define NCT6694_VENDOR_ID	0x0416
+#define NCT6694_PRODUCT_ID	0x200B
+#define NCT6694_INT_IN_EP	0x81
+#define NCT6694_BULK_IN_EP	0x02
+#define NCT6694_BULK_OUT_EP	0x03
+
+#define NCT6694_HCTRL_SET	0x40
+#define NCT6694_HCTRL_GET	0x80
+
+#define NCT6694_URB_TIMEOUT	1000
+
+enum nct6694_irq_id {
+	NCT6694_IRQ_GPIO0 = 0,
+	NCT6694_IRQ_GPIO1,
+	NCT6694_IRQ_GPIO2,
+	NCT6694_IRQ_GPIO3,
+	NCT6694_IRQ_GPIO4,
+	NCT6694_IRQ_GPIO5,
+	NCT6694_IRQ_GPIO6,
+	NCT6694_IRQ_GPIO7,
+	NCT6694_IRQ_GPIO8,
+	NCT6694_IRQ_GPIO9,
+	NCT6694_IRQ_GPIOA,
+	NCT6694_IRQ_GPIOB,
+	NCT6694_IRQ_GPIOC,
+	NCT6694_IRQ_GPIOD,
+	NCT6694_IRQ_GPIOE,
+	NCT6694_IRQ_GPIOF,
+	NCT6694_IRQ_CAN0,
+	NCT6694_IRQ_CAN1,
+	NCT6694_IRQ_RTC,
+	NCT6694_NR_IRQS,
+};
+
+enum nct6694_response_err_status {
+	NCT6694_NO_ERROR = 0,
+	NCT6694_FORMAT_ERROR,
+	NCT6694_RESERVED1,
+	NCT6694_RESERVED2,
+	NCT6694_NOT_SUPPORT_ERROR,
+	NCT6694_NO_RESPONSE_ERROR,
+	NCT6694_TIMEOUT_ERROR,
+	NCT6694_PENDING,
+};
+
+struct __packed nct6694_cmd_header {
+	u8 rsv1;
+	u8 mod;
+	union __packed {
+		__le16 offset;
+		struct __packed {
+			u8 cmd;
+			u8 sel;
+		};
+	};
+	u8 hctrl;
+	u8 rsv2;
+	__le16 len;
+};
+
+struct __packed nct6694_response_header {
+	u8 sequence_id;
+	u8 sts;
+	u8 reserved[4];
+	__le16 len;
+};
+
+union __packed nct6694_usb_msg {
+	struct nct6694_cmd_header cmd_header;
+	struct nct6694_response_header response_header;
+};
+
+struct nct6694 {
+	struct device *dev;
+	struct irq_domain *domain;
+	struct mutex access_lock;
+	struct mutex irq_lock;
+	struct urb *int_in_urb;
+	struct usb_device *udev;
+	union nct6694_usb_msg *usb_msg;
+	unsigned char *int_buffer;
+	unsigned int irq_enable;
+};
+
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+
+#endif
-- 
2.34.1


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

* [PATCH v12 2/7] gpio: Add Nuvoton NCT6694 GPIO support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
  2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04  4:14 ` [PATCH v12 3/7] i2c: Add Nuvoton NCT6694 I2C support a0282524688
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu,
	Bartosz Golaszewski

From: Ming Yu <tmyu0@nuvoton.com>

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

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Use platform_device's id to replace IDA

Changes since version 10:
- Implement IDA to allocate id

Changes since version 9:
- Add devm_add_action_or_reset() to dispose irq mapping

Changes since version 8:
- Modify the signed-off-by with my work address
- Add irq_dispose_mapping() in the error handling path and in the remove
  function

Changes since version 7:
- Add error handling for devm_mutex_init()

Changes since version 6:

Changes since version 5:
- Modify the module name and the driver name consistently

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Add header <linux/bits.h> and use BIT macro
- Modify mutex_init() to devm_mutex_init()

Changes since version 2:
- Add MODULE_ALIAS()
- Modify gpio line names be a local variable in gpio-nct6694.c
- Drop unnecessary platform_get_drvdata() in gpio-nct6694.c

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration
- Drop unnecessary header
- Add gpio line names

 MAINTAINERS                 |   1 +
 drivers/gpio/Kconfig        |  12 +
 drivers/gpio/Makefile       |   1 +
 drivers/gpio/gpio-nct6694.c | 479 ++++++++++++++++++++++++++++++++++++
 4 files changed, 493 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 29d2d05bac22..6851f6bb5fc8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17682,6 +17682,7 @@ F:	include/uapi/linux/nubus.h
 NUVOTON NCT6694 MFD DRIVER
 M:	Ming Yu <tmyu0@nuvoton.com>
 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 44f922e10db2..eac28f2b6cfa 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1500,6 +1500,18 @@ config GPIO_MAX77759
 	  This driver can also be built as a module. If so, the module will be
 	  called gpio-max77759.
 
+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 88dedd298256..567e0eca3036 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -127,6 +127,7 @@ obj-$(CONFIG_GPIO_MT7621)		+= gpio-mt7621.o
 obj-$(CONFIG_GPIO_MVEBU)		+= gpio-mvebu.o
 obj-$(CONFIG_GPIO_MXC)			+= gpio-mxc.o
 obj-$(CONFIG_GPIO_MXS)			+= gpio-mxs.o
+obj-$(CONFIG_GPIO_NCT6694)		+= gpio-nct6694.o
 obj-$(CONFIG_GPIO_NOMADIK)		+= gpio-nomadik.o
 obj-$(CONFIG_GPIO_NPCM_SGPIO)		+= gpio-npcm-sgpio.o
 obj-$(CONFIG_GPIO_OCTEON)		+= gpio-octeon.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 000000000000..06b299988a2b
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 GPIO controller.
+ * This defines the module type used for communication with the NCT6694
+ * GPIO controller over the USB interface.
+ */
+#define NCT6694_GPIO_MOD	0xFF
+
+#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 reg_val;
+	unsigned char irq_trig_falling;
+	unsigned char irq_trig_rising;
+
+	/* Current gpio group */
+	unsigned char group;
+	int irq;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	return !(BIT(offset) & data->reg_val);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	data->reg_val &= ~BIT(offset);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+				    unsigned int offset, int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	/* Set direction to output */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	data->reg_val |= BIT(offset);
+	ret = nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	/* Then set output level */
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	if (val)
+		data->reg_val |= BIT(offset);
+	else
+		data->reg_val &= ~BIT(offset);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	if (BIT(offset) & data->reg_val) {
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+		if (ret < 0)
+			return ret;
+
+		return !!(BIT(offset) & data->reg_val);
+	}
+
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPI_DATA + data->group);
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	return !!(BIT(offset) & data->reg_val);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+			      int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+
+	guard(mutex)(&data->lock);
+
+	nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+
+	if (val)
+		data->reg_val |= BIT(offset);
+	else
+		data->reg_val &= ~BIT(offset);
+
+	nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+			      unsigned long config)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_TYPE + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	switch (pinconf_to_config_param(config)) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		data->reg_val |= BIT(offset);
+		break;
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		data->reg_val &= ~BIT(offset);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+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);
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPIO_VALID + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	*valid_mask = data->reg_val;
+
+	return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+	struct nct6694_gpio_data *data = priv;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_STS + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	unsigned char status;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret)
+		return IRQ_NONE;
+
+	status = data->reg_val;
+
+	while (status) {
+		int bit = __ffs(status);
+
+		data->reg_val = BIT(bit);
+		handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+		status &= ~BIT(bit);
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPI_CLR + data->group);
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+	if (ret)
+		return ret;
+
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+	return nct6694_read_msg(data->nct6694, &cmd_hd, &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);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+
+	scoped_guard(mutex, &data->lock) {
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+	}
+
+	mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+	.name			= "gpio-nct6694",
+	.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 void nct6694_irq_dispose_mapping(void *d)
+{
+	struct nct6694_gpio_data *data = d;
+
+	irq_dispose_mapping(data->irq);
+}
+
+static int nct6694_gpio_probe(struct platform_device *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, i;
+	char **names;
+
+	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",
+					  pdev->id, i);
+		if (!names[i])
+			return -ENOMEM;
+	}
+
+	data->irq = irq_create_mapping(nct6694->domain,
+				       NCT6694_IRQ_GPIO0 + pdev->id);
+	if (!data->irq)
+		return -EINVAL;
+
+	ret = devm_add_action_or_reset(dev, nct6694_irq_dispose_mapping, data);
+	if (ret)
+		return ret;
+
+	data->nct6694 = nct6694;
+	data->group = pdev->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;
+
+	platform_set_drvdata(pdev, data);
+
+	ret = devm_mutex_init(dev, &data->lock);
+	if (ret)
+		return ret;
+
+	ret = devm_mutex_init(dev, &data->irq_lock);
+	if (ret)
+		return ret;
+
+	ret = nct6694_get_irq_trig(data);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+		return ret;
+	}
+
+	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, data->irq, NULL, nct6694_irq_handler,
+					IRQF_ONESHOT | IRQF_SHARED,
+					"gpio-nct6694", data);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to request irq\n");
+		return ret;
+	}
+
+	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] 33+ messages in thread

* [PATCH v12 3/7] i2c: Add Nuvoton NCT6694 I2C support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
  2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
  2025-06-04  4:14 ` [PATCH v12 2/7] gpio: Add Nuvoton NCT6694 GPIO support a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04  4:14 ` [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support a0282524688
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

Each I2C controller uses the default baudrate of 100kHz, which
can be overridden via module parameters.

Acked-by: Andi Shyti <andi.shyti@kernel.org>
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Use platform_device's id to replace IDA

Changes since version 10:
- Implement IDA to allocate id

Changes since version 9:

Changes since version 8:
- Modify the signed-off-by with my work address
- Add module parameters to configure I2C's baudrate

Changes since version 7:

Changes since version 6:

Changes since version 5:
- Modify the module name and the driver name consistently

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments

Changes since version 2:
- Add MODULE_ALIAS()

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration

 MAINTAINERS                      |   1 +
 drivers/i2c/busses/Kconfig       |  10 ++
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-nct6694.c | 174 +++++++++++++++++++++++++++++++
 4 files changed, 186 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6851f6bb5fc8..be6486b1a78a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17683,6 +17683,7 @@ NUVOTON NCT6694 MFD DRIVER
 M:	Ming Yu <tmyu0@nuvoton.com>
 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 48c5ab832009..b0c738ecbfeb 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1358,6 +1358,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 04db855fdfd6..fe8cf6325fc9 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..7ffac965a01f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 I2C adapter driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 I2C controller.
+ * This defines the module type used for communication with the NCT6694
+ * I2C controller over the USB interface.
+ */
+#define NCT6694_I2C_MOD		0x03
+
+/* Command 00h - I2C Deliver */
+#define NCT6694_I2C_DELIVER	0x00
+#define NCT6694_I2C_DELIVER_SEL	0x00
+
+#define NCT6694_I2C_MAX_DEVS	6
+
+static unsigned char br_reg[NCT6694_I2C_MAX_DEVS] = {[0 ... (NCT6694_I2C_MAX_DEVS - 1)] = 0xFF};
+
+module_param_array(br_reg, byte, NULL, 0644);
+MODULE_PARM_DESC(br_reg,
+		 "I2C Baudrate register per adapter: (0=25K, 1=50K, 2=100K, 3=200K, 4=400K, 5=800K, 6=1M), default=2");
+
+enum nct6694_i2c_baudrate {
+	NCT6694_I2C_BR_25K = 0,
+	NCT6694_I2C_BR_50K,
+	NCT6694_I2C_BR_100K,
+	NCT6694_I2C_BR_200K,
+	NCT6694_I2C_BR_400K,
+	NCT6694_I2C_BR_800K,
+	NCT6694_I2C_BR_1M
+};
+
+struct __packed nct6694_i2c_deliver {
+	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 device *dev;
+	struct nct6694 *nct6694;
+	struct i2c_adapter adapter;
+	struct nct6694_i2c_deliver deliver;
+	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_deliver *deliver = &data->deliver;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_I2C_MOD,
+		.cmd = NCT6694_I2C_DELIVER,
+		.sel = NCT6694_I2C_DELIVER_SEL,
+		.len = cpu_to_le16(sizeof(*deliver))
+	};
+	int ret, i;
+
+	for (i = 0; i < num; i++) {
+		struct i2c_msg *msg_temp = &msgs[i];
+
+		memset(deliver, 0, sizeof(*deliver));
+
+		if (msg_temp->len > 64)
+			return -EPROTO;
+
+		deliver->port = data->port;
+		deliver->br = data->br;
+		deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
+		if (msg_temp->flags & I2C_M_RD) {
+			deliver->r_cnt = msg_temp->len;
+			ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+			if (ret < 0)
+				return ret;
+
+			memcpy(msg_temp->buf, deliver->read_data, msg_temp->len);
+		} else {
+			deliver->w_cnt = msg_temp->len;
+			memcpy(deliver->write_data, msg_temp->buf, msg_temp->len);
+			ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	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_set_baudrate(struct nct6694_i2c_data *data)
+{
+	if (data->port >= NCT6694_I2C_MAX_DEVS) {
+		dev_err(data->dev, "Invalid I2C port index %d\n", data->port);
+		return -EINVAL;
+	}
+
+	if (br_reg[data->port] > NCT6694_I2C_BR_1M) {
+		dev_warn(data->dev, "Invalid baudrate %d for I2C%d, using 100K\n",
+			 br_reg[data->port], data->port);
+		br_reg[data->port] = NCT6694_I2C_BR_100K;
+	}
+
+	data->br = br_reg[data->port];
+
+	return 0;
+}
+
+static int nct6694_i2c_probe(struct platform_device *pdev)
+{
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_i2c_data *data;
+	int ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->dev = &pdev->dev;
+	data->nct6694 = nct6694;
+	data->port = pdev->id;
+
+	ret = nct6694_i2c_set_baudrate(data);
+	if (ret)
+		return ret;
+
+	sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", pdev->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 devm_i2c_add_adapter(&pdev->dev, &data->adapter);
+}
+
+static struct platform_driver nct6694_i2c_driver = {
+	.driver = {
+		.name	= "nct6694-i2c",
+	},
+	.probe		= nct6694_i2c_probe,
+};
+
+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] 33+ messages in thread

* [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
                   ` (2 preceding siblings ...)
  2025-06-04  4:14 ` [PATCH v12 3/7] i2c: Add Nuvoton NCT6694 I2C support a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04 10:19   ` Vincent Mailhol
  2025-06-04  4:14 ` [PATCH v12 5/7] watchdog: Add Nuvoton NCT6694 WDT support a0282524688
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Use platform_device's id to replace IDA
- Update struct data_bittiming_params related part

Changes since version 10:
- Implement IDA to allocate id
- Add header <linux/bitfield.h>
- Add support to config tdc

Changes since version 9:

Changes since version 8:
- Modify the signed-off-by with my work address
- Add irq_dispose_mapping() in the error handling path and in the remove
  function
- Rename all function names nct6694_can_xxx to nct6694_canfd_xxx
- Fix nct6694_canfd_handle_state_change()
- Fix nct6694_canfd_start() to configure NBTP and DBTP
- Add can_set_static_ctrlmode()

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

Changes since version 6:
- Fix nct6694_can_handle_state_change()
- Fix warnings
- Move the nct6694_can_priv's bec to the end

Changes since version 5:
- Modify the module name and the driver name consistently
- Update private data and drop mutex
- Fix nct6694_can_handle_state_change()

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

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Modify mutex_init() to devm_mutex_init()
- Add rx-offload helper

Changes since version 2:
- Add MODULE_ALIAS()
- Rename each command
- Modify each function name consistently

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration
- Fix errors and warnings
- Fix TX-flow control

 MAINTAINERS                         |   1 +
 drivers/net/can/usb/Kconfig         |  11 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 820 ++++++++++++++++++++++++++++
 4 files changed, 833 insertions(+)
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c

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


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

* [PATCH v12 5/7] watchdog: Add Nuvoton NCT6694 WDT support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
                   ` (3 preceding siblings ...)
  2025-06-04  4:14 ` [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04  4:14 ` [PATCH v12 6/7] hwmon: Add Nuvoton NCT6694 HWMON support a0282524688
  2025-06-04  4:14 ` [PATCH v12 7/7] rtc: Add Nuvoton NCT6694 RTC support a0282524688
  6 siblings, 0 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Use platform_device's id to replace IDA

Changes since version 10:
- Implement IDA to allocate id
- Add module parameters to configure WDT's timeout and pretimeout value

Changes since version 9:

Changes since version 8:
- Modify the signed-off-by with my work address

Changes since version 7:
- Add error handling for devm_mutex_init()

Changes since version 6:
- Fix warning

Changes since version 5:
- Modify the module name and the driver name consistently

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Modify mutex_init() to devm_mutex_init()
- Drop watchdog_init_timeout()

Changes since version 2:
- Add MODULE_ALIAS()
- Modify the pretimeout validation procedure

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration
- Fix warnings
- Drop unnecessary logs
- Modify start() function to setup device

 MAINTAINERS                    |   1 +
 drivers/watchdog/Kconfig       |  11 ++
 drivers/watchdog/Makefile      |   1 +
 drivers/watchdog/nct6694_wdt.c | 291 +++++++++++++++++++++++++++++++++
 4 files changed, 304 insertions(+)
 create mode 100644 drivers/watchdog/nct6694_wdt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 67d0dcaed9b4..8c9a1918f9e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17686,6 +17686,7 @@ F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/usb/nct6694_canfd.c
+F:	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 0c25b2ed44eb..05008d937e40 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -760,6 +760,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 bbd4d62d2cc3..b680e4d3c1bc 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -235,6 +235,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..8a98fedfd7b8
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2025 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 DEVICE_NAME "nct6694-wdt"
+
+#define NCT6694_DEFAULT_TIMEOUT		10
+#define NCT6694_DEFAULT_PRETIMEOUT	0
+
+#define NCT6694_WDT_MAX_DEVS		2
+
+/*
+ * USB command module type for NCT6694 WDT controller.
+ * This defines the module type used for communication with the NCT6694
+ * WDT controller over the USB interface.
+ */
+#define NCT6694_WDT_MOD			0x07
+
+/* Command 00h - WDT Setup */
+#define NCT6694_WDT_SETUP		0x00
+#define NCT6694_WDT_SETUP_SEL(idx)	(idx ? 0x01 : 0x00)
+
+/* Command 01h - WDT Command */
+#define NCT6694_WDT_COMMAND		0x01
+#define NCT6694_WDT_COMMAND_SEL(idx)	(idx ? 0x01 : 0x00)
+
+static unsigned int timeout[NCT6694_WDT_MAX_DEVS] = {
+	[0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_TIMEOUT
+};
+module_param_array(timeout, int, NULL, 0644);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] = {
+	[0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_PRETIMEOUT
+};
+module_param_array(pretimeout, int, NULL, 0644);
+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_setup {
+	__le32 pretimeout;
+	__le32 timeout;
+	u8 owner;
+	u8 scratch;
+	u8 control;
+	u8 status;
+	__le32 countdown;
+};
+
+struct __packed nct6694_wdt_cmd {
+	__le32 wdt_cmd;
+	__le32 reserved;
+};
+
+union __packed nct6694_wdt_msg {
+	struct nct6694_wdt_setup setup;
+	struct nct6694_wdt_cmd cmd;
+};
+
+struct nct6694_wdt_data {
+	struct watchdog_device wdev;
+	struct device *dev;
+	struct nct6694 *nct6694;
+	union nct6694_wdt_msg *msg;
+	unsigned char 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_setup *setup = &data->msg->setup;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_SETUP,
+		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*setup))
+	};
+	unsigned int timeout_fmt, pretimeout_fmt;
+
+	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(setup, 0, sizeof(*setup));
+	setup->timeout = cpu_to_le32(timeout_fmt);
+	setup->pretimeout = cpu_to_le32(pretimeout_fmt);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, setup);
+}
+
+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_cmd *cmd = &data->msg->cmd;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_COMMAND,
+		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*cmd))
+	};
+
+	memcpy(&cmd->wdt_cmd, "WDTC", 4);
+	cmd->reserved = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_COMMAND,
+		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*cmd))
+	};
+
+	memcpy(&cmd->wdt_cmd, "WDTS", 4);
+	cmd->reserved = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+				   unsigned int new_timeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, new_timeout, NCT6694_ACTION_GPO,
+				  wdev->pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->timeout = new_timeout;
+
+	return 0;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+				      unsigned int new_pretimeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+				  new_pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->pretimeout = new_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_setup *setup = &data->msg->setup;
+	const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_SETUP,
+		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*setup))
+	};
+	unsigned int timeleft_ms;
+	int ret;
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, setup);
+	if (ret)
+		return 0;
+
+	timeleft_ms = le32_to_cpu(setup->countdown);
+
+	return timeleft_ms / 1000;
+}
+
+static const struct watchdog_info nct6694_wdt_info = {
+	.options = WDIOF_SETTIMEOUT	|
+		   WDIOF_KEEPALIVEPING	|
+		   WDIOF_MAGICCLOSE	|
+		   WDIOF_PRETIMEOUT,
+	.identity = DEVICE_NAME,
+};
+
+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)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_wdt_data *data;
+	struct watchdog_device *wdev;
+	int wdev_idx = pdev->id;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->dev = dev;
+	data->nct6694 = nct6694;
+	data->wdev_idx = wdev_idx;
+
+	wdev = &data->wdev;
+	wdev->info = &nct6694_wdt_info;
+	wdev->ops = &nct6694_wdt_ops;
+	wdev->timeout = timeout[wdev_idx];
+	wdev->pretimeout = pretimeout[wdev_idx];
+	if (timeout[wdev_idx] < pretimeout[wdev_idx]) {
+		dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+		wdev->pretimeout = 0;
+	}
+
+	wdev->min_timeout = 1;
+	wdev->max_timeout = 255;
+
+	platform_set_drvdata(pdev, data);
+
+	watchdog_set_drvdata(&data->wdev, data);
+	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	= DEVICE_NAME,
+	},
+	.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] 33+ messages in thread

* [PATCH v12 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
                   ` (4 preceding siblings ...)
  2025-06-04  4:14 ` [PATCH v12 5/7] watchdog: Add Nuvoton NCT6694 WDT support a0282524688
@ 2025-06-04  4:14 ` a0282524688
  2025-06-04  4:14 ` [PATCH v12 7/7] rtc: Add Nuvoton NCT6694 RTC support a0282524688
  6 siblings, 0 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:
- Fix the typo in the header

Changes since version 10:

Changes since version 9:

Changes since version 8:
- Modify the signed-off-by with my work address

Changes since version 7:
- Add error handling for devm_mutex_init()

Changes since version 6:
- Fix temp_hyst's data type to signed variable

Changes since version 5:
- Modify the module name and the driver name consistently

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function
- Fix warngings

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Modify mutex_init() to devm_mutex_init()
- Modify the division method to DIV_ROUND_CLOSEST()

Changes since version 2:
- Add MODULE_ALIAS()
- Fix warnings

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration
- Add voltage sensors functionality
- Add temperature sensors functionality
- Fix overwrite error return values
- Add write value limitation for each write() function

 MAINTAINERS                   |   1 +
 drivers/hwmon/Kconfig         |  10 +
 drivers/hwmon/Makefile        |   1 +
 drivers/hwmon/nct6694-hwmon.c | 949 ++++++++++++++++++++++++++++++++++
 4 files changed, 961 insertions(+)
 create mode 100644 drivers/hwmon/nct6694-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8c9a1918f9e1..d127df871d36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17683,6 +17683,7 @@ NUVOTON NCT6694 MFD DRIVER
 M:	Ming Yu <tmyu0@nuvoton.com>
 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/usb/nct6694_canfd.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5fd93aad2d6d..8e92ef14bbe0 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1659,6 +1659,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 e3468d024ff3..7c953b84d67d 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -170,6 +170,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..6dcf22ca5018
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#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>
+
+/*
+ * USB command module type for NCT6694 report channel
+ * This defines the module type used for communication with the NCT6694
+ * report channel over the USB interface.
+ */
+#define NCT6694_RPT_MOD			0xFF
+
+/* Report channel */
+/*
+ * The report channel is used to report the status of the hardware monitor
+ * devices, such as voltage, temperature, fan speed, and PWM.
+ */
+#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))
+
+/*
+ * USB command module type for NCT6694 HWMON controller.
+ * This defines the module type used for communication with the NCT6694
+ * HWMON controller over the USB interface.
+ */
+#define NCT6694_HWMON_MOD		0x00
+
+/* Command 00h - Hardware Monitor Control */
+#define NCT6694_HWMON_CONTROL		0x00
+#define NCT6694_HWMON_CONTROL_SEL	0x00
+
+/* Command 02h - Alarm Control */
+#define NCT6694_HWMON_ALARM		0x02
+#define NCT6694_HWMON_ALARM_SEL		0x00
+
+/*
+ * USB command module type for NCT6694 PWM controller.
+ * This defines the module type used for communication with the NCT6694
+ * PWM controller over the USB interface.
+ */
+#define NCT6694_PWM_MOD			0x01
+
+/* PWM Command - Manual Control */
+#define NCT6694_PWM_CONTROL		0x01
+#define NCT6694_PWM_CONTROL_SEL		0x00
+
+#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)
+
+enum nct6694_hwmon_temp_mode {
+	NCT6694_HWMON_TWOTIME_IRQ = 0,
+	NCT6694_HWMON_ONETIME_IRQ,
+	NCT6694_HWMON_REALTIME_IRQ,
+	NCT6694_HWMON_COMPARE_IRQ,
+};
+
+struct __packed nct6694_hwmon_control {
+	u8 vin_en[2];
+	u8 tin_en[2];
+	u8 fin_en[2];
+	u8 pwm_en[2];
+	u8 reserved1[40];
+	u8 pwm_freq[10];
+	u8 reserved2[6];
+};
+
+struct __packed nct6694_hwmon_alarm {
+	u8 smi_ctrl;
+	u8 reserved1[15];
+	struct {
+		u8 hl;
+		u8 ll;
+	} vin_limit[16];
+	struct {
+		u8 hyst;
+		s8 hl;
+	} tin_cfg[32];
+	__be16 fin_ll[10];
+	u8 reserved2[4];
+};
+
+struct __packed nct6694_pwm_control {
+	u8 mal_en[2];
+	u8 mal_val[10];
+	u8 reserved[12];
+};
+
+union __packed nct6694_hwmon_rpt {
+	u8 vin;
+	struct {
+		u8 msb;
+		u8 lsb;
+	} tin;
+	__be16 fin;
+	u8 pwm;
+	u8 status;
+};
+
+union __packed nct6694_hwmon_msg {
+	struct nct6694_hwmon_alarm hwmon_alarm;
+	struct nct6694_pwm_control pwm_ctrl;
+};
+
+struct nct6694_hwmon_data {
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	struct nct6694_hwmon_control hwmon_en;
+	union nct6694_hwmon_rpt *rpt;
+	union nct6694_hwmon_msg *msg;
+};
+
+static inline long in_from_reg(u8 reg)
+{
+	return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+	return DIV_ROUND_CLOSEST(val, 16);
+}
+
+static inline long temp_from_reg(s8 reg)
+{
+	return reg * 1000;
+}
+
+static inline s8 temp_to_reg(long val)
+{
+	return DIV_ROUND_CLOSEST(val, 1000);
+}
+
+#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);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char vin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		vin_en = data->hwmon_en.vin_en[(channel / 8)];
+		*val = !!(vin_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_in_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_VIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->vin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->vin);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->rpt->vin);
+
+		return 0;
+	case hwmon_in_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl);
+
+		return 0;
+	case hwmon_in_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll);
+
+		return 0;
+	case hwmon_in_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & 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);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char temp_en, temp_hyst;
+	signed char temp_max;
+	int ret, temp_raw;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		temp_en = data->hwmon_en.tin_en[channel / 8];
+		*val = !!(temp_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_temp_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_TIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->tin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->tin);
+		if (ret)
+			return ret;
+
+		temp_raw = data->rpt->tin.msb << 3;
+		temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb);
+
+		/* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */
+		*val = sign_extend32(temp_raw, 10) * 125;
+
+		return 0;
+	case hwmon_temp_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl);
+
+		return 0;
+	case hwmon_temp_max_hyst:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+		temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+				      data->msg->hwmon_alarm.tin_cfg[channel].hyst);
+		*val = temp_from_reg(temp_max - temp_hyst);
+
+		return 0;
+	case hwmon_temp_max_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & 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);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char fanin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		fanin_en = data->hwmon_en.fin_en[channel / 8];
+		*val = !!(fanin_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_fan_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_FIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->fin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->fin);
+		if (ret)
+			return ret;
+
+		*val = be16_to_cpu(data->rpt->fin);
+
+		return 0;
+	case hwmon_fan_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]);
+
+		return 0;
+	case hwmon_fan_min_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & 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);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char pwm_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		pwm_en = data->hwmon_en.pwm_en[channel / 8];
+		*val = !!(pwm_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_pwm_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_PWM_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->pwm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->pwm);
+		if (ret)
+			return ret;
+
+		*val = data->rpt->pwm;
+
+		return 0;
+	case hwmon_pwm_freq:
+		*val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]);
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+			    long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		if (val == 0)
+			data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_in_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	case hwmon_in_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	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);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char temp_hyst;
+	signed char temp_max;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		if (val == 0)
+			data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_temp_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, -127000, 127000);
+		data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	case hwmon_temp_max_hyst:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+
+		val = clamp_val(val, -127000, 127000);
+		temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+		temp_hyst = temp_max - temp_to_reg(val);
+		temp_hyst = clamp_val(temp_hyst, 0, 7);
+		data->msg->hwmon_alarm.tin_cfg[channel].hyst =
+			(data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) |
+			FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	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);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		if (val == 0)
+			data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_fan_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 1, 65535);
+		data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	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);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		if (val == 0)
+			data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_pwm_input:
+		if (val < 0 || val > 255)
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_PWM_MOD,
+			.cmd = NCT6694_PWM_CONTROL,
+			.sel = NCT6694_PWM_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->pwm_ctrl))
+		};
+
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->pwm_ctrl);
+		if (ret)
+			return ret;
+
+		data->msg->pwm_ctrl.mal_val[channel] = val;
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->pwm_ctrl);
+	case hwmon_pwm_freq:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &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)
+{
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_HWMON_MOD,
+		.cmd = NCT6694_HWMON_CONTROL,
+		.sel = NCT6694_HWMON_CONTROL_SEL,
+		.len = cpu_to_le16(sizeof(data->hwmon_en))
+	};
+	int ret;
+
+	/*
+	 * Record each Hardware Monitor Channel enable status
+	 * and PWM frequency register
+	 */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+			       &data->hwmon_en);
+	if (ret)
+		return ret;
+
+	cmd_hd = (struct nct6694_cmd_header) {
+		.mod = NCT6694_HWMON_MOD,
+		.cmd = NCT6694_HWMON_ALARM,
+		.sel = NCT6694_HWMON_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+	};
+
+	/* Select hwmon device alarm mode */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+			       &data->msg->hwmon_alarm);
+	if (ret)
+		return ret;
+
+	data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd,
+				 &data->msg->hwmon_alarm);
+}
+
+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->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt),
+				 GFP_KERNEL);
+	if (!data->rpt)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->nct6694 = nct6694;
+	ret = devm_mutex_init(&pdev->dev, &data->lock);
+	if (ret)
+		return ret;
+
+	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] 33+ messages in thread

* [PATCH v12 7/7] rtc: Add Nuvoton NCT6694 RTC support
  2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
                   ` (5 preceding siblings ...)
  2025-06-04  4:14 ` [PATCH v12 6/7] hwmon: Add Nuvoton NCT6694 HWMON support a0282524688
@ 2025-06-04  4:14 ` a0282524688
  6 siblings, 0 replies; 33+ messages in thread
From: a0282524688 @ 2025-06-04  4:14 UTC (permalink / raw)
  To: 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, linux-usb, Ming Yu

From: Ming Yu <tmyu0@nuvoton.com>

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

Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
Changes since version 11:

Changes since version 10:

Changes since version 9:
- Add devm_add_action_or_reset() to dispose irq mapping

Changes since version 8:
- Modify the signed-off-by with my work address
- Add irq_dispose_mapping() in the error handling path and in the remove
  function

Changes since version 7:

Changes since version 6:

Changes since version 5:
- Modify the module name and the driver name consistently

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function

Changes since version 3:
- Modify array buffer to structure
- Fix defines and comments
- Drop private mutex and use rtc core lock
- Modify device_set_wakeup_capable() to device_init_wakeup()

Changes since version 2:
- Add MODULE_ALIAS()

Changes since version 1:
- Add each driver's command structure
- Fix platform driver registration
- Drop unnecessary logs
- Fix overwrite error return values
- Modify to use dev_err_probe API

 MAINTAINERS               |   1 +
 drivers/rtc/Kconfig       |  10 ++
 drivers/rtc/Makefile      |   1 +
 drivers/rtc/rtc-nct6694.c | 297 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 309 insertions(+)
 create mode 100644 drivers/rtc/rtc-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d127df871d36..0b63b6c3a612 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17687,6 +17687,7 @@ F:	drivers/hwmon/nct6694-hwmon.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/usb/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 838bdc138ffe..d8662b5d1e47 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -416,6 +416,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 31473b3276d9..da091d66e2d7 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -118,6 +118,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..35401a0d9cf5
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bcd.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 RTC controller.
+ * This defines the module type used for communication with the NCT6694
+ * RTC controller over the USB interface.
+ */
+#define NCT6694_RTC_MOD		0x08
+
+/* Command 00h - RTC Time */
+#define NCT6694_RTC_TIME	0x0000
+#define NCT6694_RTC_TIME_SEL	0x00
+
+/* Command 01h - RTC Alarm */
+#define NCT6694_RTC_ALARM	0x01
+#define NCT6694_RTC_ALARM_SEL	0x00
+
+/* Command 02h - RTC Status */
+#define NCT6694_RTC_STATUS	0x02
+#define NCT6694_RTC_STATUS_SEL	0x00
+
+#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_time {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 week;
+	u8 day;
+	u8 month;
+	u8 year;
+};
+
+struct __packed nct6694_rtc_alarm {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 alarm_en;
+	u8 alarm_pend;
+};
+
+struct __packed nct6694_rtc_status {
+	u8 irq_en;
+	u8 irq_pend;
+};
+
+union __packed nct6694_rtc_msg {
+	struct nct6694_rtc_time time;
+	struct nct6694_rtc_alarm alarm;
+	struct nct6694_rtc_status sts;
+};
+
+struct nct6694_rtc_data {
+	struct nct6694 *nct6694;
+	struct rtc_device *rtc;
+	union nct6694_rtc_msg *msg;
+	int irq;
+};
+
+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_time *time = &data->msg->time;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_TIME,
+		.sel = NCT6694_RTC_TIME_SEL,
+		.len = cpu_to_le16(sizeof(*time))
+	};
+	int ret;
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, time);
+	if (ret)
+		return ret;
+
+	tm->tm_sec = bcd2bin(time->sec);		/* tm_sec expect 0 ~ 59 */
+	tm->tm_min = bcd2bin(time->min);		/* tm_min expect 0 ~ 59 */
+	tm->tm_hour = bcd2bin(time->hour);		/* tm_hour expect 0 ~ 23 */
+	tm->tm_wday = bcd2bin(time->week) - 1;		/* tm_wday expect 0 ~ 6 */
+	tm->tm_mday = bcd2bin(time->day);		/* tm_mday expect 1 ~ 31 */
+	tm->tm_mon = bcd2bin(time->month) - 1;		/* tm_month expect 0 ~ 11 */
+	tm->tm_year = bcd2bin(time->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_time *time = &data->msg->time;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_TIME,
+		.sel = NCT6694_RTC_TIME_SEL,
+		.len = cpu_to_le16(sizeof(*time))
+	};
+
+	time->sec = bin2bcd(tm->tm_sec);
+	time->min = bin2bcd(tm->tm_min);
+	time->hour = bin2bcd(tm->tm_hour);
+	time->week = bin2bcd(tm->tm_wday + 1);
+	time->day = bin2bcd(tm->tm_mday);
+	time->month = bin2bcd(tm->tm_mon + 1);
+	time->year = bin2bcd(tm->tm_year - 100);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, time);
+}
+
+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_alarm *alarm = &data->msg->alarm;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_ALARM,
+		.sel = NCT6694_RTC_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(*alarm))
+	};
+	int ret;
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, alarm);
+	if (ret)
+		return ret;
+
+	alrm->time.tm_sec = bcd2bin(alarm->sec);
+	alrm->time.tm_min = bcd2bin(alarm->min);
+	alrm->time.tm_hour = bcd2bin(alarm->hour);
+	alrm->enabled = alarm->alarm_en;
+	alrm->pending = alarm->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_alarm *alarm = &data->msg->alarm;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_ALARM,
+		.sel = NCT6694_RTC_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(*alarm))
+	};
+
+	alarm->sec = bin2bcd(alrm->time.tm_sec);
+	alarm->min = bin2bcd(alrm->time.tm_min);
+	alarm->hour = bin2bcd(alrm->time.tm_hour);
+	alarm->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0;
+	alarm->alarm_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, alarm);
+}
+
+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_status *sts = &data->msg->sts;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_STATUS,
+		.sel = NCT6694_RTC_STATUS_SEL,
+		.len = cpu_to_le16(sizeof(*sts))
+	};
+
+	if (enabled)
+		sts->irq_en |= NCT6694_RTC_IRQ_EN;
+	else
+		sts->irq_en &= ~NCT6694_RTC_IRQ_EN;
+
+	sts->irq_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+}
+
+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_status *sts = &data->msg->sts;
+	static const struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_STATUS,
+		.sel = NCT6694_RTC_STATUS_SEL,
+		.len = cpu_to_le16(sizeof(*sts))
+	};
+	int ret;
+
+	rtc_lock(data->rtc);
+
+	sts->irq_en = NCT6694_RTC_IRQ_EN;
+	sts->irq_pend = NCT6694_RTC_IRQ_STS;
+	ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+	if (ret) {
+		rtc_unlock(data->rtc);
+		return IRQ_NONE;
+	}
+
+	rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF);
+
+	rtc_unlock(data->rtc);
+
+	return IRQ_HANDLED;
+}
+
+static void nct6694_irq_dispose_mapping(void *d)
+{
+	struct nct6694_rtc_data *data = d;
+
+	irq_dispose_mapping(data->irq);
+}
+
+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;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
+	if (!data->irq)
+		return -EINVAL;
+
+	ret = devm_add_action_or_reset(&pdev->dev, nct6694_irq_dispose_mapping,
+				       data);
+	if (ret)
+		return ret;
+
+	ret = devm_device_init_wakeup(&pdev->dev);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to init wakeup\n");
+
+	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;
+
+	platform_set_drvdata(pdev, data);
+
+	ret = devm_request_threaded_irq(&pdev->dev, data->irq, NULL,
+					nct6694_irq, IRQF_ONESHOT,
+					"rtc-nct6694", data);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+	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] 33+ messages in thread

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
@ 2025-06-04 10:11   ` Oliver Neukum
  2025-06-04 12:40     ` Guenter Roeck
  2025-06-05  7:48     ` Ming Yu
  2025-06-12 14:00   ` Lee Jones
  1 sibling, 2 replies; 33+ messages in thread
From: Oliver Neukum @ 2025-06-04 10:11 UTC (permalink / raw)
  To: a0282524688, 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, linux-usb, Ming Yu

On 04.06.25 06:14, a0282524688@gmail.com wrote:
> From: Ming Yu <tmyu0@nuvoton.com>
> 
> The Nuvoton NCT6694 provides an USB interface to the host to
> access its features.
> 
> Sub-devices can use the USB functions nct6694_read_msg() and
> nct6694_write_msg() to issue a command. They can also request
> interrupt that will be called when the USB device receives its
> interrupt pipe.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> Changes since version 11:
> - Modify the irq_domain_add_simple() to irq_domain_create_simple()
> - Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA
>    in sub-drivers
> 
> Changes since version 10:
> - Add change log for the patch
> - Fix mfd_cell to MFD_CELL_NAME()
> - Remove unnecessary blank line
> 
> Changes since version 9:
> - Add KernelDoc to exported functions
> 
> Changes since version 8:
> - Modify the signed-off-by with my work address
> - Rename all MFD cell names to "nct6694-xxx"
> - Fix some comments in nct6694.c and in nct6694.h
> 
> Changes since version 7:
> - Add error handling for devm_mutex_init()
> 
> Changes since version 6:
> 
> Changes since version 5:
> - Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC()
> - Drop unnecessary macros
> 
> Changes since version 4:
> - Modify arguments in read/write function to a pointer to cmd_header
> 
> Changes since version 3:
> - Modify array buffer to structure
> - Fix defines and comments
> - Add header <linux/bits.h> and use BIT macro
> - Modify mutex_init() to devm_mutex_init()
> 
> Changes since version 2:
> 
> Changes since version 1:
> - Implement IRQ domain to handle IRQ demux
> - Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API
> - Add command structure
> - Fix USB functions
> - Sort each driver's header files alphabetically
> 
>   MAINTAINERS                 |   6 +
>   drivers/mfd/Kconfig         |  15 ++
>   drivers/mfd/Makefile        |   2 +
>   drivers/mfd/nct6694.c       | 386 ++++++++++++++++++++++++++++++++++++
>   include/linux/mfd/nct6694.h |  98 +++++++++
>   5 files changed, 507 insertions(+)
>   create mode 100644 drivers/mfd/nct6694.c
>   create mode 100644 include/linux/mfd/nct6694.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 98201e1f4ab5..29d2d05bac22 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17679,6 +17679,12 @@ F:	drivers/nubus/
>   F:	include/linux/nubus.h
>   F:	include/uapi/linux/nubus.h
>   
> +NUVOTON NCT6694 MFD DRIVER
> +M:	Ming Yu <tmyu0@nuvoton.com>
> +S:	Supported
> +F:	drivers/mfd/nct6694.c
> +F:	include/linux/mfd/nct6694.h
> +
>   NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
>   M:	Antonino Daplas <adaplas@gmail.com>
>   L:	linux-fbdev@vger.kernel.org
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 96992af22565..489c1950f1ac 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1078,6 +1078,21 @@ config MFD_MENF21BMC
>   	  This driver can also be built as a module. If so the module
>   	  will be called menf21bmc.
>   
> +config MFD_NCT6694
> +	tristate "Nuvoton NCT6694 support"
> +	select MFD_CORE
> +	depends on USB
> +	help
> +	  This enables support for the Nuvoton USB device NCT6694, which shares
> +	  peripherals.
> +	  The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> +	  6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> +	  PWM, and RTC.
> +	  This driver provides core APIs to access the NCT6694 hardware
> +	  monitoring and control features.
> +	  Additional drivers must be enabled to utilize the specific
> +	  functionalities of the device.
> +
>   config MFD_OCELOT
>   	tristate "Microsemi Ocelot External Control Support"
>   	depends on SPI_MASTER
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 5e5cc279af60..a96204d938fc 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
>   obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
>   obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
>   
> +obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
> +
>   obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
>   
>   ocelot-soc-objs			:= ocelot-core.o ocelot-spi.o
> diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> new file mode 100644
> index 000000000000..82d378ee47ed
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Nuvoton Technology Corp.
> + *
> + * Nuvoton NCT6694 core driver using USB interface to provide
> + * access to the NCT6694 hardware monitoring and control features.
> + *
> + * The NCT6694 is an integrated controller that provides GPIO, I2C,
> + * CAN, WDT, HWMON and RTC management.
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +
> +static const struct mfd_cell nct6694_devs[] = {
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> +
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> +
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_NAME("nct6694-hwmon"),
> +
> +	MFD_CELL_NAME("nct6694-rtc"),
> +};
> +
> +static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
> +{
> +	switch (err_status) {
> +	case NCT6694_NO_ERROR:
> +		return 0;
> +	case NCT6694_NOT_SUPPORT_ERROR:
> +		dev_err(nct6694->dev, "Command is not supported!\n");
> +		break;
> +	case NCT6694_NO_RESPONSE_ERROR:
> +		dev_warn(nct6694->dev, "Command received no response!\n");
> +		break;
> +	case NCT6694_TIMEOUT_ERROR:
> +		dev_warn(nct6694->dev, "Command timed out!\n");
> +		break;
> +	case NCT6694_PENDING:
> +		dev_err(nct6694->dev, "Command is pending!\n");
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EIO;
> +}
> +
> +/**
> + * nct6694_read_msg() - Read message from NCT6694 device
> + * @nct6694: NCT6694 device pointer
> + * @cmd_hd: command header structure
> + * @buf: buffer to store the response data
> + *
> + * Sends a command to the NCT6694 device and reads the response.
> + * The command header is specified in @cmd_hd, and the response
> + * data is stored in @buf.
> + *
> + * Return: Negative value on error or 0 on success.
> + */
> +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> +{
> +	union nct6694_usb_msg *msg = nct6694->usb_msg;
> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);
> +
> +	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> +	msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
> +
> +	/* Send command packet to USB device */
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
> +			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive response packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
> +			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive data packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
> +			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	if (rx_len != le16_to_cpu(cmd_hd->len)) {
> +		dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
> +			le16_to_cpu(cmd_hd->len), rx_len);
> +		return -EIO;
> +	}
> +
> +	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
> +}
> +EXPORT_SYMBOL_GPL(nct6694_read_msg);
> +
> +/**
> + * nct6694_write_msg() - Write message to NCT6694 device
> + * @nct6694: NCT6694 device pointer
> + * @cmd_hd: command header structure
> + * @buf: buffer containing the data to be sent
> + *
> + * Sends a command to the NCT6694 device and writes the data
> + * from @buf. The command header is specified in @cmd_hd.
> + *
> + * Return: Negative value on error or 0 on success.
> + */
> +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> +{
> +	union nct6694_usb_msg *msg = nct6694->usb_msg;
> +	struct usb_device *udev = nct6694->udev;
> +	int tx_len, rx_len, ret;
> +
> +	guard(mutex)(&nct6694->access_lock);
> +
> +	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> +	msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
> +
> +	/* Send command packet to USB device */
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
> +			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	/* Send data packet to USB device */
> +	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
> +			   le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive response packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
> +			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	/* Receive data packet from USB device */
> +	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
> +			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	if (rx_len != le16_to_cpu(cmd_hd->len)) {
> +		dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
> +			le16_to_cpu(cmd_hd->len), rx_len);
> +		return -EIO;
> +	}
> +
> +	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
> +}
> +EXPORT_SYMBOL_GPL(nct6694_write_msg);
> +
> +static void usb_int_callback(struct urb *urb)
> +{
> +	struct nct6694 *nct6694 = urb->context;
> +	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);
> +
> +		generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> +		*int_status &= ~BIT(irq);
> +	}

Does modifying the byte have any benefit?

> +resubmit:
> +	ret = usb_submit_urb(urb, GFP_ATOMIC);
> +	if (ret)
> +		dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe",  ERR_PTR(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);
> +}

Why? Does this do anything but make it _harder_ to tell that you
cannot take the lock in interrupt?

	Regards
		Oliver


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

* Re: [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support
  2025-06-04  4:14 ` [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support a0282524688
@ 2025-06-04 10:19   ` Vincent Mailhol
  0 siblings, 0 replies; 33+ messages in thread
From: Vincent Mailhol @ 2025-06-04 10:19 UTC (permalink / raw)
  To: a0282524688
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu, lee,
	linus.walleij, brgl, andi.shyti, mkl, andrew+netdev, davem,
	edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni

On 04/06/2025 at 13:14, a0282524688@gmail.com wrote:
> From: Ming Yu <tmyu0@nuvoton.com>
> 
> This driver supports Socket CANFD functionality for NCT6694 MFD
> device based on USB interface.
> 
> Reviewed-by: Marc Kleine-Budde <mkl@pengutronix.de>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>

You are sending this from a0282524688@gmail.com, but your signature says
tmyu0@nuvoton.com.

Can you use your actual email address in the signature?

Aside from that:

Reviewed-by: Vincent Mailhol <mailhol.vincent@wanadoo.fr>

Thank you!


Yours sincerely,
Vincent Mailhol


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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04 10:11   ` Oliver Neukum
@ 2025-06-04 12:40     ` Guenter Roeck
  2025-06-05  7:49       ` Ming Yu
  2025-06-05  7:48     ` Ming Yu
  1 sibling, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2025-06-04 12:40 UTC (permalink / raw)
  To: Oliver Neukum, a0282524688, 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, linux-usb, Ming Yu

On 6/4/25 03:11, Oliver Neukum wrote:
> On 04.06.25 06:14, a0282524688@gmail.com wrote:
>> From: Ming Yu <tmyu0@nuvoton.com>
>>
>> The Nuvoton NCT6694 provides an USB interface to the host to
>> access its features.
>>
>> Sub-devices can use the USB functions nct6694_read_msg() and
>> nct6694_write_msg() to issue a command. They can also request
>> interrupt that will be called when the USB device receives its
>> interrupt pipe.
>>
>> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
>> ---
...
>> +static void usb_int_callback(struct urb *urb)
>> +{
>> +    struct nct6694 *nct6694 = urb->context;
>> +    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);
>> +
>> +        generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
>> +        *int_status &= ~BIT(irq);
>> +    }
> 
> Does modifying the byte have any benefit?
> 

Not sure if I understand the question, and assuming your question is regarding
*int_status: *int_status!=0 is the loop invariant, so, yes, modifying it does
have a benefit.

I'd be more concerned that transfer_buffer is the raw buffer, and that data
read from it is not endianness converted. That makes me wonder if and how the
code would work on a big endian system.

Guenter


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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04 10:11   ` Oliver Neukum
  2025-06-04 12:40     ` Guenter Roeck
@ 2025-06-05  7:48     ` Ming Yu
  1 sibling, 0 replies; 33+ messages in thread
From: Ming Yu @ 2025-06-05  7:48 UTC (permalink / raw)
  To: Oliver Neukum
  Cc: 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,
	linux-usb, Ming Yu

Dear Oliver,

Thank you for reviewing,

Oliver Neukum <oneukum@suse.com> 於 2025年6月4日 週三 下午6:11寫道:
>
> > +static void usb_int_callback(struct urb *urb)
> > +{
> > +     struct nct6694 *nct6694 = urb->context;
> > +     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);
> > +
> > +             generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> > +             *int_status &= ~BIT(irq);
> > +     }
>
> Does modifying the byte have any benefit?
>

I will update the code in the next patch to use __le32 for the
variable to ensure proper endianness handling across architectures.

> > +resubmit:
> > +     ret = usb_submit_urb(urb, GFP_ATOMIC);
> > +     if (ret)
> > +             dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe",  ERR_PTR(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);
> > +}
>
> Why? Does this do anything but make it _harder_ to tell that you
> cannot take the lock in interrupt?
>

I plan to remove nct6694_irq_lock() and nct6694_bus_sync_unlock(), and
instead add the spinlock directly inside the function like this:
static void nct6694_irq_enable(struct irq_data *data)
{
    ...
    spin_lock(&nct6694->irq_lock);
    nct6694->irq_enable |= BIT(hwirq);
    spin_unlock(&nct6694->irq_lock);
}

Do you think this approach is better?


Best regards,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04 12:40     ` Guenter Roeck
@ 2025-06-05  7:49       ` Ming Yu
  0 siblings, 0 replies; 33+ messages in thread
From: Ming Yu @ 2025-06-05  7:49 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Oliver Neukum, 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, linux-usb, Ming Yu

Dear Guenter,

Thank you for reviewing,

Guenter Roeck <linux@roeck-us.net> 於 2025年6月4日 週三 下午8:40寫道:
>
> >> +static void usb_int_callback(struct urb *urb)
> >> +{
> >> +    struct nct6694 *nct6694 = urb->context;
> >> +    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);
> >> +
> >> +        generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
> >> +        *int_status &= ~BIT(irq);
> >> +    }
> >
> > Does modifying the byte have any benefit?
> >
>
> Not sure if I understand the question, and assuming your question is regarding
> *int_status: *int_status!=0 is the loop invariant, so, yes, modifying it does
> have a benefit.
>
> I'd be more concerned that transfer_buffer is the raw buffer, and that data
> read from it is not endianness converted. That makes me wonder if and how the
> code would work on a big endian system.
>

I will update the code in the next patch to use __le32 for the
variable to ensure proper endianness handling across architectures.


Best regards,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
  2025-06-04 10:11   ` Oliver Neukum
@ 2025-06-12 14:00   ` Lee Jones
  2025-06-12 14:40     ` Ming Yu
  1 sibling, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-12 14:00 UTC (permalink / raw)
  To: a0282524688
  Cc: 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,
	linux-usb, Ming Yu

On Wed, 04 Jun 2025, a0282524688@gmail.com wrote:

> From: Ming Yu <tmyu0@nuvoton.com>
> 
> The Nuvoton NCT6694 provides an USB interface to the host to
> access its features.
> 
> Sub-devices can use the USB functions nct6694_read_msg() and
> nct6694_write_msg() to issue a command. They can also request
> interrupt that will be called when the USB device receives its
> interrupt pipe.
> 
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> Changes since version 11:
> - Modify the irq_domain_add_simple() to irq_domain_create_simple()
> - Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA
>   in sub-drivers
> 
> Changes since version 10:
> - Add change log for the patch
> - Fix mfd_cell to MFD_CELL_NAME()
> - Remove unnecessary blank line
> 
> Changes since version 9:
> - Add KernelDoc to exported functions
> 
> Changes since version 8:
> - Modify the signed-off-by with my work address
> - Rename all MFD cell names to "nct6694-xxx"
> - Fix some comments in nct6694.c and in nct6694.h
> 
> Changes since version 7:
> - Add error handling for devm_mutex_init()
> 
> Changes since version 6:
> 
> Changes since version 5:
> - Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC()
> - Drop unnecessary macros
> 
> Changes since version 4:
> - Modify arguments in read/write function to a pointer to cmd_header
> 
> Changes since version 3:
> - Modify array buffer to structure
> - Fix defines and comments
> - Add header <linux/bits.h> and use BIT macro
> - Modify mutex_init() to devm_mutex_init()
> 
> Changes since version 2:
> 
> Changes since version 1:
> - Implement IRQ domain to handle IRQ demux
> - Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API
> - Add command structure
> - Fix USB functions
> - Sort each driver's header files alphabetically
> 
>  MAINTAINERS                 |   6 +
>  drivers/mfd/Kconfig         |  15 ++
>  drivers/mfd/Makefile        |   2 +
>  drivers/mfd/nct6694.c       | 386 ++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/nct6694.h |  98 +++++++++
>  5 files changed, 507 insertions(+)
>  create mode 100644 drivers/mfd/nct6694.c
>  create mode 100644 include/linux/mfd/nct6694.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 98201e1f4ab5..29d2d05bac22 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17679,6 +17679,12 @@ F:	drivers/nubus/
>  F:	include/linux/nubus.h
>  F:	include/uapi/linux/nubus.h
>  
> +NUVOTON NCT6694 MFD DRIVER
> +M:	Ming Yu <tmyu0@nuvoton.com>
> +S:	Supported
> +F:	drivers/mfd/nct6694.c
> +F:	include/linux/mfd/nct6694.h
> +
>  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
>  M:	Antonino Daplas <adaplas@gmail.com>
>  L:	linux-fbdev@vger.kernel.org
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 96992af22565..489c1950f1ac 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1078,6 +1078,21 @@ config MFD_MENF21BMC
>  	  This driver can also be built as a module. If so the module
>  	  will be called menf21bmc.
>  
> +config MFD_NCT6694
> +	tristate "Nuvoton NCT6694 support"
> +	select MFD_CORE
> +	depends on USB
> +	help
> +	  This enables support for the Nuvoton USB device NCT6694, which shares
> +	  peripherals.
> +	  The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> +	  6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> +	  PWM, and RTC.
> +	  This driver provides core APIs to access the NCT6694 hardware
> +	  monitoring and control features.
> +	  Additional drivers must be enabled to utilize the specific
> +	  functionalities of the device.
> +
>  config MFD_OCELOT
>  	tristate "Microsemi Ocelot External Control Support"
>  	depends on SPI_MASTER
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 5e5cc279af60..a96204d938fc 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
>  obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
>  obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
>  
> +obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
> +
>  obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
>  
>  ocelot-soc-objs			:= ocelot-core.o ocelot-spi.o
> diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> new file mode 100644
> index 000000000000..82d378ee47ed
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Nuvoton Technology Corp.
> + *
> + * Nuvoton NCT6694 core driver using USB interface to provide
> + * access to the NCT6694 hardware monitoring and control features.
> + *
> + * The NCT6694 is an integrated controller that provides GPIO, I2C,
> + * CAN, WDT, HWMON and RTC management.
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +
> +static const struct mfd_cell nct6694_devs[] = {
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> +	MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> +
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> +	MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),

Why have we gone back to this silly numbering scheme?

What happened to using IDA in the child driver?

> +
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
> +	MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
> +
> +	MFD_CELL_NAME("nct6694-hwmon"),
> +
> +	MFD_CELL_NAME("nct6694-rtc"),
> +};

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-12 14:00   ` Lee Jones
@ 2025-06-12 14:40     ` Ming Yu
  2025-06-12 15:23       ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-12 14:40 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Dear Lee,

Thank you for reviewing,

Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
>
...
> > +static const struct mfd_cell nct6694_devs[] = {
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > +
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
>
> Why have we gone back to this silly numbering scheme?
>
> What happened to using IDA in the child driver?
>

In a previous version, I tried to maintain a static IDA in each
sub-driver. However, I didn’t consider the case where multiple NCT6694
devices are bound to the same driver — in that case, the IDs are not
fixed and become unusable for my purpose.

I’ve since realized that using pdev->id avoids the need for cell->id,
so I reverted to the earlier approach.

That said, do you think it would be a better solution to manage all
the IDAs centrally within the driver? For example:
in nct6694.c
struct nct6694 {
    struct device *dev;

    struct ida gpio_ida;
    struct ida i2c_ida;
    struct ida can_ida;
    struct ida wdt_ida;
};

static int nct6694_probe(struct platform_device *pdev)
{
    ida_init(&nct6694->gpio_ida);
    ...
}

in gpio-nct6694.c
static int nct6694_gpio_probe(struct platform_device *pdev)
{
    id = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
}


Best regards,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-12 14:40     ` Ming Yu
@ 2025-06-12 15:23       ` Lee Jones
  2025-06-13  1:54         ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-12 15:23 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Thu, 12 Jun 2025, Ming Yu wrote:

> Dear Lee,
> 
> Thank you for reviewing,
> 
> Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> >
> ...
> > > +static const struct mfd_cell nct6694_devs[] = {
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > +
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> >
> > Why have we gone back to this silly numbering scheme?
> >
> > What happened to using IDA in the child driver?
> >
> 
> In a previous version, I tried to maintain a static IDA in each
> sub-driver. However, I didn’t consider the case where multiple NCT6694
> devices are bound to the same driver — in that case, the IDs are not
> fixed and become unusable for my purpose.

Not sure I understand.

> I’ve since realized that using pdev->id avoids the need for cell->id,
> so I reverted to the earlier approach.
> 
> That said, do you think it would be a better solution to manage all
> the IDAs centrally within the driver? For example:
> in nct6694.c
> struct nct6694 {
>     struct device *dev;
> 
>     struct ida gpio_ida;
>     struct ida i2c_ida;
>     struct ida can_ida;
>     struct ida wdt_ida;
> };
> 
> static int nct6694_probe(struct platform_device *pdev)
> {
>     ida_init(&nct6694->gpio_ida);
>     ...
> }
> 
> in gpio-nct6694.c
> static int nct6694_gpio_probe(struct platform_device *pdev)
> {
>     id = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
> }

No that would be way worse.

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-12 15:23       ` Lee Jones
@ 2025-06-13  1:54         ` Ming Yu
  2025-06-13 13:11           ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-13  1:54 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
>
> On Thu, 12 Jun 2025, Ming Yu wrote:
>
> > Dear Lee,
> >
> > Thank you for reviewing,
> >
> > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > >
> > ...
> > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > +
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > >
> > > Why have we gone back to this silly numbering scheme?
> > >
> > > What happened to using IDA in the child driver?
> > >
> >
> > In a previous version, I tried to maintain a static IDA in each
> > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > devices are bound to the same driver — in that case, the IDs are not
> > fixed and become unusable for my purpose.
>
> Not sure I understand.
>

As far as I know, if I maintain the IDA in the sub-drivers and use
multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
NCT6694 device bound to the GPIO driver will receive IDs 0~15.
However, when a second NCT6694 device is connected to the system, it
will receive IDs 16~31.
Because of this behavior, I switched back to using platform_device->id.

> > I’ve since realized that using pdev->id avoids the need for cell->id,
> > so I reverted to the earlier approach.
> >
> > That said, do you think it would be a better solution to manage all
> > the IDAs centrally within the driver? For example:
> > in nct6694.c
> > struct nct6694 {
> >     struct device *dev;
> >
> >     struct ida gpio_ida;
> >     struct ida i2c_ida;
> >     struct ida can_ida;
> >     struct ida wdt_ida;
> > };
> >
> > static int nct6694_probe(struct platform_device *pdev)
> > {
> >     ida_init(&nct6694->gpio_ida);
> >     ...
> > }
> >
> > in gpio-nct6694.c
> > static int nct6694_gpio_probe(struct platform_device *pdev)
> > {
> >     id = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
> > }
>
> No that would be way worse.
>


Thanks,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-13  1:54         ` Ming Yu
@ 2025-06-13 13:11           ` Lee Jones
  2025-06-13 15:09             ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-13 13:11 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Fri, 13 Jun 2025, Ming Yu wrote:

> Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> >
> > On Thu, 12 Jun 2025, Ming Yu wrote:
> >
> > > Dear Lee,
> > >
> > > Thank you for reviewing,
> > >
> > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > >
> > > ...
> > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > +
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > >
> > > > Why have we gone back to this silly numbering scheme?
> > > >
> > > > What happened to using IDA in the child driver?
> > > >
> > >
> > > In a previous version, I tried to maintain a static IDA in each
> > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > devices are bound to the same driver — in that case, the IDs are not
> > > fixed and become unusable for my purpose.
> >
> > Not sure I understand.
> >
> 
> As far as I know, if I maintain the IDA in the sub-drivers and use
> multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> However, when a second NCT6694 device is connected to the system, it
> will receive IDs 16~31.
> Because of this behavior, I switched back to using platform_device->id.

Each of the devices will probe once.

The first one will be given 0, the second will be given 1, etc.

Why would you give multiple IDs to a single device bound to a driver?

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-13 13:11           ` Lee Jones
@ 2025-06-13 15:09             ` Ming Yu
  2025-06-19 11:53               ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-13 15:09 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
>
> On Fri, 13 Jun 2025, Ming Yu wrote:
>
> > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > >
> > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > >
> > > > Dear Lee,
> > > >
> > > > Thank you for reviewing,
> > > >
> > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > >
> > > > ...
> > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > +
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > >
> > > > > Why have we gone back to this silly numbering scheme?
> > > > >
> > > > > What happened to using IDA in the child driver?
> > > > >
> > > >
> > > > In a previous version, I tried to maintain a static IDA in each
> > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > devices are bound to the same driver — in that case, the IDs are not
> > > > fixed and become unusable for my purpose.
> > >
> > > Not sure I understand.
> > >
> >
> > As far as I know, if I maintain the IDA in the sub-drivers and use
> > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > However, when a second NCT6694 device is connected to the system, it
> > will receive IDs 16~31.
> > Because of this behavior, I switched back to using platform_device->id.
>
> Each of the devices will probe once.
>
> The first one will be given 0, the second will be given 1, etc.
>
> Why would you give multiple IDs to a single device bound to a driver?
>

The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
is independently addressable, has its own register region, and can
operate in isolation. The IDs are used to distinguish between these
instances.
For example, the GPIO driver will be probed 16 times, allocating 16
separate gpio_chip instances to control 8 GPIO lines each.

If another device binds to this driver, it is expected to expose
peripherals with the same structure and behavior.


Thanks,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-13 15:09             ` Ming Yu
@ 2025-06-19 11:53               ` Lee Jones
  2025-06-19 12:24                 ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-19 11:53 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Fri, 13 Jun 2025, Ming Yu wrote:

> Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> >
> > On Fri, 13 Jun 2025, Ming Yu wrote:
> >
> > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > >
> > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > >
> > > > > Dear Lee,
> > > > >
> > > > > Thank you for reviewing,
> > > > >
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > >
> > > > > ...
> > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > +
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > >
> > > > > > Why have we gone back to this silly numbering scheme?
> > > > > >
> > > > > > What happened to using IDA in the child driver?
> > > > > >
> > > > >
> > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > fixed and become unusable for my purpose.
> > > >
> > > > Not sure I understand.
> > > >
> > >
> > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > However, when a second NCT6694 device is connected to the system, it
> > > will receive IDs 16~31.
> > > Because of this behavior, I switched back to using platform_device->id.
> >
> > Each of the devices will probe once.
> >
> > The first one will be given 0, the second will be given 1, etc.
> >
> > Why would you give multiple IDs to a single device bound to a driver?
> >
> 
> The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> is independently addressable, has its own register region, and can
> operate in isolation. The IDs are used to distinguish between these
> instances.
> For example, the GPIO driver will be probed 16 times, allocating 16
> separate gpio_chip instances to control 8 GPIO lines each.
> 
> If another device binds to this driver, it is expected to expose
> peripherals with the same structure and behavior.

I still don't see why having a per-device IDA wouldn't render each
probed device with its own ID.  Just as you have above.

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 11:53               ` Lee Jones
@ 2025-06-19 12:24                 ` Ming Yu
  2025-06-19 15:28                   ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-19 12:24 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
>
> On Fri, 13 Jun 2025, Ming Yu wrote:
>
> > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > >
> > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > >
> > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > >
> > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > >
> > > > > > Dear Lee,
> > > > > >
> > > > > > Thank you for reviewing,
> > > > > >
> > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > >
> > > > > > ...
> > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > +
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > >
> > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > >
> > > > > > > What happened to using IDA in the child driver?
> > > > > > >
> > > > > >
> > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > fixed and become unusable for my purpose.
> > > > >
> > > > > Not sure I understand.
> > > > >
> > > >
> > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > However, when a second NCT6694 device is connected to the system, it
> > > > will receive IDs 16~31.
> > > > Because of this behavior, I switched back to using platform_device->id.
> > >
> > > Each of the devices will probe once.
> > >
> > > The first one will be given 0, the second will be given 1, etc.
> > >
> > > Why would you give multiple IDs to a single device bound to a driver?
> > >
> >
> > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > is independently addressable, has its own register region, and can
> > operate in isolation. The IDs are used to distinguish between these
> > instances.
> > For example, the GPIO driver will be probed 16 times, allocating 16
> > separate gpio_chip instances to control 8 GPIO lines each.
> >
> > If another device binds to this driver, it is expected to expose
> > peripherals with the same structure and behavior.
>
> I still don't see why having a per-device IDA wouldn't render each
> probed device with its own ID.  Just as you have above.
>

For example, when the MFD driver and the I2C sub-driver are loaded,
connecting the first NCT6694 USB device to the system results in 6
nct6694-i2c platform devices being created and bound to the
i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.

However, when a second NCT6694 USB device is connected, its
corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
instead of 0 through 5 as I originally expected.

If I've misunderstood something, please feel free to correct me. Thank you!


Sincerely,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 12:24                 ` Ming Yu
@ 2025-06-19 15:28                   ` Lee Jones
  2025-06-19 16:03                     ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-19 15:28 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Thu, 19 Jun 2025, Ming Yu wrote:

> Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> >
> > On Fri, 13 Jun 2025, Ming Yu wrote:
> >
> > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > >
> > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > >
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > >
> > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > >
> > > > > > > Dear Lee,
> > > > > > >
> > > > > > > Thank you for reviewing,
> > > > > > >
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > >
> > > > > > > ...
> > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > +
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > >
> > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > >
> > > > > > > > What happened to using IDA in the child driver?
> > > > > > > >
> > > > > > >
> > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > fixed and become unusable for my purpose.
> > > > > >
> > > > > > Not sure I understand.
> > > > > >
> > > > >
> > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > will receive IDs 16~31.
> > > > > Because of this behavior, I switched back to using platform_device->id.
> > > >
> > > > Each of the devices will probe once.
> > > >
> > > > The first one will be given 0, the second will be given 1, etc.
> > > >
> > > > Why would you give multiple IDs to a single device bound to a driver?
> > > >
> > >
> > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > is independently addressable, has its own register region, and can
> > > operate in isolation. The IDs are used to distinguish between these
> > > instances.
> > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > separate gpio_chip instances to control 8 GPIO lines each.
> > >
> > > If another device binds to this driver, it is expected to expose
> > > peripherals with the same structure and behavior.
> >
> > I still don't see why having a per-device IDA wouldn't render each
> > probed device with its own ID.  Just as you have above.
> >
> 
> For example, when the MFD driver and the I2C sub-driver are loaded,
> connecting the first NCT6694 USB device to the system results in 6
> nct6694-i2c platform devices being created and bound to the
> i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> 
> However, when a second NCT6694 USB device is connected, its
> corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> instead of 0 through 5 as I originally expected.
> 
> If I've misunderstood something, please feel free to correct me. Thank you!

In the code above you register 6 I2C devices.  Each device will be
assigned a platform ID 0 through 5. The .probe() function in the I2C
driver will be executed 6 times.  In each of those calls to .probe(),
instead of pre-allocating a contiguous assignment of IDs here, you
should be able to use IDA in .probe() to allocate those same device IDs
0 through 5.

What am I missing here?

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 15:28                   ` Lee Jones
@ 2025-06-19 16:03                     ` Ming Yu
  2025-06-19 16:20                       ` Greg KH
  2025-06-25  9:01                       ` Lee Jones
  0 siblings, 2 replies; 33+ messages in thread
From: Ming Yu @ 2025-06-19 16:03 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
>
> On Thu, 19 Jun 2025, Ming Yu wrote:
>
> > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > >
> > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > >
> > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > >
> > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > >
> > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > >
> > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > >
> > > > > > > > Dear Lee,
> > > > > > > >
> > > > > > > > Thank you for reviewing,
> > > > > > > >
> > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > >
> > > > > > > > ...
> > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > +
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > >
> > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > >
> > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > >
> > > > > > > >
> > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > fixed and become unusable for my purpose.
> > > > > > >
> > > > > > > Not sure I understand.
> > > > > > >
> > > > > >
> > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > will receive IDs 16~31.
> > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > >
> > > > > Each of the devices will probe once.
> > > > >
> > > > > The first one will be given 0, the second will be given 1, etc.
> > > > >
> > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > >
> > > >
> > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > is independently addressable, has its own register region, and can
> > > > operate in isolation. The IDs are used to distinguish between these
> > > > instances.
> > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > >
> > > > If another device binds to this driver, it is expected to expose
> > > > peripherals with the same structure and behavior.
> > >
> > > I still don't see why having a per-device IDA wouldn't render each
> > > probed device with its own ID.  Just as you have above.
> > >
> >
> > For example, when the MFD driver and the I2C sub-driver are loaded,
> > connecting the first NCT6694 USB device to the system results in 6
> > nct6694-i2c platform devices being created and bound to the
> > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> >
> > However, when a second NCT6694 USB device is connected, its
> > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > instead of 0 through 5 as I originally expected.
> >
> > If I've misunderstood something, please feel free to correct me. Thank you!
>
> In the code above you register 6 I2C devices.  Each device will be
> assigned a platform ID 0 through 5. The .probe() function in the I2C
> driver will be executed 6 times.  In each of those calls to .probe(),
> instead of pre-allocating a contiguous assignment of IDs here, you
> should be able to use IDA in .probe() to allocate those same device IDs
> 0 through 5.
>
> What am I missing here?
>

You're absolutely right in the scenario where a single NCT6694 device
is present. However, I’m wondering how we should handle the case where
a second or even third NCT6694 device is bound to the same MFD driver.
In that situation, the sub-drivers using a static IDA will continue
allocating increasing IDs, rather than restarting from 0 for each
device. How should this be handled?

Or am I doing something wrong?


Thanks,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 16:03                     ` Ming Yu
@ 2025-06-19 16:20                       ` Greg KH
  2025-06-19 16:58                         ` Guenter Roeck
  2025-06-25  9:01                       ` Lee Jones
  1 sibling, 1 reply; 33+ messages in thread
From: Greg KH @ 2025-06-19 16:20 UTC (permalink / raw)
  To: Ming Yu
  Cc: Lee Jones, 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,
	linux-usb, Ming Yu

On Fri, Jun 20, 2025 at 12:03:01AM +0800, Ming Yu wrote:
> Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> >
> > On Thu, 19 Jun 2025, Ming Yu wrote:
> >
> > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > >
> > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > >
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > >
> > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > >
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > >
> > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > >
> > > > > > > > > Dear Lee,
> > > > > > > > >
> > > > > > > > > Thank you for reviewing,
> > > > > > > > >
> > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > >
> > > > > > > > > ...
> > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > +
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > >
> > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > >
> > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > >
> > > > > > > > Not sure I understand.
> > > > > > > >
> > > > > > >
> > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > will receive IDs 16~31.
> > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > >
> > > > > > Each of the devices will probe once.
> > > > > >
> > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > >
> > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > >
> > > > >
> > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > is independently addressable, has its own register region, and can
> > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > instances.
> > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > >
> > > > > If another device binds to this driver, it is expected to expose
> > > > > peripherals with the same structure and behavior.
> > > >
> > > > I still don't see why having a per-device IDA wouldn't render each
> > > > probed device with its own ID.  Just as you have above.
> > > >
> > >
> > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > connecting the first NCT6694 USB device to the system results in 6
> > > nct6694-i2c platform devices being created and bound to the
> > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > >
> > > However, when a second NCT6694 USB device is connected, its
> > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > instead of 0 through 5 as I originally expected.
> > >
> > > If I've misunderstood something, please feel free to correct me. Thank you!
> >
> > In the code above you register 6 I2C devices.  Each device will be
> > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > driver will be executed 6 times.  In each of those calls to .probe(),
> > instead of pre-allocating a contiguous assignment of IDs here, you
> > should be able to use IDA in .probe() to allocate those same device IDs
> > 0 through 5.
> >
> > What am I missing here?
> >
> 
> You're absolutely right in the scenario where a single NCT6694 device
> is present. However, I’m wondering how we should handle the case where
> a second or even third NCT6694 device is bound to the same MFD driver.
> In that situation, the sub-drivers using a static IDA will continue
> allocating increasing IDs, rather than restarting from 0 for each
> device. How should this be handled?

What is wrong with increasing ids?  The id value means nothing, they
just have to be unique.

thanks,

greg k-h

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 16:20                       ` Greg KH
@ 2025-06-19 16:58                         ` Guenter Roeck
  2025-06-19 17:18                           ` Greg KH
  0 siblings, 1 reply; 33+ messages in thread
From: Guenter Roeck @ 2025-06-19 16:58 UTC (permalink / raw)
  To: Greg KH, Ming Yu
  Cc: Lee Jones, 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, linux-usb,
	Ming Yu

On 6/19/25 09:20, Greg KH wrote:
> On Fri, Jun 20, 2025 at 12:03:01AM +0800, Ming Yu wrote:
>> Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
>>>
>>> On Thu, 19 Jun 2025, Ming Yu wrote:
>>>
>>>> Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
>>>>>
>>>>> On Fri, 13 Jun 2025, Ming Yu wrote:
>>>>>
>>>>>> Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
>>>>>>>
>>>>>>> On Fri, 13 Jun 2025, Ming Yu wrote:
>>>>>>>
>>>>>>>> Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
>>>>>>>>>
>>>>>>>>> On Thu, 12 Jun 2025, Ming Yu wrote:
>>>>>>>>>
>>>>>>>>>> Dear Lee,
>>>>>>>>>>
>>>>>>>>>> Thank you for reviewing,
>>>>>>>>>>
>>>>>>>>>> Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
>>>>>>>>>>>
>>>>>>>>>> ...
>>>>>>>>>>>> +static const struct mfd_cell nct6694_devs[] = {
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
>>>>>>>>>>>> +
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
>>>>>>>>>>>> +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
>>>>>>>>>>>
>>>>>>>>>>> Why have we gone back to this silly numbering scheme?
>>>>>>>>>>>
>>>>>>>>>>> What happened to using IDA in the child driver?
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> In a previous version, I tried to maintain a static IDA in each
>>>>>>>>>> sub-driver. However, I didn’t consider the case where multiple NCT6694
>>>>>>>>>> devices are bound to the same driver — in that case, the IDs are not
>>>>>>>>>> fixed and become unusable for my purpose.
>>>>>>>>>
>>>>>>>>> Not sure I understand.
>>>>>>>>>
>>>>>>>>
>>>>>>>> As far as I know, if I maintain the IDA in the sub-drivers and use
>>>>>>>> multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
>>>>>>>> NCT6694 device bound to the GPIO driver will receive IDs 0~15.
>>>>>>>> However, when a second NCT6694 device is connected to the system, it
>>>>>>>> will receive IDs 16~31.
>>>>>>>> Because of this behavior, I switched back to using platform_device->id.
>>>>>>>
>>>>>>> Each of the devices will probe once.
>>>>>>>
>>>>>>> The first one will be given 0, the second will be given 1, etc.
>>>>>>>
>>>>>>> Why would you give multiple IDs to a single device bound to a driver?
>>>>>>>
>>>>>>
>>>>>> The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
>>>>>> adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
>>>>>> is independently addressable, has its own register region, and can
>>>>>> operate in isolation. The IDs are used to distinguish between these
>>>>>> instances.
>>>>>> For example, the GPIO driver will be probed 16 times, allocating 16
>>>>>> separate gpio_chip instances to control 8 GPIO lines each.
>>>>>>
>>>>>> If another device binds to this driver, it is expected to expose
>>>>>> peripherals with the same structure and behavior.
>>>>>
>>>>> I still don't see why having a per-device IDA wouldn't render each
>>>>> probed device with its own ID.  Just as you have above.
>>>>>
>>>>
>>>> For example, when the MFD driver and the I2C sub-driver are loaded,
>>>> connecting the first NCT6694 USB device to the system results in 6
>>>> nct6694-i2c platform devices being created and bound to the
>>>> i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
>>>>
>>>> However, when a second NCT6694 USB device is connected, its
>>>> corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
>>>> instead of 0 through 5 as I originally expected.
>>>>
>>>> If I've misunderstood something, please feel free to correct me. Thank you!
>>>
>>> In the code above you register 6 I2C devices.  Each device will be
>>> assigned a platform ID 0 through 5. The .probe() function in the I2C
>>> driver will be executed 6 times.  In each of those calls to .probe(),
>>> instead of pre-allocating a contiguous assignment of IDs here, you
>>> should be able to use IDA in .probe() to allocate those same device IDs
>>> 0 through 5.
>>>
>>> What am I missing here?
>>>
>>
>> You're absolutely right in the scenario where a single NCT6694 device
>> is present. However, I’m wondering how we should handle the case where
>> a second or even third NCT6694 device is bound to the same MFD driver.
>> In that situation, the sub-drivers using a static IDA will continue
>> allocating increasing IDs, rather than restarting from 0 for each
>> device. How should this be handled?
> 
> What is wrong with increasing ids?  The id value means nothing, they
> just have to be unique.
> 

Unless they are used in the client driver as index into an array, as in
"this is the Nth instance of this device for this chip". There has to be
_some_ means to pass N to the client driver.

Guenter


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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 16:58                         ` Guenter Roeck
@ 2025-06-19 17:18                           ` Greg KH
  2025-06-20  2:54                             ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Greg KH @ 2025-06-19 17:18 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Ming Yu, Lee Jones, 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, linux-usb, Ming Yu

On Thu, Jun 19, 2025 at 09:58:04AM -0700, Guenter Roeck wrote:
> On 6/19/25 09:20, Greg KH wrote:
> > On Fri, Jun 20, 2025 at 12:03:01AM +0800, Ming Yu wrote:
> > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > > > 
> > > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > > > 
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > > > 
> > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > 
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > > > 
> > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > > 
> > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > > > 
> > > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > > > 
> > > > > > > > > > > Dear Lee,
> > > > > > > > > > > 
> > > > > > > > > > > Thank you for reviewing,
> > > > > > > > > > > 
> > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > > > 
> > > > > > > > > > > ...
> > > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > > > 
> > > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > > > 
> > > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > > > 
> > > > > > > > > > > 
> > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > > > 
> > > > > > > > > > Not sure I understand.
> > > > > > > > > > 
> > > > > > > > > 
> > > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > > will receive IDs 16~31.
> > > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > > > 
> > > > > > > > Each of the devices will probe once.
> > > > > > > > 
> > > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > > > 
> > > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > > > 
> > > > > > > 
> > > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > > is independently addressable, has its own register region, and can
> > > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > > instances.
> > > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > > > 
> > > > > > > If another device binds to this driver, it is expected to expose
> > > > > > > peripherals with the same structure and behavior.
> > > > > > 
> > > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > > probed device with its own ID.  Just as you have above.
> > > > > > 
> > > > > 
> > > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > > connecting the first NCT6694 USB device to the system results in 6
> > > > > nct6694-i2c platform devices being created and bound to the
> > > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > > > 
> > > > > However, when a second NCT6694 USB device is connected, its
> > > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > > instead of 0 through 5 as I originally expected.
> > > > > 
> > > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > > > 
> > > > In the code above you register 6 I2C devices.  Each device will be
> > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > 0 through 5.
> > > > 
> > > > What am I missing here?
> > > > 
> > > 
> > > You're absolutely right in the scenario where a single NCT6694 device
> > > is present. However, I’m wondering how we should handle the case where
> > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > In that situation, the sub-drivers using a static IDA will continue
> > > allocating increasing IDs, rather than restarting from 0 for each
> > > device. How should this be handled?
> > 
> > What is wrong with increasing ids?  The id value means nothing, they
> > just have to be unique.
> > 
> 
> Unless they are used in the client driver as index into an array, as in
> "this is the Nth instance of this device for this chip". There has to be
> _some_ means to pass N to the client driver.

Ick, that should just be walking the list of child devices instead, as
obviously no one is hard coding array sizes for devices these days,
right?  :)

Anyway, sure, if you _have_ to have a specific id, then use a specific
id, but really, it should not matter.

thanks,

greg k-h

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 17:18                           ` Greg KH
@ 2025-06-20  2:54                             ` Ming Yu
  2025-06-20  5:02                               ` Greg KH
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-20  2:54 UTC (permalink / raw)
  To: Greg KH
  Cc: Guenter Roeck, Lee Jones, 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, linux-usb, Ming Yu

Dear Guenter and Greg,

Thank you for reviewing,

Greg KH <gregkh@linuxfoundation.org> 於 2025年6月20日 週五 上午1:18寫道:
>
> On Thu, Jun 19, 2025 at 09:58:04AM -0700, Guenter Roeck wrote:
> > On 6/19/25 09:20, Greg KH wrote:
> > > On Fri, Jun 20, 2025 at 12:03:01AM +0800, Ming Yu wrote:
> > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > > > >
> > > > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > > > >
> > > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > > > >
> > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > >
> > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > > > >
> > > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > > >
> > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > > > >
> > > > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > > > >
> > > > > > > > > > > > Dear Lee,
> > > > > > > > > > > >
> > > > > > > > > > > > Thank you for reviewing,
> > > > > > > > > > > >
> > > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > > > >
> > > > > > > > > > > > ...
> > > > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > > > +
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > > > >
> > > > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > > > >
> > > > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > > > >
> > > > > > > > > > > >
> > > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > > > >
> > > > > > > > > > > Not sure I understand.
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > > > will receive IDs 16~31.
> > > > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > > > >
> > > > > > > > > Each of the devices will probe once.
> > > > > > > > >
> > > > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > > > >
> > > > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > > > >
> > > > > > > >
> > > > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > > > is independently addressable, has its own register region, and can
> > > > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > > > instances.
> > > > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > > > >
> > > > > > > > If another device binds to this driver, it is expected to expose
> > > > > > > > peripherals with the same structure and behavior.
> > > > > > >
> > > > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > > > probed device with its own ID.  Just as you have above.
> > > > > > >
> > > > > >
> > > > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > > > connecting the first NCT6694 USB device to the system results in 6
> > > > > > nct6694-i2c platform devices being created and bound to the
> > > > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > > > >
> > > > > > However, when a second NCT6694 USB device is connected, its
> > > > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > > > instead of 0 through 5 as I originally expected.
> > > > > >
> > > > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > > > >
> > > > > In the code above you register 6 I2C devices.  Each device will be
> > > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > > 0 through 5.
> > > > >
> > > > > What am I missing here?
> > > > >
> > > >
> > > > You're absolutely right in the scenario where a single NCT6694 device
> > > > is present. However, I’m wondering how we should handle the case where
> > > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > > In that situation, the sub-drivers using a static IDA will continue
> > > > allocating increasing IDs, rather than restarting from 0 for each
> > > > device. How should this be handled?
> > >
> > > What is wrong with increasing ids?  The id value means nothing, they
> > > just have to be unique.
> > >
> >
> > Unless they are used in the client driver as index into an array, as in
> > "this is the Nth instance of this device for this chip". There has to be
> > _some_ means to pass N to the client driver.
>
> Ick, that should just be walking the list of child devices instead, as
> obviously no one is hard coding array sizes for devices these days,
> right?  :)
>
> Anyway, sure, if you _have_ to have a specific id, then use a specific
> id, but really, it should not matter.
>

I need fixed IDs in order to communicate with the sub-devices
correctly. For instance, the I2C driver registers 6 devices, and the
userspace interface needs to know which specific I2C controller (e.g.,
index 0 ~ 5) to target with custom commands. Using fixed IDs allow the
driver to maintain a consistent mapping between device instances and
register sets.

I'm open to better alternatives, but so far, using fixed
platform_device->id has been the most straightforward way to achieve
this.


Best regards,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-20  2:54                             ` Ming Yu
@ 2025-06-20  5:02                               ` Greg KH
  0 siblings, 0 replies; 33+ messages in thread
From: Greg KH @ 2025-06-20  5:02 UTC (permalink / raw)
  To: Ming Yu
  Cc: Guenter Roeck, Lee Jones, 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, linux-usb, Ming Yu

On Fri, Jun 20, 2025 at 10:54:44AM +0800, Ming Yu wrote:
> Dear Guenter and Greg,
> 
> Thank you for reviewing,
> 
> Greg KH <gregkh@linuxfoundation.org> 於 2025年6月20日 週五 上午1:18寫道:
> >
> > On Thu, Jun 19, 2025 at 09:58:04AM -0700, Guenter Roeck wrote:
> > > On 6/19/25 09:20, Greg KH wrote:
> > > > On Fri, Jun 20, 2025 at 12:03:01AM +0800, Ming Yu wrote:
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > > > > >
> > > > > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > > > > >
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > > > > >
> > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > >
> > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > > > > >
> > > > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > > > >
> > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > > > > >
> > > > > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > > > > >
> > > > > > > > > > > > > Dear Lee,
> > > > > > > > > > > > >
> > > > > > > > > > > > > Thank you for reviewing,
> > > > > > > > > > > > >
> > > > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > > > > >
> > > > > > > > > > > > > ...
> > > > > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > > > > >
> > > > > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > > > > >
> > > > > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > > > > >
> > > > > > > > > > > > >
> > > > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > > > > >
> > > > > > > > > > > > Not sure I understand.
> > > > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > > > > will receive IDs 16~31.
> > > > > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > > > > >
> > > > > > > > > > Each of the devices will probe once.
> > > > > > > > > >
> > > > > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > > > > >
> > > > > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > > > > is independently addressable, has its own register region, and can
> > > > > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > > > > instances.
> > > > > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > > > > >
> > > > > > > > > If another device binds to this driver, it is expected to expose
> > > > > > > > > peripherals with the same structure and behavior.
> > > > > > > >
> > > > > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > > > > probed device with its own ID.  Just as you have above.
> > > > > > > >
> > > > > > >
> > > > > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > > > > connecting the first NCT6694 USB device to the system results in 6
> > > > > > > nct6694-i2c platform devices being created and bound to the
> > > > > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > > > > >
> > > > > > > However, when a second NCT6694 USB device is connected, its
> > > > > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > > > > instead of 0 through 5 as I originally expected.
> > > > > > >
> > > > > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > > > > >
> > > > > > In the code above you register 6 I2C devices.  Each device will be
> > > > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > > > 0 through 5.
> > > > > >
> > > > > > What am I missing here?
> > > > > >
> > > > >
> > > > > You're absolutely right in the scenario where a single NCT6694 device
> > > > > is present. However, I’m wondering how we should handle the case where
> > > > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > > > In that situation, the sub-drivers using a static IDA will continue
> > > > > allocating increasing IDs, rather than restarting from 0 for each
> > > > > device. How should this be handled?
> > > >
> > > > What is wrong with increasing ids?  The id value means nothing, they
> > > > just have to be unique.
> > > >
> > >
> > > Unless they are used in the client driver as index into an array, as in
> > > "this is the Nth instance of this device for this chip". There has to be
> > > _some_ means to pass N to the client driver.
> >
> > Ick, that should just be walking the list of child devices instead, as
> > obviously no one is hard coding array sizes for devices these days,
> > right?  :)
> >
> > Anyway, sure, if you _have_ to have a specific id, then use a specific
> > id, but really, it should not matter.
> >
> 
> I need fixed IDs in order to communicate with the sub-devices
> correctly. For instance, the I2C driver registers 6 devices, and the
> userspace interface needs to know which specific I2C controller (e.g.,
> index 0 ~ 5) to target with custom commands. Using fixed IDs allow the
> driver to maintain a consistent mapping between device instances and
> register sets.
> 
> I'm open to better alternatives, but so far, using fixed
> platform_device->id has been the most straightforward way to achieve
> this.

The kernel is not responsible for numbering devices in a determinisitic
way like this, so be very careful.  Userspace can implement whatever
policy it wants to figure out what device id is what actual device, by
using tools like udev and the like.  See how it does this today by
looking at your /dev/disks/ directory on your system at the symlinks.

So good luck with this, but again, it's not the kernel's job to keep
device numbering static across boots, that is not something we guarantee
at all.

Perhaps you should not be using mfd for this?  Perhaps something like
the auxbus would work better?

thanks,

greg k-h

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-19 16:03                     ` Ming Yu
  2025-06-19 16:20                       ` Greg KH
@ 2025-06-25  9:01                       ` Lee Jones
  2025-06-25 10:54                         ` Ming Yu
  1 sibling, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-25  9:01 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Fri, 20 Jun 2025, Ming Yu wrote:

> Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> >
> > On Thu, 19 Jun 2025, Ming Yu wrote:
> >
> > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > >
> > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > >
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > >
> > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > >
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > >
> > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > >
> > > > > > > > > Dear Lee,
> > > > > > > > >
> > > > > > > > > Thank you for reviewing,
> > > > > > > > >
> > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > >
> > > > > > > > > ...
> > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > +
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > >
> > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > >
> > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > >
> > > > > > > > Not sure I understand.
> > > > > > > >
> > > > > > >
> > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > will receive IDs 16~31.
> > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > >
> > > > > > Each of the devices will probe once.
> > > > > >
> > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > >
> > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > >
> > > > >
> > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > is independently addressable, has its own register region, and can
> > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > instances.
> > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > >
> > > > > If another device binds to this driver, it is expected to expose
> > > > > peripherals with the same structure and behavior.
> > > >
> > > > I still don't see why having a per-device IDA wouldn't render each
> > > > probed device with its own ID.  Just as you have above.
> > > >
> > >
> > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > connecting the first NCT6694 USB device to the system results in 6
> > > nct6694-i2c platform devices being created and bound to the
> > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > >
> > > However, when a second NCT6694 USB device is connected, its
> > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > instead of 0 through 5 as I originally expected.
> > >
> > > If I've misunderstood something, please feel free to correct me. Thank you!
> >
> > In the code above you register 6 I2C devices.  Each device will be
> > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > driver will be executed 6 times.  In each of those calls to .probe(),
> > instead of pre-allocating a contiguous assignment of IDs here, you
> > should be able to use IDA in .probe() to allocate those same device IDs
> > 0 through 5.
> >
> > What am I missing here?
> >
> 
> You're absolutely right in the scenario where a single NCT6694 device
> is present. However, I’m wondering how we should handle the case where
> a second or even third NCT6694 device is bound to the same MFD driver.
> In that situation, the sub-drivers using a static IDA will continue
> allocating increasing IDs, rather than restarting from 0 for each
> device. How should this be handled?

I'd like to see the implementation of this before advising.

In such a case, I assume there would be a differentiating factor between
the two (or three) devices.  You would then use that to decide which IDA
would need to be incremented.

However, Greg is correct.  Hard-coding look-ups for userspace to use
sounds like a terrible idea.

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-25  9:01                       ` Lee Jones
@ 2025-06-25 10:54                         ` Ming Yu
  2025-06-25 13:46                           ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-25 10:54 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Dear Greg and Lee,

Thank you for your comments.
I've reviewed your suggestions, but would appreciate your feedback on
a few remaining points.

Lee Jones <lee@kernel.org> 於 2025年6月25日 週三 下午5:01寫道:
>
> On Fri, 20 Jun 2025, Ming Yu wrote:
>
> > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > >
> > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > >
> > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > >
> > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > >
> > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > >
> > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > >
> > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > >
> > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > >
> > > > > > > > > > Dear Lee,
> > > > > > > > > >
> > > > > > > > > > Thank you for reviewing,
> > > > > > > > > >
> > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > >
> > > > > > > > > > ...
> > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > +
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > >
> > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > >
> > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > >
> > > > > > > > > Not sure I understand.
> > > > > > > > >
> > > > > > > >
> > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > will receive IDs 16~31.
> > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > >
> > > > > > > Each of the devices will probe once.
> > > > > > >
> > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > >
> > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > >
> > > > > >
> > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > is independently addressable, has its own register region, and can
> > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > instances.
> > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > >
> > > > > > If another device binds to this driver, it is expected to expose
> > > > > > peripherals with the same structure and behavior.
> > > > >
> > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > probed device with its own ID.  Just as you have above.
> > > > >
> > > >
> > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > connecting the first NCT6694 USB device to the system results in 6
> > > > nct6694-i2c platform devices being created and bound to the
> > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > >
> > > > However, when a second NCT6694 USB device is connected, its
> > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > instead of 0 through 5 as I originally expected.
> > > >
> > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > >
> > > In the code above you register 6 I2C devices.  Each device will be
> > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > should be able to use IDA in .probe() to allocate those same device IDs
> > > 0 through 5.
> > >
> > > What am I missing here?
> > >
> >
> > You're absolutely right in the scenario where a single NCT6694 device
> > is present. However, I’m wondering how we should handle the case where
> > a second or even third NCT6694 device is bound to the same MFD driver.
> > In that situation, the sub-drivers using a static IDA will continue
> > allocating increasing IDs, rather than restarting from 0 for each
> > device. How should this be handled?
>
> I'd like to see the implementation of this before advising.
>
> In such a case, I assume there would be a differentiating factor between
> the two (or three) devices.  You would then use that to decide which IDA
> would need to be incremented.
>
> However, Greg is correct.  Hard-coding look-ups for userspace to use
> sounds like a terrible idea.
>

I understand.
Do you think it would be better to pass the index via platform_data
and use PLATFORM_DEVID_AUTO together with mfd_add_hotplug_devices()
instead?
For example:
struct nct6694_platform_data {
    int index;
};

static struct nct6694_platform_data i2c_data[] = {
    { .index = 0 }, { .index = 1 }, { .index = 2 }, { .index = 3 }, {
.index = 4 }, { .index = 5 },
};

static const struct mfd_cell nct6694_devs[] = {
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[0], sizeof(struct
nct6694_platform_data), 0),
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[1], sizeof(struct
nct6694_platform_data), 0),
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[2], sizeof(struct
nct6694_platform_data), 0),
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[3], sizeof(struct
nct6694_platform_data), 0),
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[4], sizeof(struct
nct6694_platform_data), 0),
    MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[5], sizeof(struct
nct6694_platform_data), 0),
};
...
mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
...

Thank you again for your support.


Best regards,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-25 10:54                         ` Ming Yu
@ 2025-06-25 13:46                           ` Lee Jones
  2025-06-25 14:24                             ` Ming Yu
  0 siblings, 1 reply; 33+ messages in thread
From: Lee Jones @ 2025-06-25 13:46 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

On Wed, 25 Jun 2025, Ming Yu wrote:

> Dear Greg and Lee,
> 
> Thank you for your comments.
> I've reviewed your suggestions, but would appreciate your feedback on
> a few remaining points.
> 
> Lee Jones <lee@kernel.org> 於 2025年6月25日 週三 下午5:01寫道:
> >
> > On Fri, 20 Jun 2025, Ming Yu wrote:
> >
> > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > > >
> > > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > > >
> > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > > >
> > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > >
> > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > > >
> > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > >
> > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > > >
> > > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > > >
> > > > > > > > > > > Dear Lee,
> > > > > > > > > > >
> > > > > > > > > > > Thank you for reviewing,
> > > > > > > > > > >
> > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > > >
> > > > > > > > > > > ...
> > > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > > >
> > > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > > >
> > > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > > >
> > > > > > > > > > Not sure I understand.
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > > will receive IDs 16~31.
> > > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > > >
> > > > > > > > Each of the devices will probe once.
> > > > > > > >
> > > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > > >
> > > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > > >
> > > > > > >
> > > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > > is independently addressable, has its own register region, and can
> > > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > > instances.
> > > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > > >
> > > > > > > If another device binds to this driver, it is expected to expose
> > > > > > > peripherals with the same structure and behavior.
> > > > > >
> > > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > > probed device with its own ID.  Just as you have above.
> > > > > >
> > > > >
> > > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > > connecting the first NCT6694 USB device to the system results in 6
> > > > > nct6694-i2c platform devices being created and bound to the
> > > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > > >
> > > > > However, when a second NCT6694 USB device is connected, its
> > > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > > instead of 0 through 5 as I originally expected.
> > > > >
> > > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > > >
> > > > In the code above you register 6 I2C devices.  Each device will be
> > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > 0 through 5.
> > > >
> > > > What am I missing here?
> > > >
> > >
> > > You're absolutely right in the scenario where a single NCT6694 device
> > > is present. However, I’m wondering how we should handle the case where
> > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > In that situation, the sub-drivers using a static IDA will continue
> > > allocating increasing IDs, rather than restarting from 0 for each
> > > device. How should this be handled?
> >
> > I'd like to see the implementation of this before advising.
> >
> > In such a case, I assume there would be a differentiating factor between
> > the two (or three) devices.  You would then use that to decide which IDA
> > would need to be incremented.
> >
> > However, Greg is correct.  Hard-coding look-ups for userspace to use
> > sounds like a terrible idea.
> >
> 
> I understand.
> Do you think it would be better to pass the index via platform_data
> and use PLATFORM_DEVID_AUTO together with mfd_add_hotplug_devices()
> instead?
> For example:
> struct nct6694_platform_data {
>     int index;
> };
> 
> static struct nct6694_platform_data i2c_data[] = {
>     { .index = 0 }, { .index = 1 }, { .index = 2 }, { .index = 3 }, {
> .index = 4 }, { .index = 5 },
> };
> 
> static const struct mfd_cell nct6694_devs[] = {
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[0], sizeof(struct
> nct6694_platform_data), 0),
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[1], sizeof(struct
> nct6694_platform_data), 0),
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[2], sizeof(struct
> nct6694_platform_data), 0),
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[3], sizeof(struct
> nct6694_platform_data), 0),
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[4], sizeof(struct
> nct6694_platform_data), 0),
>     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[5], sizeof(struct
> nct6694_platform_data), 0),
> };
> ...
> mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
> ...

No, that's clearly way worse.  =:-)

The clean-up that this provides is probably not worth all of this
discussion.  I _still_ think this enumeration should be done in the
driver.  But if you really can't make it work, I'll accept the .id
patch.

-- 
Lee Jones [李琼斯]

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-25 13:46                           ` Lee Jones
@ 2025-06-25 14:24                             ` Ming Yu
  2025-06-25 14:53                               ` Lee Jones
  0 siblings, 1 reply; 33+ messages in thread
From: Ming Yu @ 2025-06-25 14:24 UTC (permalink / raw)
  To: Lee Jones
  Cc: 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,
	linux-usb, Ming Yu

Lee Jones <lee@kernel.org> 於 2025年6月25日 週三 下午9:46寫道:
>
> On Wed, 25 Jun 2025, Ming Yu wrote:
>
> > Dear Greg and Lee,
> >
> > Thank you for your comments.
> > I've reviewed your suggestions, but would appreciate your feedback on
> > a few remaining points.
> >
> > Lee Jones <lee@kernel.org> 於 2025年6月25日 週三 下午5:01寫道:
> > >
> > > On Fri, 20 Jun 2025, Ming Yu wrote:
> > >
> > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午11:28寫道:
> > > > >
> > > > > On Thu, 19 Jun 2025, Ming Yu wrote:
> > > > >
> > > > > > Lee Jones <lee@kernel.org> 於 2025年6月19日 週四 下午7:53寫道:
> > > > > > >
> > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > >
> > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道:
> > > > > > > > >
> > > > > > > > > On Fri, 13 Jun 2025, Ming Yu wrote:
> > > > > > > > >
> > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道:
> > > > > > > > > > >
> > > > > > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote:
> > > > > > > > > > >
> > > > > > > > > > > > Dear Lee,
> > > > > > > > > > > >
> > > > > > > > > > > > Thank you for reviewing,
> > > > > > > > > > > >
> > > > > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道:
> > > > > > > > > > > > >
> > > > > > > > > > > > ...
> > > > > > > > > > > > > > +static const struct mfd_cell nct6694_devs[] = {
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
> > > > > > > > > > > > > > +
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
> > > > > > > > > > > > > > +     MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
> > > > > > > > > > > > >
> > > > > > > > > > > > > Why have we gone back to this silly numbering scheme?
> > > > > > > > > > > > >
> > > > > > > > > > > > > What happened to using IDA in the child driver?
> > > > > > > > > > > > >
> > > > > > > > > > > >
> > > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each
> > > > > > > > > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694
> > > > > > > > > > > > devices are bound to the same driver — in that case, the IDs are not
> > > > > > > > > > > > fixed and become unusable for my purpose.
> > > > > > > > > > >
> > > > > > > > > > > Not sure I understand.
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use
> > > > > > > > > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first
> > > > > > > > > > NCT6694 device bound to the GPIO driver will receive IDs 0~15.
> > > > > > > > > > However, when a second NCT6694 device is connected to the system, it
> > > > > > > > > > will receive IDs 16~31.
> > > > > > > > > > Because of this behavior, I switched back to using platform_device->id.
> > > > > > > > >
> > > > > > > > > Each of the devices will probe once.
> > > > > > > > >
> > > > > > > > > The first one will be given 0, the second will be given 1, etc.
> > > > > > > > >
> > > > > > > > > Why would you give multiple IDs to a single device bound to a driver?
> > > > > > > > >
> > > > > > > >
> > > > > > > > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C
> > > > > > > > adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral
> > > > > > > > is independently addressable, has its own register region, and can
> > > > > > > > operate in isolation. The IDs are used to distinguish between these
> > > > > > > > instances.
> > > > > > > > For example, the GPIO driver will be probed 16 times, allocating 16
> > > > > > > > separate gpio_chip instances to control 8 GPIO lines each.
> > > > > > > >
> > > > > > > > If another device binds to this driver, it is expected to expose
> > > > > > > > peripherals with the same structure and behavior.
> > > > > > >
> > > > > > > I still don't see why having a per-device IDA wouldn't render each
> > > > > > > probed device with its own ID.  Just as you have above.
> > > > > > >
> > > > > >
> > > > > > For example, when the MFD driver and the I2C sub-driver are loaded,
> > > > > > connecting the first NCT6694 USB device to the system results in 6
> > > > > > nct6694-i2c platform devices being created and bound to the
> > > > > > i2c-nct6694 driver. These devices receive IDs 0 through 5 via the IDA.
> > > > > >
> > > > > > However, when a second NCT6694 USB device is connected, its
> > > > > > corresponding nct6694-i2c platform devices receive IDs 6 through 11 —
> > > > > > instead of 0 through 5 as I originally expected.
> > > > > >
> > > > > > If I've misunderstood something, please feel free to correct me. Thank you!
> > > > >
> > > > > In the code above you register 6 I2C devices.  Each device will be
> > > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > > 0 through 5.
> > > > >
> > > > > What am I missing here?
> > > > >
> > > >
> > > > You're absolutely right in the scenario where a single NCT6694 device
> > > > is present. However, I’m wondering how we should handle the case where
> > > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > > In that situation, the sub-drivers using a static IDA will continue
> > > > allocating increasing IDs, rather than restarting from 0 for each
> > > > device. How should this be handled?
> > >
> > > I'd like to see the implementation of this before advising.
> > >
> > > In such a case, I assume there would be a differentiating factor between
> > > the two (or three) devices.  You would then use that to decide which IDA
> > > would need to be incremented.
> > >
> > > However, Greg is correct.  Hard-coding look-ups for userspace to use
> > > sounds like a terrible idea.
> > >
> >
> > I understand.
> > Do you think it would be better to pass the index via platform_data
> > and use PLATFORM_DEVID_AUTO together with mfd_add_hotplug_devices()
> > instead?
> > For example:
> > struct nct6694_platform_data {
> >     int index;
> > };
> >
> > static struct nct6694_platform_data i2c_data[] = {
> >     { .index = 0 }, { .index = 1 }, { .index = 2 }, { .index = 3 }, {
> > .index = 4 }, { .index = 5 },
> > };
> >
> > static const struct mfd_cell nct6694_devs[] = {
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[0], sizeof(struct
> > nct6694_platform_data), 0),
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[1], sizeof(struct
> > nct6694_platform_data), 0),
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[2], sizeof(struct
> > nct6694_platform_data), 0),
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[3], sizeof(struct
> > nct6694_platform_data), 0),
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[4], sizeof(struct
> > nct6694_platform_data), 0),
> >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[5], sizeof(struct
> > nct6694_platform_data), 0),
> > };
> > ...
> > mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
> > ...
>
> No, that's clearly way worse.  =:-)
>
> The clean-up that this provides is probably not worth all of this
> discussion.  I _still_ think this enumeration should be done in the
> driver.  But if you really can't make it work, I'll accept the .id
> patch.
>

Okay, I would like to ask for your advice regarding the implementation of IDA.

Using a global IDA in the sub-driver like this:
(in i2c-nct6694.c)
static DEFINE_IDA(nct6694_i2c_ida);

static int nct6694_i2c_probe(struct platform_device *pdev)
{
    ida_alloc(&nct6694_i2c_ida, GFP_KERNEL);
    ...
}

causes IDs to be globally incremented across all devices. For example,
the first NCT6694 device gets probed 6 times and receives IDs 0–5, but
when a second NCT6694 device is added, it receives IDs starting from
6, rather than starting again from 0. This makes per-device ID mapping
unreliable.

To solve this, I believe the right approach is to have each NCT6694
instance maintain its own IDA, managed by the MFD driver's private
data. As mentioned earlier, for example:
(in nct6694.c)
struct nct6694 {
    struct device *dev;
    struct ida i2c_ida;
};

static int nct6694_probe(struct platform_device *pdev)
{
    ...
    ida_init(&nct6694->i2c_ida);
    ...
}

(in i2c-nct6694.c)
static int nct6694_i2c_probe(struct platform_device *pdev)
{
    id = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
}

This way, each device allocates IDs independently, and each set of
I2C/GPIO instances gets predictable IDs starting from 0 per device. I
think this resolves the original issue without relying on hardcoded
platform IDs.
Please let me know if this implementation aligns with what you had in mind.


Thanks,
Ming

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

* Re: [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694
  2025-06-25 14:24                             ` Ming Yu
@ 2025-06-25 14:53                               ` Lee Jones
  0 siblings, 0 replies; 33+ messages in thread
From: Lee Jones @ 2025-06-25 14:53 UTC (permalink / raw)
  To: Ming Yu
  Cc: 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,
	linux-usb, Ming Yu

[...]

> > > > > > In the code above you register 6 I2C devices.  Each device will be
> > > > > > assigned a platform ID 0 through 5. The .probe() function in the I2C
> > > > > > driver will be executed 6 times.  In each of those calls to .probe(),
> > > > > > instead of pre-allocating a contiguous assignment of IDs here, you
> > > > > > should be able to use IDA in .probe() to allocate those same device IDs
> > > > > > 0 through 5.
> > > > > >
> > > > > > What am I missing here?
> > > > > >
> > > > >
> > > > > You're absolutely right in the scenario where a single NCT6694 device
> > > > > is present. However, I’m wondering how we should handle the case where
> > > > > a second or even third NCT6694 device is bound to the same MFD driver.
> > > > > In that situation, the sub-drivers using a static IDA will continue
> > > > > allocating increasing IDs, rather than restarting from 0 for each
> > > > > device. How should this be handled?
> > > >
> > > > I'd like to see the implementation of this before advising.
> > > >
> > > > In such a case, I assume there would be a differentiating factor between
> > > > the two (or three) devices.  You would then use that to decide which IDA
> > > > would need to be incremented.
> > > >
> > > > However, Greg is correct.  Hard-coding look-ups for userspace to use
> > > > sounds like a terrible idea.
> > > >
> > >
> > > I understand.
> > > Do you think it would be better to pass the index via platform_data
> > > and use PLATFORM_DEVID_AUTO together with mfd_add_hotplug_devices()
> > > instead?
> > > For example:
> > > struct nct6694_platform_data {
> > >     int index;
> > > };
> > >
> > > static struct nct6694_platform_data i2c_data[] = {
> > >     { .index = 0 }, { .index = 1 }, { .index = 2 }, { .index = 3 }, {
> > > .index = 4 }, { .index = 5 },
> > > };
> > >
> > > static const struct mfd_cell nct6694_devs[] = {
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[0], sizeof(struct
> > > nct6694_platform_data), 0),
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[1], sizeof(struct
> > > nct6694_platform_data), 0),
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[2], sizeof(struct
> > > nct6694_platform_data), 0),
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[3], sizeof(struct
> > > nct6694_platform_data), 0),
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[4], sizeof(struct
> > > nct6694_platform_data), 0),
> > >     MFD_CELL_BASIC("nct6694-i2c", NULL, &i2c_data[5], sizeof(struct
> > > nct6694_platform_data), 0),
> > > };
> > > ...
> > > mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
> > > ...
> >
> > No, that's clearly way worse.  =:-)
> >
> > The clean-up that this provides is probably not worth all of this
> > discussion.  I _still_ think this enumeration should be done in the
> > driver.  But if you really can't make it work, I'll accept the .id
> > patch.
> >
> 
> Okay, I would like to ask for your advice regarding the implementation of IDA.
> 
> Using a global IDA in the sub-driver like this:
> (in i2c-nct6694.c)
> static DEFINE_IDA(nct6694_i2c_ida);
> 
> static int nct6694_i2c_probe(struct platform_device *pdev)
> {
>     ida_alloc(&nct6694_i2c_ida, GFP_KERNEL);
>     ...
> }
> 
> causes IDs to be globally incremented across all devices. For example,
> the first NCT6694 device gets probed 6 times and receives IDs 0–5, but
> when a second NCT6694 device is added, it receives IDs starting from
> 6, rather than starting again from 0. This makes per-device ID mapping
> unreliable.
> 
> To solve this, I believe the right approach is to have each NCT6694
> instance maintain its own IDA, managed by the MFD driver's private
> data. As mentioned earlier, for example:
> (in nct6694.c)
> struct nct6694 {
>     struct device *dev;
>     struct ida i2c_ida;
> };
> 
> static int nct6694_probe(struct platform_device *pdev)
> {
>     ...
>     ida_init(&nct6694->i2c_ida);
>     ...
> }
> 
> (in i2c-nct6694.c)
> static int nct6694_i2c_probe(struct platform_device *pdev)
> {
>     id = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
> }
> 
> This way, each device allocates IDs independently, and each set of
> I2C/GPIO instances gets predictable IDs starting from 0 per device. I
> think this resolves the original issue without relying on hardcoded
> platform IDs.
> Please let me know if this implementation aligns with what you had in mind.

This sounds like an acceptable way forward.

-- 
Lee Jones [李琼斯]

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

end of thread, other threads:[~2025-06-25 14:53 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-04  4:14 [PATCH v12 0/7] Add Nuvoton NCT6694 MFD drivers a0282524688
2025-06-04  4:14 ` [PATCH v12 1/7] mfd: Add core driver for Nuvoton NCT6694 a0282524688
2025-06-04 10:11   ` Oliver Neukum
2025-06-04 12:40     ` Guenter Roeck
2025-06-05  7:49       ` Ming Yu
2025-06-05  7:48     ` Ming Yu
2025-06-12 14:00   ` Lee Jones
2025-06-12 14:40     ` Ming Yu
2025-06-12 15:23       ` Lee Jones
2025-06-13  1:54         ` Ming Yu
2025-06-13 13:11           ` Lee Jones
2025-06-13 15:09             ` Ming Yu
2025-06-19 11:53               ` Lee Jones
2025-06-19 12:24                 ` Ming Yu
2025-06-19 15:28                   ` Lee Jones
2025-06-19 16:03                     ` Ming Yu
2025-06-19 16:20                       ` Greg KH
2025-06-19 16:58                         ` Guenter Roeck
2025-06-19 17:18                           ` Greg KH
2025-06-20  2:54                             ` Ming Yu
2025-06-20  5:02                               ` Greg KH
2025-06-25  9:01                       ` Lee Jones
2025-06-25 10:54                         ` Ming Yu
2025-06-25 13:46                           ` Lee Jones
2025-06-25 14:24                             ` Ming Yu
2025-06-25 14:53                               ` Lee Jones
2025-06-04  4:14 ` [PATCH v12 2/7] gpio: Add Nuvoton NCT6694 GPIO support a0282524688
2025-06-04  4:14 ` [PATCH v12 3/7] i2c: Add Nuvoton NCT6694 I2C support a0282524688
2025-06-04  4:14 ` [PATCH v12 4/7] can: Add Nuvoton NCT6694 CANFD support a0282524688
2025-06-04 10:19   ` Vincent Mailhol
2025-06-04  4:14 ` [PATCH v12 5/7] watchdog: Add Nuvoton NCT6694 WDT support a0282524688
2025-06-04  4:14 ` [PATCH v12 6/7] hwmon: Add Nuvoton NCT6694 HWMON support a0282524688
2025-06-04  4:14 ` [PATCH v12 7/7] rtc: Add Nuvoton NCT6694 RTC support a0282524688

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).