* [PATCH v8 01/13] gpio: nct6694: Mark the GPIO controller as sleeping
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 02/13] mfd: nct6694: Validate the USB endpoints a0282524688
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu, Linus Walleij, Bartosz Golaszewski
Cc: linux-kernel, Ming Yu, linux-gpio
From: Ming Yu <a0282524688@gmail.com>
The GPIO callbacks take a mutex and issue USB transfers, so this GPIO
chip must not be used in atomic context. Change the can_sleep switch in
struct gpio_chip to true.
Fixes: 611a995e8ae1 ("gpio: Add Nuvoton NCT6694 GPIO support")
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- New patch. Set can_sleep, as the GPIO callbacks sleep on USB I/O.
drivers/gpio/gpio-nct6694.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index a8607f0d9915..a38add1cde29 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -444,7 +444,7 @@ static int nct6694_gpio_probe(struct platform_device *pdev)
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.can_sleep = true;
data->gpio.owner = THIS_MODULE;
data->gpio.ngpio = NCT6694_NR_GPIO;
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 02/13] mfd: nct6694: Validate the USB endpoints
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
2026-10-07 9:20 ` [PATCH v8 01/13] gpio: nct6694: Mark the GPIO controller as sleeping a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 03/13] mfd: nct6694: Check the length of received USB packets a0282524688
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
The probe reads endpoint[0] of the current altsetting without checking
that the interface describes any endpoint, and the bulk endpoints used
for the command transfers are never validated. A malformed device can
make the driver read past the endpoint array or submit URBs to
endpoints of the wrong type.
The interface exposes several interrupt and bulk endpoints, of which
only EP1 IN, EP2 IN and EP3 OUT carry the command interface, so the
endpoints cannot be looked up by type. Check that the expected
endpoints are present with usb_check_{bulk,int}_endpoints(), and take
the polling interval from the interrupt endpoint actually used.
Define the interrupt endpoint by its number, like the bulk endpoints,
as the pipe macros expect an endpoint number rather than an address.
Fixes: 51dad33ede63 ("mfd: Add core driver for Nuvoton NCT6694")
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Also validate the bulk endpoints. Keep the fixed endpoint numbers and
check them with usb_check_{bulk,int}_endpoints() instead of looking
up the first endpoint of each type, as the interface exposes several
interrupt and bulk endpoints.
- Take the polling interval from the interrupt endpoint actually used,
and define it by its number like the bulk endpoints.
Changes in v7:
- New patch.
drivers/mfd/nct6694.c | 29 +++++++++++++++++------------
include/linux/mfd/nct6694.h | 2 +-
2 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 308b2fda3055..b9526b22754c 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -273,13 +273,25 @@ static const struct irq_domain_ops nct6694_irq_domain_ops = {
static int nct6694_usb_probe(struct usb_interface *iface,
const struct usb_device_id *id)
{
+ static const u8 bulk_ep_addr[] = {
+ USB_DIR_IN | NCT6694_BULK_IN_EP,
+ USB_DIR_OUT | NCT6694_BULK_OUT_EP,
+ 0
+ };
+ static const u8 int_ep_addr[] = {
+ USB_DIR_IN | NCT6694_INT_IN_EP,
+ 0
+ };
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;
+ unsigned int int_pipe;
int ret;
+ if (!usb_check_bulk_endpoints(iface, bulk_ep_addr) ||
+ !usb_check_int_endpoints(iface, int_ep_addr))
+ return -ENODEV;
+
nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
if (!nct6694)
return -ENOMEM;
@@ -318,17 +330,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (ret)
goto err_ida;
- interface = iface->cur_altsetting;
-
- int_endpoint = &interface->endpoint[0].desc;
- if (!usb_endpoint_is_int_in(int_endpoint)) {
- ret = -ENODEV;
- goto err_ida;
- }
-
- usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ int_pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
+ usb_fill_int_urb(nct6694->int_in_urb, udev, int_pipe,
nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
- nct6694, int_endpoint->bInterval);
+ nct6694, usb_pipe_endpoint(udev, int_pipe)->desc.bInterval);
ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
if (ret)
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 6eb9be2cd4a0..a5ad7be47bf9 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -10,7 +10,7 @@
#define NCT6694_VENDOR_ID 0x0416
#define NCT6694_PRODUCT_ID 0x200B
-#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_INT_IN_EP 0x01
#define NCT6694_BULK_IN_EP 0x02
#define NCT6694_BULK_OUT_EP 0x03
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 03/13] mfd: nct6694: Check the length of received USB packets
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
2026-10-07 9:20 ` [PATCH v8 01/13] gpio: nct6694: Mark the GPIO controller as sleeping a0282524688
2026-10-07 9:20 ` [PATCH v8 02/13] mfd: nct6694: Validate the USB endpoints a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 04/13] mfd: nct6694: Ignore interrupts without a mapping a0282524688
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, mfd
From: Ming Yu <a0282524688@gmail.com>
The response status and the interrupt status are parsed without
checking how many bytes were actually received. A short or zero-length
packet leaves stale data in the buffer, which is then taken as a valid
status and may repeatedly dispatch the same interrupts.
Reject short response packets and ignore short interrupt packets.
Fixes: 51dad33ede63 ("mfd: Add core driver for Nuvoton NCT6694")
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- New patch. Check the length of the response and interrupt packets
before parsing them.
drivers/mfd/nct6694.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index b9526b22754c..de0027c98726 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -117,6 +117,12 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
if (ret)
return ret;
+ if (rx_len != sizeof(msg->response_header)) {
+ dev_err(nct6694->dev, "Expected response length %zu, but got %d\n",
+ sizeof(msg->response_header), rx_len);
+ return -EIO;
+ }
+
/* 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);
@@ -173,6 +179,12 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
if (ret)
return ret;
+ if (rx_len != sizeof(msg->response_header)) {
+ dev_err(nct6694->dev, "Expected response length %zu, but got %d\n",
+ sizeof(msg->response_header), rx_len);
+ return -EIO;
+ }
+
/* 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);
@@ -207,6 +219,9 @@ static void usb_int_callback(struct urb *urb)
goto resubmit;
}
+ if (urb->actual_length < sizeof(*status_le))
+ goto resubmit;
+
int_status = le32_to_cpu(*status_le);
while (int_status) {
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 04/13] mfd: nct6694: Ignore interrupts without a mapping
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (2 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 03/13] mfd: nct6694: Check the length of received USB packets a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 05/13] mfd: nct6694: Transfer data packets via a dedicated buffer a0282524688
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, mfd
From: Ming Yu <a0282524688@gmail.com>
The return value of irq_find_mapping() is passed straight to
generic_handle_irq_safe(). If the device reports a hardware interrupt
that has no mapping, irq_find_mapping() returns 0 and the handler of
the system's IRQ 0 is invoked instead.
Use generic_handle_domain_irq_safe(), which ignores unmapped hardware
interrupts.
Fixes: 51dad33ede63 ("mfd: Add core driver for Nuvoton NCT6694")
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- New patch. Use generic_handle_domain_irq_safe() so that unmapped
interrupts are ignored.
drivers/mfd/nct6694.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index de0027c98726..4f5a5b855de2 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -227,7 +227,7 @@ static void usb_int_callback(struct urb *urb)
while (int_status) {
int irq = __ffs(int_status);
- generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+ generic_handle_domain_irq_safe(nct6694->domain, irq);
int_status &= ~BIT(irq);
}
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 05/13] mfd: nct6694: Transfer data packets via a dedicated buffer
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (3 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 04/13] mfd: nct6694: Ignore interrupts without a mapping a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 06/13] mfd: nct6694: Move module type macros to shared header a0282524688
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
The caller's buffer is passed straight to usb_bulk_msg(). Sub-device
drivers embed those buffers in their private data, so they are neither
cacheline aligned nor exclusively owned by the transfer, and mapping
them for DMA can corrupt adjacent fields on non-coherent architectures.
Transfer the data packets through a buffer owned by the transport, and
reject commands exceeding the maximum data length.
Fixes: 51dad33ede63 ("mfd: Add core driver for Nuvoton NCT6694")
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Moved before the refactoring so it applies to the original driver.
- Renamed xfer_buf to data_buf, len to data_len and
NCT6694_MAX_PACKET_SIZE to NCT6694_MAX_DATA_LEN.
Changes in v7:
- New patch.
drivers/mfd/nct6694.c | 37 +++++++++++++++++++++++++++----------
include/linux/mfd/nct6694.h | 4 ++++
2 files changed, 31 insertions(+), 10 deletions(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 4f5a5b855de2..f9ae8476fcae 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -98,8 +98,12 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
{
union nct6694_usb_msg *msg = nct6694->usb_msg;
struct usb_device *udev = nct6694->udev;
+ u16 data_len = le16_to_cpu(cmd_hd->len);
int tx_len, rx_len, ret;
+ if (data_len > NCT6694_MAX_DATA_LEN)
+ return -EINVAL;
+
guard(mutex)(&nct6694->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
@@ -124,17 +128,19 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
}
/* 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);
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), nct6694->data_buf,
+ data_len, &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
- if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ if (rx_len != data_len) {
dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
- le16_to_cpu(cmd_hd->len), rx_len);
+ data_len, rx_len);
return -EIO;
}
+ memcpy(buf, nct6694->data_buf, data_len);
+
return nct6694_response_err_handling(nct6694, msg->response_header.sts);
}
EXPORT_SYMBOL_GPL(nct6694_read_msg);
@@ -154,12 +160,17 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
{
union nct6694_usb_msg *msg = nct6694->usb_msg;
struct usb_device *udev = nct6694->udev;
+ u16 data_len = le16_to_cpu(cmd_hd->len);
int tx_len, rx_len, ret;
+ if (data_len > NCT6694_MAX_DATA_LEN)
+ return -EINVAL;
+
guard(mutex)(&nct6694->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+ memcpy(nct6694->data_buf, buf, data_len);
/* Send command packet to USB device */
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
@@ -168,8 +179,8 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
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);
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), nct6694->data_buf,
+ data_len, &tx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
@@ -186,17 +197,19 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
}
/* 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);
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), nct6694->data_buf,
+ data_len, &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
- if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ if (rx_len != data_len) {
dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
- le16_to_cpu(cmd_hd->len), rx_len);
+ data_len, rx_len);
return -EIO;
}
+ memcpy(buf, nct6694->data_buf, data_len);
+
return nct6694_response_err_handling(nct6694, msg->response_header.sts);
}
EXPORT_SYMBOL_GPL(nct6694_write_msg);
@@ -315,6 +328,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (!nct6694->usb_msg)
return -ENOMEM;
+ nct6694->data_buf = devm_kzalloc(dev, NCT6694_MAX_DATA_LEN, GFP_KERNEL);
+ if (!nct6694->data_buf)
+ return -ENOMEM;
+
nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
if (!nct6694->int_buffer)
return -ENOMEM;
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index a5ad7be47bf9..69e4652cf16c 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -19,6 +19,9 @@
#define NCT6694_URB_TIMEOUT 1000
+/* Maximum data packet length the firmware accepts in a single command */
+#define NCT6694_MAX_DATA_LEN 0x3F0
+
enum nct6694_irq_id {
NCT6694_IRQ_GPIO0 = 0,
NCT6694_IRQ_GPIO1,
@@ -92,6 +95,7 @@ struct nct6694 {
struct urb *int_in_urb;
struct usb_device *udev;
union nct6694_usb_msg *usb_msg;
+ void *data_buf;
__le32 *int_buffer;
unsigned int irq_enable;
};
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 06/13] mfd: nct6694: Move module type macros to shared header
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (4 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 05/13] mfd: nct6694: Transfer data packets via a dedicated buffer a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 07/13] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data a0282524688
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Marc Kleine-Budde, Vincent Mailhol,
Alexandre Belloni, Wim Van Sebroeck
Cc: linux-kernel, Ming Yu, linux-gpio, linux-hwmon, linux-i2c,
linux-can, linux-rtc, linux-watchdog, mfd
From: Ming Yu <a0282524688@gmail.com>
Each sub-device driver defines the module type used by its own
commands. The upcoming HIF transport also needs to recognise
NCT6694_RPT_MOD, as report accesses are served from a separate
register space rather than through the command mailbox.
Move all module types into the shared header so that the transports
and the sub-device drivers use a single set of definitions.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Reworded the commit message to explain why the transport needs the
module types.
Changes in v7:
- None.
Changes in v6:
- None.
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
drivers/gpio/gpio-nct6694.c | 7 -------
drivers/hwmon/nct6694-hwmon.c | 21 ---------------------
drivers/i2c/busses/i2c-nct6694.c | 7 -------
drivers/net/can/usb/nct6694_canfd.c | 6 ------
drivers/rtc/rtc-nct6694.c | 7 -------
drivers/watchdog/nct6694_wdt.c | 7 -------
include/linux/mfd/nct6694.h | 9 +++++++++
7 files changed, 9 insertions(+), 55 deletions(-)
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index a38add1cde29..c8cdf4520466 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -13,13 +13,6 @@
#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
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
index 9a9a4db434c4..bd4c106305c7 100644
--- a/drivers/hwmon/nct6694-hwmon.c
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -15,13 +15,6 @@
#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
@@ -38,13 +31,6 @@
#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
@@ -53,13 +39,6 @@
#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
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index 1413ab6f9462..ef3329f34246 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -12,13 +12,6 @@
#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
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index e5f7f8849a73..262b4c26c9d4 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -18,12 +18,6 @@
#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)
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
index 35401a0d9cf5..c06902f150c9 100644
--- a/drivers/rtc/rtc-nct6694.c
+++ b/drivers/rtc/rtc-nct6694.c
@@ -14,13 +14,6 @@
#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
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
index bc3689bd4b6b..4c06ac105562 100644
--- a/drivers/watchdog/nct6694_wdt.c
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -20,13 +20,6 @@
#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)
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 69e4652cf16c..d46ae34a497e 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -8,6 +8,15 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#define NCT6694_HWMON_MOD 0x00
+#define NCT6694_PWM_MOD 0x01
+#define NCT6694_I2C_MOD 0x03
+#define NCT6694_CANFD_MOD 0x05
+#define NCT6694_WDT_MOD 0x07
+#define NCT6694_RTC_MOD 0x08
+#define NCT6694_RPT_MOD 0xFF
+#define NCT6694_GPIO_MOD NCT6694_RPT_MOD
+
#define NCT6694_VENDOR_ID 0x0416
#define NCT6694_PRODUCT_ID 0x200B
#define NCT6694_INT_IN_EP 0x01
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 07/13] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (5 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 06/13] mfd: nct6694: Move module type macros to shared header a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 08/13] mfd: nct6694: Rename USB transport functions with _usb_ prefix a0282524688
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Separate the USB transport fields from the core nct6694 structure into
a new nct6694_usb_data structure, which embeds struct nct6694 and is
retrieved with container_of(). USB-only definitions are moved from the
shared header into the USB driver.
While at it, use consistent names for the USB device, the buffers and
the core structure.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Embedded struct nct6694 in nct6694_usb_data and dropped the priv
pointer.
- Renamed udev to usb_dev, udata to usb_data, usb_msg to hdr_buf,
int_buffer to int_buf, int_in_urb to int_urb, and nct6694 to ddata.
Changes in v7:
- None.
Changes in v6:
- None.
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
drivers/mfd/nct6694.c | 243 +++++++++++++++++++++---------------
include/linux/mfd/nct6694.h | 26 +---
2 files changed, 143 insertions(+), 126 deletions(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index f9ae8476fcae..6d75b1485350 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -22,6 +22,34 @@
#include <linux/spinlock.h>
#include <linux/usb.h>
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x01
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_URB_TIMEOUT 1000
+
+union __packed nct6694_usb_hdr {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694_usb_data {
+ struct nct6694 core;
+ struct mutex access_lock;
+ struct usb_device *usb_dev;
+ struct urb *int_urb;
+ union nct6694_usb_hdr *hdr_buf;
+ void *data_buf;
+ __le32 *int_buf;
+};
+
+static struct nct6694_usb_data *to_nct6694_usb_data(struct nct6694 *ddata)
+{
+ return container_of(ddata, struct nct6694_usb_data, core);
+}
+
static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-gpio"),
MFD_CELL_NAME("nct6694-gpio"),
@@ -58,22 +86,22 @@ static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-rtc"),
};
-static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+static int nct6694_response_err_handling(struct nct6694 *ddata, 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");
+ dev_err(ddata->dev, "Command is not supported!\n");
break;
case NCT6694_NO_RESPONSE_ERROR:
- dev_warn(nct6694->dev, "Command received no response!\n");
+ dev_warn(ddata->dev, "Command received no response!\n");
break;
case NCT6694_TIMEOUT_ERROR:
- dev_warn(nct6694->dev, "Command timed out!\n");
+ dev_warn(ddata->dev, "Command timed out!\n");
break;
case NCT6694_PENDING:
- dev_err(nct6694->dev, "Command is pending!\n");
+ dev_err(ddata->dev, "Command is pending!\n");
break;
default:
return -EINVAL;
@@ -84,7 +112,7 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
/**
* nct6694_read_msg() - Read message from NCT6694 device
- * @nct6694: NCT6694 device pointer
+ * @ddata: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer to store the response data
*
@@ -94,60 +122,61 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
*
* 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)
+int nct6694_read_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
+ union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
+ struct usb_device *usb_dev = usb_data->usb_dev;
u16 data_len = le16_to_cpu(cmd_hd->len);
int tx_len, rx_len, ret;
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&usb_data->access_lock);
- memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
- msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+ memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ hdr->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);
+ ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, NCT6694_BULK_OUT_EP),
+ &hdr->cmd_header, sizeof(*hdr), &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);
+ ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, NCT6694_BULK_IN_EP),
+ &hdr->response_header, sizeof(*hdr), &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
- if (rx_len != sizeof(msg->response_header)) {
- dev_err(nct6694->dev, "Expected response length %zu, but got %d\n",
- sizeof(msg->response_header), rx_len);
+ if (rx_len != sizeof(hdr->response_header)) {
+ dev_err(ddata->dev, "Expected response length %zu, but got %d\n",
+ sizeof(hdr->response_header), rx_len);
return -EIO;
}
/* Receive data packet from USB device */
- ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), nct6694->data_buf,
- data_len, &rx_len, NCT6694_URB_TIMEOUT);
+ ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, NCT6694_BULK_IN_EP),
+ usb_data->data_buf, data_len, &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
if (rx_len != data_len) {
- dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
+ dev_err(ddata->dev, "Expected received length %d, but got %d\n",
data_len, rx_len);
return -EIO;
}
- memcpy(buf, nct6694->data_buf, data_len);
+ memcpy(buf, usb_data->data_buf, data_len);
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_response_err_handling(ddata, hdr->response_header.sts);
}
EXPORT_SYMBOL_GPL(nct6694_read_msg);
/**
* nct6694_write_msg() - Write message to NCT6694 device
- * @nct6694: NCT6694 device pointer
+ * @ddata: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer containing the data to be sent
*
@@ -156,67 +185,68 @@ EXPORT_SYMBOL_GPL(nct6694_read_msg);
*
* 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)
+int nct6694_write_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
+ union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
+ struct usb_device *usb_dev = usb_data->usb_dev;
u16 data_len = le16_to_cpu(cmd_hd->len);
int tx_len, rx_len, ret;
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&usb_data->access_lock);
- memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
- msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
- memcpy(nct6694->data_buf, buf, data_len);
+ memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ hdr->cmd_header.hctrl = NCT6694_HCTRL_SET;
+ memcpy(usb_data->data_buf, buf, data_len);
/* 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);
+ ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, NCT6694_BULK_OUT_EP),
+ &hdr->cmd_header, sizeof(*hdr), &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), nct6694->data_buf,
- data_len, &tx_len, NCT6694_URB_TIMEOUT);
+ ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, NCT6694_BULK_OUT_EP),
+ usb_data->data_buf, data_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);
+ ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, NCT6694_BULK_IN_EP),
+ &hdr->response_header, sizeof(*hdr), &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
- if (rx_len != sizeof(msg->response_header)) {
- dev_err(nct6694->dev, "Expected response length %zu, but got %d\n",
- sizeof(msg->response_header), rx_len);
+ if (rx_len != sizeof(hdr->response_header)) {
+ dev_err(ddata->dev, "Expected response length %zu, but got %d\n",
+ sizeof(hdr->response_header), rx_len);
return -EIO;
}
/* Receive data packet from USB device */
- ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), nct6694->data_buf,
- data_len, &rx_len, NCT6694_URB_TIMEOUT);
+ ret = usb_bulk_msg(usb_dev, usb_rcvbulkpipe(usb_dev, NCT6694_BULK_IN_EP),
+ usb_data->data_buf, data_len, &rx_len, NCT6694_URB_TIMEOUT);
if (ret)
return ret;
if (rx_len != data_len) {
- dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
+ dev_err(ddata->dev, "Expected transmitted length %d, but got %d\n",
data_len, rx_len);
return -EIO;
}
- memcpy(buf, nct6694->data_buf, data_len);
+ memcpy(buf, usb_data->data_buf, data_len);
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_response_err_handling(ddata, hdr->response_header.sts);
}
EXPORT_SYMBOL_GPL(nct6694_write_msg);
static void usb_int_callback(struct urb *urb)
{
- struct nct6694 *nct6694 = urb->context;
+ struct nct6694 *ddata = urb->context;
__le32 *status_le = urb->transfer_buffer;
u32 int_status;
int ret;
@@ -240,34 +270,34 @@ static void usb_int_callback(struct urb *urb)
while (int_status) {
int irq = __ffs(int_status);
- generic_handle_domain_irq_safe(nct6694->domain, irq);
+ generic_handle_domain_irq_safe(ddata->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));
+ dev_warn(ddata->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret));
}
static void nct6694_irq_enable(struct irq_data *data)
{
- struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
- guard(spinlock_irqsave)(&nct6694->irq_lock);
+ guard(spinlock_irqsave)(&ddata->irq_lock);
- nct6694->irq_enable |= BIT(hwirq);
+ ddata->irq_enable |= BIT(hwirq);
}
static void nct6694_irq_disable(struct irq_data *data)
{
- struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
- guard(spinlock_irqsave)(&nct6694->irq_lock);
+ guard(spinlock_irqsave)(&ddata->irq_lock);
- nct6694->irq_enable &= ~BIT(hwirq);
+ ddata->irq_enable &= ~BIT(hwirq);
}
static const struct irq_chip nct6694_irq_chip = {
@@ -279,9 +309,9 @@ static const struct irq_chip nct6694_irq_chip = {
static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
- struct nct6694 *nct6694 = d->host_data;
+ struct nct6694 *ddata = d->host_data;
- irq_set_chip_data(irq, nct6694);
+ irq_set_chip_data(irq, ddata);
irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
return 0;
@@ -310,9 +340,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
USB_DIR_IN | NCT6694_INT_IN_EP,
0
};
- struct usb_device *udev = interface_to_usbdev(iface);
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+ struct nct6694_usb_data *usb_data;
struct device *dev = &iface->dev;
- struct nct6694 *nct6694;
+ struct nct6694 *ddata;
unsigned int int_pipe;
int ret;
@@ -320,58 +351,61 @@ static int nct6694_usb_probe(struct usb_interface *iface,
!usb_check_int_endpoints(iface, int_ep_addr))
return -ENODEV;
- nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
- if (!nct6694)
+ usb_data = devm_kzalloc(dev, sizeof(*usb_data), GFP_KERNEL);
+ if (!usb_data)
return -ENOMEM;
- nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
- if (!nct6694->usb_msg)
+ usb_data->hdr_buf = devm_kzalloc(dev, sizeof(*usb_data->hdr_buf), GFP_KERNEL);
+ if (!usb_data->hdr_buf)
return -ENOMEM;
- nct6694->data_buf = devm_kzalloc(dev, NCT6694_MAX_DATA_LEN, GFP_KERNEL);
- if (!nct6694->data_buf)
+ usb_data->data_buf = devm_kzalloc(dev, NCT6694_MAX_DATA_LEN, GFP_KERNEL);
+ if (!usb_data->data_buf)
return -ENOMEM;
- nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
- if (!nct6694->int_buffer)
+ usb_data->int_buf = devm_kzalloc(dev, sizeof(*usb_data->int_buf), GFP_KERNEL);
+ if (!usb_data->int_buf)
return -ENOMEM;
- nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!nct6694->int_in_urb)
+ usb_data->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usb_data->int_urb)
return -ENOMEM;
- nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
- &nct6694_irq_domain_ops,
- nct6694);
- if (!nct6694->domain) {
+ ddata = &usb_data->core;
+
+ ddata->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ ddata);
+ if (!ddata->domain) {
ret = -ENODEV;
goto err_urb;
}
- nct6694->dev = dev;
- nct6694->udev = udev;
+ ddata->dev = dev;
+ usb_data->usb_dev = usb_dev;
- ida_init(&nct6694->gpio_ida);
- ida_init(&nct6694->i2c_ida);
- ida_init(&nct6694->canfd_ida);
- ida_init(&nct6694->wdt_ida);
+ ida_init(&ddata->gpio_ida);
+ ida_init(&ddata->i2c_ida);
+ ida_init(&ddata->canfd_ida);
+ ida_init(&ddata->wdt_ida);
- spin_lock_init(&nct6694->irq_lock);
+ spin_lock_init(&ddata->irq_lock);
- ret = devm_mutex_init(dev, &nct6694->access_lock);
+ ret = devm_mutex_init(dev, &usb_data->access_lock);
if (ret)
goto err_ida;
- int_pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
- usb_fill_int_urb(nct6694->int_in_urb, udev, int_pipe,
- nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
- nct6694, usb_pipe_endpoint(udev, int_pipe)->desc.bInterval);
+ int_pipe = usb_rcvintpipe(usb_dev, NCT6694_INT_IN_EP);
+ usb_fill_int_urb(usb_data->int_urb, usb_dev, int_pipe,
+ usb_data->int_buf, sizeof(*usb_data->int_buf),
+ usb_int_callback, ddata,
+ usb_pipe_endpoint(usb_dev, int_pipe)->desc.bInterval);
- ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ ret = usb_submit_urb(usb_data->int_urb, GFP_KERNEL);
if (ret)
goto err_ida;
- usb_set_intfdata(iface, nct6694);
+ usb_set_intfdata(iface, ddata);
ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
if (ret)
@@ -380,30 +414,31 @@ static int nct6694_usb_probe(struct usb_interface *iface,
return 0;
err_mfd:
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(usb_data->int_urb);
err_ida:
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
- irq_domain_remove(nct6694->domain);
+ ida_destroy(&ddata->wdt_ida);
+ ida_destroy(&ddata->canfd_ida);
+ ida_destroy(&ddata->i2c_ida);
+ ida_destroy(&ddata->gpio_ida);
+ irq_domain_remove(ddata->domain);
err_urb:
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(usb_data->int_urb);
return ret;
}
static void nct6694_usb_disconnect(struct usb_interface *iface)
{
- struct nct6694 *nct6694 = usb_get_intfdata(iface);
-
- mfd_remove_devices(nct6694->dev);
- usb_kill_urb(nct6694->int_in_urb);
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
- irq_domain_remove(nct6694->domain);
- usb_free_urb(nct6694->int_in_urb);
+ struct nct6694 *ddata = usb_get_intfdata(iface);
+ struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
+
+ mfd_remove_devices(ddata->dev);
+ usb_kill_urb(usb_data->int_urb);
+ ida_destroy(&ddata->wdt_ida);
+ ida_destroy(&ddata->canfd_ida);
+ ida_destroy(&ddata->i2c_ida);
+ ida_destroy(&ddata->gpio_ida);
+ irq_domain_remove(ddata->domain);
+ usb_free_urb(usb_data->int_urb);
}
static const struct usb_device_id nct6694_ids[] = {
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index d46ae34a497e..0ab2bd3bfdc1 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -2,7 +2,8 @@
/*
* Copyright (C) 2025 Nuvoton Technology Corp.
*
- * Nuvoton NCT6694 USB transaction and data structure.
+ * Nuvoton NCT6694 core definitions shared by the transport driver
+ * and the sub-device drivers.
*/
#ifndef __MFD_NCT6694_H
@@ -17,17 +18,9 @@
#define NCT6694_RPT_MOD 0xFF
#define NCT6694_GPIO_MOD NCT6694_RPT_MOD
-#define NCT6694_VENDOR_ID 0x0416
-#define NCT6694_PRODUCT_ID 0x200B
-#define NCT6694_INT_IN_EP 0x01
-#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
-
/* Maximum data packet length the firmware accepts in a single command */
#define NCT6694_MAX_DATA_LEN 0x3F0
@@ -87,11 +80,6 @@ struct __packed nct6694_response_header {
__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 ida gpio_ida;
@@ -99,17 +87,11 @@ struct nct6694 {
struct ida canfd_ida;
struct ida wdt_ida;
struct irq_domain *domain;
- struct mutex access_lock;
spinlock_t irq_lock;
- struct urb *int_in_urb;
- struct usb_device *udev;
- union nct6694_usb_msg *usb_msg;
- void *data_buf;
- __le32 *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);
+int nct6694_read_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf);
+int nct6694_write_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf);
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 08/13] mfd: nct6694: Rename USB transport functions with _usb_ prefix
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (6 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 07/13] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 09/13] mfd: nct6694: Rename driver to nct6694-usb a0282524688
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Add a _usb_ prefix to the USB transport functions, tables and driver
name to mark them as USB specific, in preparation for the core and HIF
transport added in later patches.
Keep nct6694_{read,write}_msg() as static inline wrappers in the shared
header so that the sub-device drivers remain untouched; they are removed
once the regmap-based transport abstraction is in place.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- None.
Changes in v7:
- None.
Changes in v6:
- New patch replacing the v5 function-pointer transport abstraction:
rename the exported I/O functions with an _usb_ prefix and add
transitional inline nct6694_{read,write}_msg() wrappers in the shared
header so sub-device drivers stay untouched in this commit.
drivers/mfd/nct6694.c | 40 ++++++++++++++++++++-----------------
include/linux/mfd/nct6694.h | 22 ++++++++++++++++++--
2 files changed, 42 insertions(+), 20 deletions(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 6d75b1485350..83e9c7ac9bee 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -50,7 +50,7 @@ static struct nct6694_usb_data *to_nct6694_usb_data(struct nct6694 *ddata)
return container_of(ddata, struct nct6694_usb_data, core);
}
-static const struct mfd_cell nct6694_devs[] = {
+static const struct mfd_cell nct6694_usb_devs[] = {
MFD_CELL_NAME("nct6694-gpio"),
MFD_CELL_NAME("nct6694-gpio"),
MFD_CELL_NAME("nct6694-gpio"),
@@ -86,7 +86,7 @@ static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-rtc"),
};
-static int nct6694_response_err_handling(struct nct6694 *ddata, unsigned char err_status)
+static int nct6694_usb_err_handling(struct nct6694 *ddata, unsigned char err_status)
{
switch (err_status) {
case NCT6694_NO_ERROR:
@@ -111,7 +111,7 @@ static int nct6694_response_err_handling(struct nct6694 *ddata, unsigned char er
}
/**
- * nct6694_read_msg() - Read message from NCT6694 device
+ * nct6694_usb_read_msg() - Read message from NCT6694 device
* @ddata: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer to store the response data
@@ -122,7 +122,9 @@ static int nct6694_response_err_handling(struct nct6694 *ddata, unsigned char er
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_read_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf)
+int nct6694_usb_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -170,12 +172,12 @@ int nct6694_read_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd
memcpy(buf, usb_data->data_buf, data_len);
- return nct6694_response_err_handling(ddata, hdr->response_header.sts);
+ return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_read_msg);
+EXPORT_SYMBOL_GPL(nct6694_usb_read_msg);
/**
- * nct6694_write_msg() - Write message to NCT6694 device
+ * nct6694_usb_write_msg() - Write message to NCT6694 device
* @ddata: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer containing the data to be sent
@@ -185,7 +187,9 @@ EXPORT_SYMBOL_GPL(nct6694_read_msg);
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_write_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf)
+int nct6694_usb_write_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -240,11 +244,11 @@ int nct6694_write_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cm
memcpy(buf, usb_data->data_buf, data_len);
- return nct6694_response_err_handling(ddata, hdr->response_header.sts);
+ return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_write_msg);
+EXPORT_SYMBOL_GPL(nct6694_usb_write_msg);
-static void usb_int_callback(struct urb *urb)
+static void nct6694_usb_int_callback(struct urb *urb)
{
struct nct6694 *ddata = urb->context;
__le32 *status_le = urb->transfer_buffer;
@@ -398,7 +402,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
int_pipe = usb_rcvintpipe(usb_dev, NCT6694_INT_IN_EP);
usb_fill_int_urb(usb_data->int_urb, usb_dev, int_pipe,
usb_data->int_buf, sizeof(*usb_data->int_buf),
- usb_int_callback, ddata,
+ nct6694_usb_int_callback, ddata,
usb_pipe_endpoint(usb_dev, int_pipe)->desc.bInterval);
ret = usb_submit_urb(usb_data->int_urb, GFP_KERNEL);
@@ -407,7 +411,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
usb_set_intfdata(iface, ddata);
- ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
+ ret = mfd_add_hotplug_devices(dev, nct6694_usb_devs, ARRAY_SIZE(nct6694_usb_devs));
if (ret)
goto err_mfd;
@@ -441,20 +445,20 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
usb_free_urb(usb_data->int_urb);
}
-static const struct usb_device_id nct6694_ids[] = {
+static const struct usb_device_id nct6694_usb_ids[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) },
{ }
};
-MODULE_DEVICE_TABLE(usb, nct6694_ids);
+MODULE_DEVICE_TABLE(usb, nct6694_usb_ids);
static struct usb_driver nct6694_usb_driver = {
- .name = "nct6694",
- .id_table = nct6694_ids,
+ .name = "nct6694-usb",
+ .id_table = nct6694_usb_ids,
.probe = nct6694_usb_probe,
.disconnect = nct6694_usb_disconnect,
};
module_usb_driver(nct6694_usb_driver);
-MODULE_DESCRIPTION("Nuvoton NCT6694 core driver");
+MODULE_DESCRIPTION("Nuvoton NCT6694 USB transport 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
index 0ab2bd3bfdc1..61155f9832b0 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -91,7 +91,25 @@ struct nct6694 {
unsigned int irq_enable;
};
-int nct6694_read_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf);
-int nct6694_write_msg(struct nct6694 *ddata, const struct nct6694_cmd_header *cmd_hd, void *buf);
+int nct6694_usb_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+int nct6694_usb_write_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+
+static inline int nct6694_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694_usb_read_msg(ddata, cmd_hd, buf);
+}
+
+static inline int nct6694_write_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694_usb_write_msg(ddata, cmd_hd, buf);
+}
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 09/13] mfd: nct6694: Rename driver to nct6694-usb
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (7 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 08/13] mfd: nct6694: Rename USB transport functions with _usb_ prefix a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:20 ` [PATCH v8 10/13] mfd: nct6694: Extract core device management into a separate module a0282524688
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Rename nct6694.c to nct6694-usb.c to reflect that it implements the
USB transport, in preparation for the core and HIF transport added in
later patches. The module is renamed to nct6694-usb accordingly.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Only rename the file; the Kconfig split moves to the patch that
introduces nct6694-core.c.
Changes in v7:
- None.
Changes in v6:
- None.
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
MAINTAINERS | 2 +-
drivers/mfd/Makefile | 2 +-
drivers/mfd/{nct6694.c => nct6694-usb.c} | 0
3 files changed, 2 insertions(+), 2 deletions(-)
rename drivers/mfd/{nct6694.c => nct6694-usb.c} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 65e8a4b5c90b..1f00fd84365a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19539,7 +19539,7 @@ 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/mfd/nct6694-usb.c
F: drivers/net/can/usb/nct6694_canfd.c
F: drivers/rtc/rtc-nct6694.c
F: drivers/watchdog/nct6694_wdt.c
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 72d3944b0ad8..5ff440fb75bf 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -124,7 +124,7 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
-obj-$(CONFIG_MFD_NCT6694) += nct6694.o
+obj-$(CONFIG_MFD_NCT6694) += nct6694-usb.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694-usb.c
similarity index 100%
rename from drivers/mfd/nct6694.c
rename to drivers/mfd/nct6694-usb.c
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 10/13] mfd: nct6694: Extract core device management into a separate module
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (8 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 09/13] mfd: nct6694: Rename driver to nct6694-usb a0282524688
@ 2026-10-07 9:20 ` a0282524688
2026-10-07 9:21 ` [PATCH v8 11/13] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:20 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Move the IRQ domain and IDA handling, which do not depend on the
transport, from the USB driver into a new nct6694-core module. The
core retrieves struct nct6694 from the driver data of the transport
device, and the transport keeps registering its own MFD cells.
The USB driver now creates the IRQ domain before submitting the
interrupt URB, and kills the URB before the domain is removed.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Pass only the transport device to the core and retrieve struct nct6694
from its driver data.
- Let the transport register its own MFD cells.
- Rename nct6694_core_{probe,remove}() to nct6694_device_{init,exit}().
- Return -ENOMEM when the IRQ domain cannot be created.
- Folded in the v7 patch "Submit the interrupt URB after the core is
registered", whose issue no longer arises.
- Moved the Kconfig split to a later patch.
Changes in v7:
- None.
Changes in v6:
- Reordered to sit before the transport-abstraction patch; no functional
change.
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
MAINTAINERS | 2 +-
drivers/mfd/Makefile | 1 +
drivers/mfd/nct6694-core.c | 111 ++++++++++++++++++++++++++++++++++++
drivers/mfd/nct6694-usb.c | 98 +++++--------------------------
include/linux/mfd/nct6694.h | 3 +
5 files changed, 130 insertions(+), 85 deletions(-)
create mode 100644 drivers/mfd/nct6694-core.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1f00fd84365a..3dadc6e8105a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19539,7 +19539,7 @@ 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-usb.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
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5ff440fb75bf..dfd81356b485 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -124,6 +124,7 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
+obj-$(CONFIG_MFD_NCT6694) += nct6694-core.o
obj-$(CONFIG_MFD_NCT6694) += nct6694-usb.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
diff --git a/drivers/mfd/nct6694-core.c b/drivers/mfd/nct6694-core.c
new file mode 100644
index 000000000000..4b100882ae07
--- /dev/null
+++ b/drivers/mfd/nct6694-core.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 core driver providing the IRQ domain and IDA pools
+ * shared by all transport drivers.
+ */
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+ struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&ddata->irq_lock);
+
+ ddata->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&ddata->irq_lock);
+
+ ddata->irq_enable &= ~BIT(hwirq);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-irq",
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+ .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 *ddata = d->host_data;
+
+ irq_set_chip_data(irq, ddata);
+ 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,
+};
+
+/**
+ * nct6694_device_init() - Set up the resources shared by all transports
+ * @dev: transport device whose driver data points to the struct nct6694
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+int nct6694_device_init(struct device *dev)
+{
+ struct nct6694 *ddata = dev_get_drvdata(dev);
+
+ spin_lock_init(&ddata->irq_lock);
+
+ ida_init(&ddata->gpio_ida);
+ ida_init(&ddata->i2c_ida);
+ ida_init(&ddata->canfd_ida);
+ ida_init(&ddata->wdt_ida);
+
+ ddata->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ ddata);
+ if (!ddata->domain)
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nct6694_device_init);
+
+/**
+ * nct6694_device_exit() - Release the resources set up by nct6694_device_init()
+ * @dev: transport device passed to nct6694_device_init()
+ */
+void nct6694_device_exit(struct device *dev)
+{
+ struct nct6694 *ddata = dev_get_drvdata(dev);
+
+ ida_destroy(&ddata->wdt_ida);
+ ida_destroy(&ddata->canfd_ida);
+ ida_destroy(&ddata->i2c_ida);
+ ida_destroy(&ddata->gpio_ida);
+ irq_domain_remove(ddata->domain);
+}
+EXPORT_SYMBOL_GPL(nct6694_device_exit);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 core driver");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/nct6694-usb.c b/drivers/mfd/nct6694-usb.c
index 83e9c7ac9bee..455eeed62dce 100644
--- a/drivers/mfd/nct6694-usb.c
+++ b/drivers/mfd/nct6694-usb.c
@@ -11,7 +11,6 @@
#include <linux/bits.h>
#include <linux/interrupt.h>
-#include <linux/idr.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
@@ -19,7 +18,6 @@
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
#include <linux/slab.h>
-#include <linux/spinlock.h>
#include <linux/usb.h>
#define NCT6694_VENDOR_ID 0x0416
@@ -284,54 +282,6 @@ static void nct6694_usb_int_callback(struct urb *urb)
dev_warn(ddata->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret));
}
-static void nct6694_irq_enable(struct irq_data *data)
-{
- struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
- irq_hw_number_t hwirq = irqd_to_hwirq(data);
-
- guard(spinlock_irqsave)(&ddata->irq_lock);
-
- ddata->irq_enable |= BIT(hwirq);
-}
-
-static void nct6694_irq_disable(struct irq_data *data)
-{
- struct nct6694 *ddata = irq_data_get_irq_chip_data(data);
- irq_hw_number_t hwirq = irqd_to_hwirq(data);
-
- guard(spinlock_irqsave)(&ddata->irq_lock);
-
- ddata->irq_enable &= ~BIT(hwirq);
-}
-
-static const struct irq_chip nct6694_irq_chip = {
- .name = "nct6694-irq",
- .flags = IRQCHIP_SKIP_SET_WAKE,
- .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 *ddata = d->host_data;
-
- irq_set_chip_data(irq, ddata);
- 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)
{
@@ -376,28 +326,12 @@ static int nct6694_usb_probe(struct usb_interface *iface,
return -ENOMEM;
ddata = &usb_data->core;
-
- ddata->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
- &nct6694_irq_domain_ops,
- ddata);
- if (!ddata->domain) {
- ret = -ENODEV;
- goto err_urb;
- }
-
ddata->dev = dev;
usb_data->usb_dev = usb_dev;
- ida_init(&ddata->gpio_ida);
- ida_init(&ddata->i2c_ida);
- ida_init(&ddata->canfd_ida);
- ida_init(&ddata->wdt_ida);
-
- spin_lock_init(&ddata->irq_lock);
-
ret = devm_mutex_init(dev, &usb_data->access_lock);
if (ret)
- goto err_ida;
+ goto err_free_urb;
int_pipe = usb_rcvintpipe(usb_dev, NCT6694_INT_IN_EP);
usb_fill_int_urb(usb_data->int_urb, usb_dev, int_pipe,
@@ -405,27 +339,27 @@ static int nct6694_usb_probe(struct usb_interface *iface,
nct6694_usb_int_callback, ddata,
usb_pipe_endpoint(usb_dev, int_pipe)->desc.bInterval);
- ret = usb_submit_urb(usb_data->int_urb, GFP_KERNEL);
+ usb_set_intfdata(iface, ddata);
+
+ ret = nct6694_device_init(dev);
if (ret)
- goto err_ida;
+ goto err_free_urb;
- usb_set_intfdata(iface, ddata);
+ ret = usb_submit_urb(usb_data->int_urb, GFP_KERNEL);
+ if (ret)
+ goto err_device_exit;
ret = mfd_add_hotplug_devices(dev, nct6694_usb_devs, ARRAY_SIZE(nct6694_usb_devs));
if (ret)
- goto err_mfd;
+ goto err_kill_urb;
return 0;
-err_mfd:
+err_kill_urb:
usb_kill_urb(usb_data->int_urb);
-err_ida:
- ida_destroy(&ddata->wdt_ida);
- ida_destroy(&ddata->canfd_ida);
- ida_destroy(&ddata->i2c_ida);
- ida_destroy(&ddata->gpio_ida);
- irq_domain_remove(ddata->domain);
-err_urb:
+err_device_exit:
+ nct6694_device_exit(dev);
+err_free_urb:
usb_free_urb(usb_data->int_urb);
return ret;
}
@@ -437,11 +371,7 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
mfd_remove_devices(ddata->dev);
usb_kill_urb(usb_data->int_urb);
- ida_destroy(&ddata->wdt_ida);
- ida_destroy(&ddata->canfd_ida);
- ida_destroy(&ddata->i2c_ida);
- ida_destroy(&ddata->gpio_ida);
- irq_domain_remove(ddata->domain);
+ nct6694_device_exit(ddata->dev);
usb_free_urb(usb_data->int_urb);
}
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 61155f9832b0..03296132ad34 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -91,6 +91,9 @@ struct nct6694 {
unsigned int irq_enable;
};
+int nct6694_device_init(struct device *dev);
+void nct6694_device_exit(struct device *dev);
+
int nct6694_usb_read_msg(struct nct6694 *ddata,
const struct nct6694_cmd_header *cmd_hd,
void *buf);
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 11/13] mfd: nct6694: Introduce regmap-based transport abstraction
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (9 preceding siblings ...)
2026-10-07 9:20 ` [PATCH v8 10/13] mfd: nct6694: Extract core device management into a separate module a0282524688
@ 2026-10-07 9:21 ` a0282524688
2026-10-07 9:21 ` [PATCH v8 12/13] mfd: nct6694: Add a Kconfig symbol for the USB transport a0282524688
2026-10-07 9:21 ` [PATCH v8 13/13] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:21 UTC (permalink / raw)
To: lee, Ming Yu, Andi Shyti; +Cc: linux-kernel, Ming Yu, linux-i2c, mfd
From: Ming Yu <a0282524688@gmail.com>
Sub-device drivers call into the USB transport directly, so a second
transport cannot be added without touching all of them.
Wrap the transport behind a regmap bus. The command header is mapped
onto a single 32-bit register by packing the host control byte, the
module id and the 16-bit offset, so that the sub-device drivers can use
the regmap bulk accessors. The access_lock is dropped as regmap already
serialises the bus accesses.
Add nct6694_write_read_msg() for the commands that send a request and
read the reply back in the same firmware message, and use it for the
I2C deliver command.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Renamed the transport buffer arguments to tx_buf/rx_buf.
- Dropped max_raw_read/max_raw_write from the regmap_config, as they
are ignored when a regmap_bus is provided.
Changes in v7:
- Made the USB transport helpers static and gave nct6694_usb_write_msg()
separate @tx/@rx buffers, so a SET command only copies the firmware
reply back when the caller asks for it.
- Documented in the shared header why nct6694_write_read_msg() is
expressed as a read of a SET register.
Changes in v6:
- New patch. Replaces the v5 function-pointer abstraction with a
regmap_bus based transport: the firmware command header is packed into
a single 32-bit regmap register and sub-device drivers use the regmap
bulk accessors. Adds nct6694_write_read_msg() for request/response
commands and drops the per-transport access_lock (regmap already
serialises bus accesses).
drivers/i2c/busses/i2c-nct6694.c | 2 +-
drivers/mfd/Kconfig | 1 +
drivers/mfd/nct6694-usb.c | 102 ++++++++++++++++++-------------
include/linux/mfd/nct6694.h | 53 +++++++++++++---
4 files changed, 108 insertions(+), 50 deletions(-)
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index ef3329f34246..7e32dab6e759 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -77,7 +77,7 @@ static int nct6694_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int
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);
+ ret = nct6694_write_read_msg(data->nct6694, &cmd_hd, deliver);
if (ret < 0)
return ret;
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e4fd4572472f..a0d9d6ccfb0c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1166,6 +1166,7 @@ config MFD_MENF21BMC
config MFD_NCT6694
tristate "Nuvoton NCT6694 support"
select MFD_CORE
+ select REGMAP
depends on USB
help
This enables support for the Nuvoton USB device NCT6694, which shares
diff --git a/drivers/mfd/nct6694-usb.c b/drivers/mfd/nct6694-usb.c
index 455eeed62dce..636721f277a0 100644
--- a/drivers/mfd/nct6694-usb.c
+++ b/drivers/mfd/nct6694-usb.c
@@ -9,6 +9,7 @@
* CAN, WDT, HWMON and RTC management.
*/
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -17,7 +18,9 @@
#include <linux/mfd/core.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/unaligned.h>
#include <linux/usb.h>
#define NCT6694_VENDOR_ID 0x0416
@@ -35,7 +38,6 @@ union __packed nct6694_usb_hdr {
struct nct6694_usb_data {
struct nct6694 core;
- struct mutex access_lock;
struct usb_device *usb_dev;
struct urb *int_urb;
union nct6694_usb_hdr *hdr_buf;
@@ -108,21 +110,9 @@ static int nct6694_usb_err_handling(struct nct6694 *ddata, unsigned char err_sta
return -EIO;
}
-/**
- * nct6694_usb_read_msg() - Read message from NCT6694 device
- * @ddata: 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_usb_read_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+static int nct6694_usb_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *rx_buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -133,8 +123,6 @@ int nct6694_usb_read_msg(struct nct6694 *ddata,
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;
- guard(mutex)(&usb_data->access_lock);
-
memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
hdr->cmd_header.hctrl = NCT6694_HCTRL_GET;
@@ -168,26 +156,15 @@ int nct6694_usb_read_msg(struct nct6694 *ddata,
return -EIO;
}
- memcpy(buf, usb_data->data_buf, data_len);
+ memcpy(rx_buf, usb_data->data_buf, data_len);
return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_usb_read_msg);
-/**
- * nct6694_usb_write_msg() - Write message to NCT6694 device
- * @ddata: 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_usb_write_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+/* @rx_buf may be NULL when the reply to the SET command is not needed */
+static int nct6694_usb_write_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ const void *tx_buf, void *rx_buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -198,11 +175,9 @@ int nct6694_usb_write_msg(struct nct6694 *ddata,
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;
- guard(mutex)(&usb_data->access_lock);
-
memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
hdr->cmd_header.hctrl = NCT6694_HCTRL_SET;
- memcpy(usb_data->data_buf, buf, data_len);
+ memcpy(usb_data->data_buf, tx_buf, data_len);
/* Send command packet to USB device */
ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, NCT6694_BULK_OUT_EP),
@@ -240,11 +215,53 @@ int nct6694_usb_write_msg(struct nct6694 *ddata,
return -EIO;
}
- memcpy(buf, usb_data->data_buf, data_len);
+ if (rx_buf)
+ memcpy(rx_buf, usb_data->data_buf, data_len);
return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_usb_write_msg);
+
+static int nct6694_usb_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct nct6694 *ddata = context;
+ u32 reg = get_unaligned_be32(reg_buf);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(val_size),
+ };
+
+ if (FIELD_GET(NCT6694_REG_HCTRL, reg) == NCT6694_HCTRL_SET)
+ return nct6694_usb_write_msg(ddata, &cmd_hd, val_buf, val_buf);
+
+ return nct6694_usb_read_msg(ddata, &cmd_hd, val_buf);
+}
+
+static int nct6694_usb_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct nct6694 *ddata = context;
+ u32 reg = get_unaligned_be32(data);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(count - sizeof(reg)),
+ };
+
+ return nct6694_usb_write_msg(ddata, &cmd_hd, data + sizeof(reg), NULL);
+}
+
+static const struct regmap_bus nct6694_usb_regmap_bus = {
+ .read = nct6694_usb_regmap_read,
+ .write = nct6694_usb_regmap_write,
+};
+
+static const struct regmap_config nct6694_usb_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 8,
+};
static void nct6694_usb_int_callback(struct urb *urb)
{
@@ -329,9 +346,12 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ddata->dev = dev;
usb_data->usb_dev = usb_dev;
- ret = devm_mutex_init(dev, &usb_data->access_lock);
- if (ret)
+ ddata->regmap = devm_regmap_init(dev, &nct6694_usb_regmap_bus, ddata,
+ &nct6694_usb_regmap_config);
+ if (IS_ERR(ddata->regmap)) {
+ ret = PTR_ERR(ddata->regmap);
goto err_free_urb;
+ }
int_pipe = usb_rcvintpipe(usb_dev, NCT6694_INT_IN_EP);
usb_fill_int_urb(usb_data->int_urb, usb_dev, int_pipe,
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 03296132ad34..1593806d34fd 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -9,6 +9,9 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#include <linux/bitfield.h>
+#include <linux/regmap.h>
+
#define NCT6694_HWMON_MOD 0x00
#define NCT6694_PWM_MOD 0x01
#define NCT6694_I2C_MOD 0x03
@@ -82,6 +85,7 @@ struct __packed nct6694_response_header {
struct nct6694 {
struct device *dev;
+ struct regmap *regmap;
struct ida gpio_ida;
struct ida i2c_ida;
struct ida canfd_ida;
@@ -94,25 +98,58 @@ struct nct6694 {
int nct6694_device_init(struct device *dev);
void nct6694_device_exit(struct device *dev);
-int nct6694_usb_read_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
-int nct6694_usb_write_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
+/*
+ * Each firmware command is mapped to a single 32-bit regmap register, so that
+ * the sub-device drivers use the regmap bulk accessors and each transport only
+ * implements a regmap bus:
+ *
+ * bits [31:24] host control (NCT6694_HCTRL_GET / NCT6694_HCTRL_SET)
+ * bits [23:16] module id
+ * bits [15:0] offset (low byte = command, high byte = selector)
+ */
+#define NCT6694_REG_HCTRL GENMASK(31, 24)
+#define NCT6694_REG_MOD GENMASK(23, 16)
+#define NCT6694_REG_OFFSET GENMASK(15, 0)
+
+static inline u32 nct6694_cmd_to_reg(const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl)
+{
+ return FIELD_PREP(NCT6694_REG_HCTRL, hctrl) |
+ FIELD_PREP(NCT6694_REG_MOD, cmd_hd->mod) |
+ FIELD_PREP(NCT6694_REG_OFFSET, le16_to_cpu(cmd_hd->offset));
+}
static inline int nct6694_read_msg(struct nct6694 *ddata,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_read_msg(ddata, cmd_hd, buf);
+ return regmap_bulk_read(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_GET),
+ buf, le16_to_cpu(cmd_hd->len));
}
static inline int nct6694_write_msg(struct nct6694 *ddata,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_write_msg(ddata, cmd_hd, buf);
+ return regmap_bulk_write(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
+}
+
+/*
+ * Some commands, such as the I2C deliver, send a request and read the reply
+ * back in the same firmware message. Express this as a read of a SET register:
+ * @buf holds the request on entry and the reply on return. This relies on the
+ * regmap being uncached and byte sized, so that @buf reaches the bus as is.
+ */
+static inline int nct6694_write_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return regmap_bulk_read(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
}
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 12/13] mfd: nct6694: Add a Kconfig symbol for the USB transport
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (10 preceding siblings ...)
2026-10-07 9:21 ` [PATCH v8 11/13] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
@ 2026-10-07 9:21 ` a0282524688
2026-10-07 9:21 ` [PATCH v8 13/13] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:21 UTC (permalink / raw)
To: lee; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Now that the sub-device drivers only access the device through regmap,
the core no longer depends on USB.
Build the USB transport under a new MFD_NCT6694_USB symbol and keep
MFD_NCT6694 for the core. MFD_NCT6694_USB defaults to MFD_NCT6694 so
that existing configurations keep the USB transport enabled.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- New patch. Split out of v7 "Rename driver to nct6694-usb and update
Kconfig"; MFD_NCT6694 remains the user visible core symbol and
MFD_NCT6694_USB defaults to it to keep existing configurations
working.
drivers/mfd/Kconfig | 20 ++++++++++++++++----
drivers/mfd/Makefile | 2 +-
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a0d9d6ccfb0c..de7d2171f514 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1167,18 +1167,30 @@ config MFD_NCT6694
tristate "Nuvoton NCT6694 support"
select MFD_CORE
select REGMAP
- depends on USB
help
- This enables support for the Nuvoton USB device NCT6694, which shares
+ This enables support for the Nuvoton 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.
+ This driver provides the core support shared by the transport
+ interface drivers, at least one of which must be enabled.
Additional drivers must be enabled to utilize the specific
functionalities of the device.
+config MFD_NCT6694_USB
+ tristate "Nuvoton NCT6694 USB interface support"
+ depends on MFD_NCT6694 && USB
+ default MFD_NCT6694
+ help
+ This enables support for the Nuvoton NCT6694 connected through
+ its USB interface. The driver uses USB bulk transfers to send
+ commands to the device firmware and an interrupt transfer to
+ receive its interrupt status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694-usb.
+
config MFD_OCELOT
tristate "Microsemi Ocelot External Control Support"
depends on SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dfd81356b485..a42b99250454 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -125,7 +125,7 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
obj-$(CONFIG_MFD_NCT6694) += nct6694-core.o
-obj-$(CONFIG_MFD_NCT6694) += nct6694-usb.o
+obj-$(CONFIG_MFD_NCT6694_USB) += nct6694-usb.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v8 13/13] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver
2026-10-07 9:20 [PATCH v8 00/13] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (11 preceding siblings ...)
2026-10-07 9:21 ` [PATCH v8 12/13] mfd: nct6694: Add a Kconfig symbol for the USB transport a0282524688
@ 2026-10-07 9:21 ` a0282524688
12 siblings, 0 replies; 14+ messages in thread
From: a0282524688 @ 2026-10-07 9:21 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu, mfd
From: Ming Yu <a0282524688@gmail.com>
Add support for the Host Interface (HIF) transport over eSPI for the
Nuvoton NCT6694.
The driver locates the Super-I/O to configure the shared memory base
address and SIRQ, and implements a regmap_bus that exchanges firmware
commands through the shared memory window. Report accesses are served
from a separate MMIO regmap.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v8:
- Embedded struct nct6694 in nct6694_hif_data and dropped the priv
pointer; renamed hdata to hif_data and the transfer buffers and
lengths consistently with the USB transport.
- Adapted to nct6694_device_{init,exit}() and register the MFD cells
from the transport.
- Request the interrupt after the core is set up, so that it is freed
before the IRQ domain is removed.
- Dropped the max_raw_read/max_raw_write settings, which are ignored
with a regmap_bus.
Changes in v7:
- Zero the tail of the caller buffer when the firmware answers with
fewer bytes than requested, and split the transfer helper into
separate @tx/@rx buffers instead of a shared scratch buffer.
- Request the interrupt before registering the core, so devres frees it
after the sub-devices are removed. Mask the interrupt and clear a
stale status in nct6694_hif_irq_init() so the handler cannot run
before the IRQ domain exists.
- Return IRQ_HANDLED once the interrupt status has been cleared.
- Skip hardware interrupts that have no mapping instead of dispatching
virq 0.
- Bail out with -ENODEV if the BIOS left the shared memory base address
unconfigured.
- Program the SIRQ register with the hardware interrupt number rather
than the Linux virq.
Changes in v6:
- Reworked the transport to implement a regmap_bus instead of the v5
read_msg/write_msg function pointers.
Changes in v5:
- Split from the monolithic v4 patch.
- Adapted to re-use the newly introduced nct6694_core_probe() and
abstracted I/O APIs.
Changes since version 3:
- Remove redundant module type macro definitions from sub-device drivers
that are now provided by the shared header <linux/mfd/nct6694.h>,
fixing -Wmacro-redefined warnings.
Changes since version 2:
- Restore per-device IDA and mfd_add_hotplug_devices()/PLATFORM_DEVID_AUTO
to avoid child device ID conflicts with multiple NCT6694 chips.
- Validate irq_find_mapping() return value before dispatching IRQs.
- Check superio_enter() return value in nct6694_irq_init().
Changes since version 1:
- Drop function pointers from Super-I/O access and use static inline
helpers with proper types.
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/nct6694-hif.c | 572 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/nct6694.h | 2 +-
4 files changed, 587 insertions(+), 1 deletion(-)
create mode 100644 drivers/mfd/nct6694-hif.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index de7d2171f514..9afd6b5bd28d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1178,6 +1178,19 @@ config MFD_NCT6694
Additional drivers must be enabled to utilize the specific
functionalities of the device.
+config MFD_NCT6694_HIF
+ tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
+ depends on MFD_NCT6694 && HAS_IOPORT && ACPI
+ select REGMAP_MMIO
+ help
+ This enables support for the Nuvoton NCT6694 connected through
+ the Host Interface (HIF) over eSPI. The driver uses the Super-I/O
+ configuration and the shared memory window to send commands to
+ the device firmware.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694-hif.
+
config MFD_NCT6694_USB
tristate "Nuvoton NCT6694 USB interface support"
depends on MFD_NCT6694 && USB
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a42b99250454..f98a92058827 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -125,6 +125,7 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
obj-$(CONFIG_MFD_NCT6694) += nct6694-core.o
+obj-$(CONFIG_MFD_NCT6694_HIF) += nct6694-hif.o
obj-$(CONFIG_MFD_NCT6694_USB) += nct6694-usb.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
diff --git a/drivers/mfd/nct6694-hif.c b/drivers/mfd/nct6694-hif.c
new file mode 100644
index 000000000000..3880f024d313
--- /dev/null
+++ b/drivers/mfd/nct6694-hif.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 host-interface (eSPI) transport driver.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/irq.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/regmap.h>
+#include <linux/unaligned.h>
+
+#define DRVNAME "nct6694-hif"
+
+#define NCT6694_POLL_INTERVAL_US 10
+#define NCT6694_POLL_TIMEOUT_US 10000
+
+/*
+ * Super-I/O registers
+ */
+#define SIO_REG_LDSEL 0x07 /* Logical device select */
+#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
+#define SIO_REG_LD_SHM 0x0F /* Logical device shared memory control */
+
+#define SIO_REG_SHM_ENABLE 0x30 /* Enable shared memory */
+#define SIO_REG_SHM_BASE_ADDR 0x60 /* Shared memory base address (2 bytes) */
+#define SIO_REG_SHM_IRQ_NR 0x70 /* Shared memory interrupt number */
+#define SIO_SHM_IRQ_NR_MAX 15 /* Highest ISA interrupt line */
+
+#define SIO_REG_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
+#define SIO_REG_LOCK_KEY 0xAA /* Key to disable Super-I/O */
+
+#define SIO_NCT6694B_ID 0xD029
+#define SIO_NCT6694D_ID 0x5832
+
+/*
+ * Super-I/O Shared Memory Logical Device registers
+ */
+#define NCT6694_SHM_COFS_STS 0x2E
+#define NCT6694_SHM_COFS_STS_COFS4W BIT(7)
+
+#define NCT6694_SHM_COFS_CTL2 0x3B
+#define NCT6694_SHM_COFS_CTL2_COFS4W_IE BIT(3)
+
+/* COFS register block [STS..CTL2] is the only SHM range driven via inb/outb */
+#define NCT6694_SHM_COFS_LEN \
+ (NCT6694_SHM_COFS_CTL2 - NCT6694_SHM_COFS_STS + 1)
+
+#define NCT6694_SHM_INTR_STATUS 0x9C /* Interrupt status register (4 bytes) */
+
+enum nct6694_chips {
+ NCT6694B = 0,
+ NCT6694D,
+};
+
+struct __packed nct6694_hif_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+ unsigned char data[];
+};
+
+struct nct6694_sio_data {
+ enum nct6694_chips chip;
+ int sioreg; /* Super-I/O index port */
+};
+
+struct nct6694_hif_data {
+ struct nct6694 core;
+ struct regmap *rpt_regmap;
+ struct nct6694_sio_data *sio_data;
+ void __iomem *msg_base;
+ unsigned int shm_base;
+};
+
+static struct nct6694_hif_data *to_nct6694_hif_data(struct nct6694 *ddata)
+{
+ return container_of(ddata, struct nct6694_hif_data, core);
+}
+
+static const char * const nct6694_chip_names[] = {
+ [NCT6694B] = "NCT6694B",
+ [NCT6694D] = "NCT6694D",
+};
+
+/*
+ * Super-I/O functions.
+ */
+static inline int superio_enter(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ /*
+ * Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
+ */
+ if (!request_muxed_region(ioreg, 2, DRVNAME))
+ return -EBUSY;
+
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+
+ return 0;
+}
+
+static inline void superio_exit(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LOCK_KEY, ioreg);
+
+ release_region(ioreg, 2);
+}
+
+static inline void superio_select(struct nct6694_sio_data *sio_data, int ld)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LDSEL, ioreg);
+ outb(ld, ioreg + 1);
+}
+
+static inline int superio_inb(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ return inb(ioreg + 1);
+}
+
+static inline int superio_inw(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+ int val;
+
+ outb(reg++, ioreg);
+ val = inb(ioreg + 1) << 8;
+ outb(reg, ioreg);
+ val |= inb(ioreg + 1);
+
+ return val;
+}
+
+static inline void superio_outb(struct nct6694_sio_data *sio_data, int reg, u8 val)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ outb(val, ioreg + 1);
+}
+
+static int nct6694_sio_find(struct nct6694_sio_data *sio_data, u8 sioreg)
+{
+ int ret;
+ u16 devid;
+
+ sio_data->sioreg = sioreg;
+
+ ret = superio_enter(sio_data);
+ if (ret)
+ return ret;
+
+ /* Check Chip ID */
+ devid = superio_inw(sio_data, SIO_REG_DEVID);
+ switch (devid) {
+ case SIO_NCT6694B_ID:
+ sio_data->chip = NCT6694B;
+ break;
+ case SIO_NCT6694D_ID:
+ sio_data->chip = NCT6694D;
+ break;
+ default:
+ superio_exit(sio_data);
+ return -ENODEV;
+ }
+
+ superio_exit(sio_data);
+
+ return 0;
+}
+
+static const struct mfd_cell nct6694_hif_devs[] = {
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+
+ MFD_CELL_NAME("nct6694-canfd"),
+ MFD_CELL_NAME("nct6694-canfd"),
+};
+
+static int nct6694_hif_err_handling(struct nct6694 *ddata, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(ddata->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(ddata->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(ddata->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(ddata->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+/* @tx_buf is NULL for GET, @rx_buf is NULL if the reply is not needed */
+static int nct6694_hif_xfer_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl, const void *tx_buf, void *rx_buf)
+{
+ struct nct6694_hif_data *hif_data = to_nct6694_hif_data(ddata);
+ void __iomem *hdr = hif_data->msg_base + offsetof(struct nct6694_hif_msg, cmd_header);
+ void __iomem *data = hif_data->msg_base + offsetof(struct nct6694_hif_msg, data);
+ struct nct6694_cmd_header cmd = *cmd_hd;
+ struct nct6694_response_header resp;
+ u16 data_len = le16_to_cpu(cmd.len);
+ u16 resp_len;
+ u8 status;
+ int ret;
+
+ if (data_len > NCT6694_MAX_DATA_LEN)
+ return -EINVAL;
+
+ /* Wait until the previous command is completed */
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ /*
+ * Write cmd header fields, but skip hctrl - writing to it triggers
+ * firmware command processing and must be deferred until data is ready.
+ */
+ memcpy_toio(hdr, &cmd, offsetof(struct nct6694_cmd_header, hctrl));
+ memcpy_toio(hdr + offsetof(struct nct6694_cmd_header, rsv2), &cmd.rsv2,
+ sizeof(cmd) - offsetof(struct nct6694_cmd_header, rsv2));
+
+ if (tx_buf)
+ memcpy_toio(data, tx_buf, data_len);
+
+ /* Write hctrl last to trigger command processing */
+ writeb(hctrl, hdr + offsetof(struct nct6694_cmd_header, hctrl));
+
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ memcpy_fromio(&resp, hif_data->msg_base + offsetof(struct nct6694_hif_msg, response_header),
+ sizeof(resp));
+
+ ret = nct6694_hif_err_handling(ddata, resp.sts);
+ if (ret)
+ return ret;
+
+ if (!rx_buf)
+ return 0;
+
+ /* The firmware may answer with fewer bytes than requested */
+ resp_len = min(data_len, le16_to_cpu(resp.len));
+ memcpy_fromio(rx_buf, data, resp_len);
+ memset(rx_buf + resp_len, 0, data_len - resp_len);
+
+ return 0;
+}
+
+static int nct6694_hif_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct nct6694 *ddata = context;
+ struct nct6694_hif_data *hif_data = to_nct6694_hif_data(ddata);
+ u32 reg = get_unaligned_be32(reg_buf);
+ u8 hctrl = FIELD_GET(NCT6694_REG_HCTRL, reg);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(val_size),
+ };
+
+ if (cmd_hd.mod == NCT6694_RPT_MOD)
+ return regmap_bulk_read(hif_data->rpt_regmap,
+ FIELD_GET(NCT6694_REG_OFFSET, reg),
+ val_buf, val_size);
+
+ return nct6694_hif_xfer_msg(ddata, &cmd_hd, hctrl,
+ hctrl == NCT6694_HCTRL_SET ? val_buf : NULL,
+ val_buf);
+}
+
+static int nct6694_hif_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct nct6694 *ddata = context;
+ struct nct6694_hif_data *hif_data = to_nct6694_hif_data(ddata);
+ u32 reg = get_unaligned_be32(data);
+ size_t data_len = count - sizeof(reg);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(data_len),
+ };
+
+ if (cmd_hd.mod == NCT6694_RPT_MOD)
+ return regmap_bulk_write(hif_data->rpt_regmap,
+ FIELD_GET(NCT6694_REG_OFFSET, reg),
+ data + sizeof(reg), data_len);
+
+ return nct6694_hif_xfer_msg(ddata, &cmd_hd, NCT6694_HCTRL_SET,
+ data + sizeof(reg), NULL);
+}
+
+static const struct regmap_bus nct6694_hif_regmap_bus = {
+ .read = nct6694_hif_regmap_read,
+ .write = nct6694_hif_regmap_write,
+};
+
+static const struct regmap_config nct6694_hif_msg_regmap_config = {
+ .name = "msg",
+ .reg_bits = 32,
+ .val_bits = 8,
+};
+
+static const struct regmap_config nct6694_hif_rpt_regmap_config = {
+ .name = "rpt",
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static irqreturn_t nct6694_hif_irq_handler(int irq, void *data)
+{
+ struct nct6694_hif_data *hif_data = data;
+ u8 reg_data[4];
+ u32 intr_status;
+ int ret;
+
+ if (!(inb(hif_data->shm_base + NCT6694_SHM_COFS_STS) & NCT6694_SHM_COFS_STS_COFS4W))
+ return IRQ_NONE;
+
+ outb(NCT6694_SHM_COFS_STS_COFS4W, hif_data->shm_base + NCT6694_SHM_COFS_STS);
+
+ ret = regmap_bulk_read(hif_data->rpt_regmap, NCT6694_SHM_INTR_STATUS,
+ reg_data, ARRAY_SIZE(reg_data));
+ if (ret)
+ return IRQ_HANDLED;
+
+ intr_status = get_unaligned_le32(reg_data);
+
+ while (intr_status) {
+ int hwirq = __ffs(intr_status);
+
+ generic_handle_domain_irq_safe(hif_data->core.domain, hwirq);
+ intr_status &= ~BIT(hwirq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void nct6694_hif_irq_disable(void *data)
+{
+ struct nct6694_hif_data *hif_data = data;
+ u8 cofs_ctl2;
+
+ cofs_ctl2 = inb(hif_data->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 &= ~NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hif_data->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static void nct6694_hif_irq_enable(struct nct6694_hif_data *hif_data)
+{
+ u8 cofs_ctl2;
+
+ cofs_ctl2 = inb(hif_data->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 |= NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hif_data->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static int nct6694_hif_irq_init(struct nct6694_hif_data *hif_data, int irq)
+{
+ struct nct6694_sio_data *sio_data = hif_data->sio_data;
+ struct irq_data *irq_data;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ /* The chip is programmed with the SIRQ line, not the Linux irq number */
+ irq_data = irq_get_irq_data(irq);
+ if (!irq_data)
+ return -EINVAL;
+
+ hwirq = irqd_to_hwirq(irq_data);
+ if (hwirq > SIO_SHM_IRQ_NR_MAX)
+ return -ERANGE;
+
+ ret = superio_enter(sio_data);
+ if (ret)
+ return ret;
+
+ superio_select(sio_data, SIO_REG_LD_SHM);
+
+ if (!superio_inb(sio_data, SIO_REG_SHM_ENABLE)) {
+ superio_exit(sio_data);
+ return -EIO;
+ }
+
+ hif_data->shm_base = superio_inw(sio_data, SIO_REG_SHM_BASE_ADDR);
+ if (!hif_data->shm_base) {
+ superio_exit(sio_data);
+ return -ENODEV;
+ }
+
+ superio_outb(sio_data, SIO_REG_SHM_IRQ_NR, hwirq);
+
+ superio_exit(sio_data);
+
+ if (!devm_request_region(hif_data->core.dev,
+ hif_data->shm_base + NCT6694_SHM_COFS_STS,
+ NCT6694_SHM_COFS_LEN, DRVNAME))
+ return -EBUSY;
+
+ /* Keep the device quiet until the IRQ domain is ready */
+ nct6694_hif_irq_disable(hif_data);
+ outb(NCT6694_SHM_COFS_STS_COFS4W, hif_data->shm_base + NCT6694_SHM_COFS_STS);
+
+ return 0;
+}
+
+static void nct6694_hif_device_exit(void *dev)
+{
+ nct6694_device_exit(dev);
+}
+
+static const u8 sio_addrs[] = { 0x2e, 0x4e };
+
+static int nct6694_hif_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694_sio_data *sio_data;
+ struct nct6694_hif_data *hif_data;
+ void __iomem *rpt_base, *msg_base;
+ struct nct6694 *ddata;
+ int ret, i, irq;
+
+ rpt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rpt_base))
+ return PTR_ERR(rpt_base);
+
+ msg_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(msg_base))
+ return PTR_ERR(msg_base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ sio_data = devm_kzalloc(dev, sizeof(*sio_data), GFP_KERNEL);
+ if (!sio_data)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(sio_addrs); i++) {
+ if (!nct6694_sio_find(sio_data, sio_addrs[i]))
+ break;
+ }
+ if (i == ARRAY_SIZE(sio_addrs))
+ return -ENODEV;
+
+ dev_dbg(dev, "Found %s at %#x\n", nct6694_chip_names[sio_data->chip], sio_data->sioreg);
+
+ hif_data = devm_kzalloc(dev, sizeof(*hif_data), GFP_KERNEL);
+ if (!hif_data)
+ return -ENOMEM;
+
+ hif_data->sio_data = sio_data;
+ hif_data->msg_base = msg_base;
+ hif_data->rpt_regmap = devm_regmap_init_mmio(dev, rpt_base,
+ &nct6694_hif_rpt_regmap_config);
+ if (IS_ERR(hif_data->rpt_regmap))
+ return PTR_ERR(hif_data->rpt_regmap);
+
+ ddata = &hif_data->core;
+ ddata->dev = dev;
+ ddata->regmap = devm_regmap_init(dev, &nct6694_hif_regmap_bus, ddata,
+ &nct6694_hif_msg_regmap_config);
+ if (IS_ERR(ddata->regmap))
+ return PTR_ERR(ddata->regmap);
+
+ ret = nct6694_hif_irq_init(hif_data, irq);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, ddata);
+
+ ret = nct6694_device_init(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_hif_device_exit, dev);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_hif_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), hif_data);
+ if (ret)
+ return ret;
+
+ nct6694_hif_irq_enable(hif_data);
+
+ ret = devm_add_action_or_reset(dev, nct6694_hif_irq_disable, hif_data);
+ if (ret)
+ return ret;
+
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, nct6694_hif_devs,
+ ARRAY_SIZE(nct6694_hif_devs), NULL, 0, NULL);
+}
+
+static const struct acpi_device_id nct6694_hif_acpi_ids[] = {
+ { "NTN0538", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, nct6694_hif_acpi_ids);
+
+static struct platform_driver nct6694_hif_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .acpi_match_table = nct6694_hif_acpi_ids,
+ },
+ .probe = nct6694_hif_probe,
+};
+module_platform_driver(nct6694_hif_driver);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 host-interface transport 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
index 1593806d34fd..34ea9edfca42 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2025 Nuvoton Technology Corp.
*
- * Nuvoton NCT6694 core definitions shared by the transport driver
+ * Nuvoton NCT6694 core definitions shared by the transport drivers
* and the sub-device drivers.
*/
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread