* [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support
@ 2026-07-01 3:50 a0282524688
2026-07-01 3:50 ` [PATCH v6 1/7] mfd: nct6694: Move module type macros to shared header a0282524688
` (6 more replies)
0 siblings, 7 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
The Nuvoton NCT6694 is a peripheral expander that provides GPIO, I2C,
CAN-FD, Watchdog, HWMON, PWM, and RTC sub-devices. Currently, the
driver only supports USB as the host transport interface.
This series refactors the NCT6694 MFD core to support multiple transport
backends and adds a new Host Interface (HIF) transport driver that
communicates over eSPI using Super-I/O shared memory.
The refactoring is split into small, self-contained steps: the USB
transport is first isolated behind a dedicated data structure and file
(nct6694-usb.c), the transport-agnostic device management is extracted
into a new core module (nct6694-core.c), and the firmware command
interface is then wrapped behind regmap so that sub-device drivers no
longer reference any transport detail. The HIF (eSPI) driver is finally
added cleanly on top of this abstraction, reusing the shared core.
Changes since version 5:
- Replaced the custom read_msg/write_msg function-pointer indirection
with a regmap-based transport abstraction. The firmware addressing
(host control, module id and 16-bit offset) is packed into a single
32-bit regmap register, so sub-device drivers use the standard regmap
bulk accessors while each transport implements its own regmap_bus.
- Added nct6694_write_read_msg() for the few commands (e.g. the I2C
"deliver") that send a request and read the reply back in a single
transaction, and converted the I2C transfer path to use it.
- Dropped the per-transport access_lock; regmap already serialises bus
accesses on its own.
- Reordered the series accordingly: rename the USB I/O functions and the
driver file first, extract the transport-agnostic core, then introduce
the regmap abstraction, and finally add the HIF driver.
Changes since version 4:
- Split the monolithic refactoring and HIF support patch into a series of
smaller, logical commits to improve reviewability and adhere to the
single logical change principle.
- Decoupled USB-specific data into a dedicated 'nct6694_usb_data'
structure referenced through a void *priv pointer in the core.
- Renamed the existing driver to 'nct6694-usb.c' to strictly identify its
transport boundary, alongside Kconfig/Makefile updates.
- Extracted transport-agnostic device management (IRQ domain setup, IDA
initialization, and MFD cell registration) into a standalone
'nct6694-core.c' module.
- Added the 'nct6694-hif' eSPI transport driver on top of the new core
abstraction.
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:
- Reworked the Super-I/O access helpers.
Ming Yu (7):
mfd: nct6694: Move module type macros to shared header
mfd: nct6694: Refactor USB-specific data into nct6694_usb_data
mfd: nct6694: Rename USB transport functions with _usb_ prefix
mfd: nct6694: Rename driver to nct6694-usb and update Kconfig
mfd: nct6694: Extract core device management into a separate module
mfd: nct6694: Introduce regmap-based transport abstraction
mfd: nct6694: Add Host Interface (HIF) eSPI transport driver
MAINTAINERS | 2 +-
drivers/gpio/gpio-nct6694.c | 7 -
drivers/hwmon/nct6694-hwmon.c | 21 -
drivers/i2c/busses/i2c-nct6694.c | 9 +-
drivers/mfd/Kconfig | 48 +-
drivers/mfd/Makefile | 4 +-
drivers/mfd/nct6694-core.c | 136 ++++++
drivers/mfd/nct6694-hif.c | 565 +++++++++++++++++++++++
drivers/mfd/{nct6694.c => nct6694-usb.c} | 283 ++++++------
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 | 102 +++-
13 files changed, 969 insertions(+), 228 deletions(-)
create mode 100644 drivers/mfd/nct6694-core.c
create mode 100644 drivers/mfd/nct6694-hif.c
rename drivers/mfd/{nct6694.c => nct6694-usb.c} (54%)
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v6 1/7] mfd: nct6694: Move module type macros to shared header
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-01 3:50 ` [PATCH v6 2/7] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data a0282524688
` (5 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 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
From: Ming Yu <a0282524688@gmail.com>
Move NCT6694_XXX_MOD macro definitions from individual sub-device
drivers into the shared header include/linux/mfd/nct6694.h.
This is a prerequisite for supporting multiple transport interfaces
(USB, HIF) without duplicating these definitions.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v6:
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 a8607f0d9915..53bfc5983648 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 6dcf22ca5018..581451875f2c 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 6eb9be2cd4a0..3c683e317aa3 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 0x81
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v6 2/7] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
2026-07-01 3:50 ` [PATCH v6 1/7] mfd: nct6694: Move module type macros to shared header a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-01 3:50 ` [PATCH v6 3/7] mfd: nct6694: Rename USB transport functions with _usb_ prefix a0282524688
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
Separate USB transport-specific fields from the core nct6694 structure
into a new nct6694_usb_data structure. This decouples the shared MFD
core from the USB transport layer, preparing the driver for potential
support of alternative transport backends in the future.
The following fields are moved into nct6694_usb_data:
- access_lock
- int_in_urb
- udev
- usb_msg
- int_buffer
The core nct6694 structure now holds a void *priv pointer to reference
the transport-specific data. USB-only definitions (vendor/product IDs,
endpoint addresses, URB timeout, and the USB message union) are also
moved from the shared header into the USB driver source file.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v6:
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
drivers/mfd/nct6694.c | 76 ++++++++++++++++++++++++++-----------
include/linux/mfd/nct6694.h | 27 ++++---------
2 files changed, 62 insertions(+), 41 deletions(-)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 308b2fda3055..58c1cbcbe3f2 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -10,8 +10,8 @@
*/
#include <linux/bits.h>
-#include <linux/interrupt.h>
#include <linux/idr.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
@@ -22,6 +22,27 @@
#include <linux/spinlock.h>
#include <linux/usb.h>
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_URB_TIMEOUT 1000
+
+union __packed nct6694_usb_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694_usb_data {
+ struct mutex access_lock;
+ struct urb *int_in_urb;
+ struct usb_device *udev;
+ union nct6694_usb_msg *usb_msg;
+ __le32 *int_buffer;
+};
+
static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-gpio"),
MFD_CELL_NAME("nct6694-gpio"),
@@ -96,11 +117,12 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
*/
int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
@@ -146,11 +168,12 @@ EXPORT_SYMBOL_GPL(nct6694_read_msg);
*/
int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
@@ -277,6 +300,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
struct usb_endpoint_descriptor *int_endpoint;
struct usb_host_interface *interface;
struct device *dev = &iface->dev;
+ struct nct6694_usb_data *udata;
struct nct6694 *nct6694;
int ret;
@@ -284,18 +308,26 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (!nct6694)
return -ENOMEM;
- nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
- if (!nct6694->usb_msg)
+ udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL);
+ if (!udata)
+ return -ENOMEM;
+
+ udata->usb_msg = devm_kzalloc(dev, sizeof(*udata->usb_msg), GFP_KERNEL);
+ if (!udata->usb_msg)
return -ENOMEM;
- nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
- if (!nct6694->int_buffer)
+ udata->int_buffer = devm_kzalloc(dev, sizeof(*udata->int_buffer), GFP_KERNEL);
+ if (!udata->int_buffer)
return -ENOMEM;
- nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!nct6694->int_in_urb)
+ udata->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!udata->int_in_urb)
return -ENOMEM;
+ udata->udev = udev;
+
+ nct6694->priv = udata;
+
nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
&nct6694_irq_domain_ops,
nct6694);
@@ -305,7 +337,6 @@ static int nct6694_usb_probe(struct usb_interface *iface,
}
nct6694->dev = dev;
- nct6694->udev = udev;
ida_init(&nct6694->gpio_ida);
ida_init(&nct6694->i2c_ida);
@@ -314,7 +345,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
spin_lock_init(&nct6694->irq_lock);
- ret = devm_mutex_init(dev, &nct6694->access_lock);
+ ret = devm_mutex_init(dev, &udata->access_lock);
if (ret)
goto err_ida;
@@ -326,11 +357,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
goto err_ida;
}
- usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
- nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
+ usb_fill_int_urb(udata->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ udata->int_buffer, sizeof(*udata->int_buffer), usb_int_callback,
nct6694, int_endpoint->bInterval);
- ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
if (ret)
goto err_ida;
@@ -343,7 +374,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
return 0;
err_mfd:
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->int_in_urb);
err_ida:
ida_destroy(&nct6694->wdt_ida);
ida_destroy(&nct6694->canfd_ida);
@@ -351,22 +382,23 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ida_destroy(&nct6694->gpio_ida);
irq_domain_remove(nct6694->domain);
err_urb:
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(udata->int_in_urb);
return ret;
}
static void nct6694_usb_disconnect(struct usb_interface *iface)
{
struct nct6694 *nct6694 = usb_get_intfdata(iface);
+ struct nct6694_usb_data *udata = nct6694->priv;
mfd_remove_devices(nct6694->dev);
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->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);
+ usb_free_urb(udata->int_in_urb);
}
static const struct usb_device_id nct6694_ids[] = {
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 3c683e317aa3..3f5dd53f38de 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -2,12 +2,18 @@
/*
* Copyright (C) 2025 Nuvoton Technology Corp.
*
- * Nuvoton NCT6694 USB transaction and data structure.
+ * Nuvoton NCT6694 core definitions shared by all transport drivers
+ * and sub-device drivers.
*/
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
#define NCT6694_HWMON_MOD 0x00
#define NCT6694_PWM_MOD 0x01
#define NCT6694_I2C_MOD 0x03
@@ -17,17 +23,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 0x81
-#define NCT6694_BULK_IN_EP 0x02
-#define NCT6694_BULK_OUT_EP 0x03
-
#define NCT6694_HCTRL_SET 0x40
#define NCT6694_HCTRL_GET 0x80
-#define NCT6694_URB_TIMEOUT 1000
-
enum nct6694_irq_id {
NCT6694_IRQ_GPIO0 = 0,
NCT6694_IRQ_GPIO1,
@@ -84,11 +82,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;
@@ -96,13 +89,9 @@ 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;
- __le32 *int_buffer;
unsigned int irq_enable;
+ void *priv;
};
int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v6 3/7] mfd: nct6694: Rename USB transport functions with _usb_ prefix
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
2026-07-01 3:50 ` [PATCH v6 1/7] mfd: nct6694: Move module type macros to shared header a0282524688
2026-07-01 3:50 ` [PATCH v6 2/7] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-01 3:50 ` [PATCH v6 4/7] mfd: nct6694: Rename driver to nct6694-usb and update Kconfig a0282524688
` (3 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
Rename nct6694_{read,write,err_handling}_msg() to carry an `_usb_`
prefix, marking them as USB transport-specific. This prepares for
the core layer and HIF (eSPI) transport added in later patches.
Add transitional static inline wrappers nct6694_{read,write}_msg()
in the shared header so sub-device drivers remain untouched in
this commit; they will be replaced by the transport dispatch layer
in a subsequent patch.
No functional change.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
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 58c1cbcbe3f2..7c6b986db7f7 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -43,7 +43,7 @@ struct nct6694_usb_data {
__le32 *int_buffer;
};
-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"),
@@ -79,7 +79,7 @@ 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_usb_err_handling(struct nct6694 *nct6694, unsigned char err_status)
{
switch (err_status) {
case NCT6694_NO_ERROR:
@@ -104,7 +104,7 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
}
/**
- * nct6694_read_msg() - Read message from NCT6694 device
+ * nct6694_usb_read_msg() - Read message from NCT6694 device
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer to store the response data
@@ -115,7 +115,9 @@ 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_usb_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *udata = nct6694->priv;
union nct6694_usb_msg *msg = udata->usb_msg;
@@ -151,12 +153,12 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->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
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer containing the data to be sent
@@ -166,7 +168,9 @@ 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_usb_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *udata = nct6694->priv;
union nct6694_usb_msg *msg = udata->usb_msg;
@@ -208,11 +212,11 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->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 *nct6694 = urb->context;
__le32 *status_le = urb->transfer_buffer;
@@ -358,7 +362,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
}
usb_fill_int_urb(udata->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
- udata->int_buffer, sizeof(*udata->int_buffer), usb_int_callback,
+ udata->int_buffer, sizeof(*udata->int_buffer), nct6694_usb_int_callback,
nct6694, int_endpoint->bInterval);
ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
@@ -367,7 +371,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
usb_set_intfdata(iface, nct6694);
- 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;
@@ -401,20 +405,20 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
usb_free_urb(udata->int_in_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 3f5dd53f38de..43b51f243e8e 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -94,7 +94,25 @@ struct nct6694 {
void *priv;
};
-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_usb_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+int nct6694_usb_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+
+static inline int nct6694_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694_usb_read_msg(nct6694, cmd_hd, buf);
+}
+
+static inline int nct6694_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694_usb_write_msg(nct6694, cmd_hd, buf);
+}
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v6 4/7] mfd: nct6694: Rename driver to nct6694-usb and update Kconfig
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (2 preceding siblings ...)
2026-07-01 3:50 ` [PATCH v6 3/7] mfd: nct6694: Rename USB transport functions with _usb_ prefix a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-01 3:50 ` [PATCH v6 5/7] mfd: nct6694: Extract core device management into a separate module a0282524688
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
Rename nct6694.c to nct6694-usb.c to accurately reflect that it
implements the USB transport backend.
Additionally, introduce a new MFD_NCT6694_USB Kconfig option and convert
the existing MFD_NCT6694 into a hidden core symbol. The core symbol is
now automatically selected by the transport drivers.
This Kconfig and naming restructure aligns with standard MFD transport
abstraction practices, paving the way for future interfaces (e.g., HIF)
to be seamlessly integrated.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
Changes in v6:
Changes in v5:
- Split from the monolithic v4 patch to follow the single logical change
principle.
MAINTAINERS | 2 +-
drivers/mfd/Kconfig | 31 ++++++++++++++++--------
drivers/mfd/Makefile | 2 +-
drivers/mfd/{nct6694.c => nct6694-usb.c} | 0
4 files changed, 23 insertions(+), 12 deletions(-)
rename drivers/mfd/{nct6694.c => nct6694-usb.c} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..3f483c2d783d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19318,7 +19318,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/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782..5506a0adf3ec 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1164,19 +1164,30 @@ config MFD_MENF21BMC
will be called menf21bmc.
config MFD_NCT6694
- tristate "Nuvoton NCT6694 support"
+ tristate
select MFD_CORE
+ help
+ Core MFD support for the Nuvoton NCT6694 peripheral expander.
+ This provides the common APIs and shared structures used by all
+ interfaces (USB, HIF) to access the NCT6694 hardware features
+ such as GPIO, I2C, CAN-FD, Watchdog, ADC, PWM, and RTC.
+
+ It is selected automatically by the transport interface drivers.
+
+config MFD_NCT6694_USB
+ tristate "Nuvoton NCT6694 USB interface support"
+ select MFD_NCT6694
depends on USB
help
- This enables support for the Nuvoton USB device NCT6694, which shares
- peripherals.
- The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
- 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
- PWM, and RTC.
- This driver provides core APIs to access the NCT6694 hardware
- monitoring and control features.
- Additional drivers must be enabled to utilize the specific
- functionalities of the device.
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the USB interface.
+
+ The transport driver uses USB bulk and interrupt transfers to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 via a USB connection.
+
+ 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"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dd4bb7e77c33..48caac64f3d8 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_USB) += 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 related [flat|nested] 11+ messages in thread
* [PATCH v6 5/7] mfd: nct6694: Extract core device management into a separate module
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (3 preceding siblings ...)
2026-07-01 3:50 ` [PATCH v6 4/7] mfd: nct6694: Rename driver to nct6694-usb and update Kconfig a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-01 3:50 ` [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
2026-07-01 3:50 ` [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
6 siblings, 0 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
Extract the transport-agnostic core logic, including IRQ domain setup,
IDA initialization, and MFD sub-device registration, from the USB driver
into a new nct6694-core.c module.
The core routines are exported as nct6694_core_probe() and
nct6694_core_remove() to be consumed by the transport drivers. The USB
driver is updated to pass its specific MFD cells to the core probe
routine.
This completes the transport abstraction, ensuring that the shared
device management logic is cleanly separated from the underlying I/O
implementation, and is fully ready for new transport backends
(e.g., HIF).
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
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 | 136 ++++++++++++++++++++++++++++++++++++
drivers/mfd/nct6694-usb.c | 91 ++----------------------
include/linux/mfd/nct6694.h | 9 ++-
5 files changed, 153 insertions(+), 86 deletions(-)
create mode 100644 drivers/mfd/nct6694-core.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3f483c2d783d..bc4a031001d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19318,7 +19318,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 48caac64f3d8..10c19a19541e 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_USB) += 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..36dfaa2e327f
--- /dev/null
+++ b/drivers/mfd/nct6694-core.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 MFD core driver.
+ *
+ * This provides common registration for IRQ domain, IDA pools,
+ * and MFD sub-devices shared by all transport drivers.
+ */
+
+#include <linux/idr.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->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 *nct6694 = d->host_data;
+
+ irq_set_chip_data(irq, nct6694);
+ irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+ return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+ .map = nct6694_irq_domain_map,
+ .unmap = nct6694_irq_domain_unmap,
+};
+
+/**
+ * nct6694_core_probe() - Register IRQ domain, IDAs, and MFD sub-devices
+ * @dev: parent device (USB interface or platform device)
+ * @nct6694: initialized nct6694 structure with its transport regmap set
+ *
+ * This function completes the common probe steps shared by all transport
+ * drivers: IRQ domain creation, IDA initialization, and MFD cell registration.
+ *
+ * The caller must have already set nct6694->dev, nct6694->priv, and
+ * nct6694->regmap before calling this.
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
+ const struct mfd_cell *cells, int n_cells)
+{
+ int ret;
+
+ spin_lock_init(&nct6694->irq_lock);
+
+ ida_init(&nct6694->gpio_ida);
+ ida_init(&nct6694->i2c_ida);
+ ida_init(&nct6694->canfd_ida);
+ ida_init(&nct6694->wdt_ida);
+
+ nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ nct6694);
+ if (!nct6694->domain) {
+ ret = -ENODEV;
+ goto err_ida;
+ }
+
+ ret = mfd_add_hotplug_devices(dev, cells, n_cells);
+ if (ret)
+ goto err_domain;
+
+ return 0;
+
+err_domain:
+ irq_domain_remove(nct6694->domain);
+err_ida:
+ ida_destroy(&nct6694->wdt_ida);
+ ida_destroy(&nct6694->canfd_ida);
+ ida_destroy(&nct6694->i2c_ida);
+ ida_destroy(&nct6694->gpio_ida);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(nct6694_core_probe);
+
+/**
+ * nct6694_core_remove() - Unregister MFD sub-devices and free core resources
+ * @nct6694: nct6694 structure previously passed to nct6694_core_probe()
+ */
+void nct6694_core_remove(struct nct6694 *nct6694)
+{
+ mfd_remove_devices(nct6694->dev);
+ irq_domain_remove(nct6694->domain);
+ ida_destroy(&nct6694->wdt_ida);
+ ida_destroy(&nct6694->canfd_ida);
+ ida_destroy(&nct6694->i2c_ida);
+ ida_destroy(&nct6694->gpio_ida);
+}
+EXPORT_SYMBOL_GPL(nct6694_core_remove);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 MFD 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 7c6b986db7f7..2289ebfde7fa 100644
--- a/drivers/mfd/nct6694-usb.c
+++ b/drivers/mfd/nct6694-usb.c
@@ -10,7 +10,6 @@
*/
#include <linux/bits.h>
-#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.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
@@ -246,57 +244,9 @@ static void nct6694_usb_int_callback(struct urb *urb)
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(nct6694->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);
- irq_hw_number_t hwirq = irqd_to_hwirq(data);
-
- guard(spinlock_irqsave)(&nct6694->irq_lock);
-
- nct6694->irq_enable |= BIT(hwirq);
-}
-
-static void nct6694_irq_disable(struct irq_data *data)
-{
- struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
- irq_hw_number_t hwirq = irqd_to_hwirq(data);
-
- guard(spinlock_irqsave)(&nct6694->irq_lock);
-
- nct6694->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 *nct6694 = d->host_data;
-
- irq_set_chip_data(irq, nct6694);
- irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
-
- return 0;
-}
-
-static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
-{
- irq_set_chip_and_handler(irq, NULL, NULL);
- irq_set_chip_data(irq, NULL);
-}
-
-static const struct irq_domain_ops nct6694_irq_domain_ops = {
- .map = nct6694_irq_domain_map,
- .unmap = nct6694_irq_domain_unmap,
-};
-
static int nct6694_usb_probe(struct usb_interface *iface,
const struct usb_device_id *id)
{
@@ -330,35 +280,19 @@ static int nct6694_usb_probe(struct usb_interface *iface,
udata->udev = udev;
- nct6694->priv = udata;
-
- nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
- &nct6694_irq_domain_ops,
- nct6694);
- if (!nct6694->domain) {
- ret = -ENODEV;
- goto err_urb;
- }
-
nct6694->dev = dev;
-
- ida_init(&nct6694->gpio_ida);
- ida_init(&nct6694->i2c_ida);
- ida_init(&nct6694->canfd_ida);
- ida_init(&nct6694->wdt_ida);
-
- spin_lock_init(&nct6694->irq_lock);
+ nct6694->priv = udata;
ret = devm_mutex_init(dev, &udata->access_lock);
if (ret)
- goto err_ida;
+ goto err_urb;
interface = iface->cur_altsetting;
int_endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(int_endpoint)) {
ret = -ENODEV;
- goto err_ida;
+ goto err_urb;
}
usb_fill_int_urb(udata->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
@@ -367,11 +301,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
if (ret)
- goto err_ida;
+ goto err_urb;
usb_set_intfdata(iface, nct6694);
- ret = mfd_add_hotplug_devices(dev, nct6694_usb_devs, ARRAY_SIZE(nct6694_usb_devs));
+ ret = nct6694_core_probe(dev, nct6694, nct6694_usb_devs, ARRAY_SIZE(nct6694_usb_devs));
if (ret)
goto err_mfd;
@@ -379,12 +313,6 @@ static int nct6694_usb_probe(struct usb_interface *iface,
err_mfd:
usb_kill_urb(udata->int_in_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);
err_urb:
usb_free_urb(udata->int_in_urb);
return ret;
@@ -395,13 +323,8 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
struct nct6694 *nct6694 = usb_get_intfdata(iface);
struct nct6694_usb_data *udata = nct6694->priv;
- mfd_remove_devices(nct6694->dev);
+ nct6694_core_remove(nct6694);
usb_kill_urb(udata->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(udata->int_in_urb);
}
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 43b51f243e8e..853b1530755d 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -10,10 +10,13 @@
#define __MFD_NCT6694_H
#include <linux/idr.h>
-#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/types.h>
+struct device;
+struct irq_domain;
+struct mfd_cell;
+
#define NCT6694_HWMON_MOD 0x00
#define NCT6694_PWM_MOD 0x01
#define NCT6694_I2C_MOD 0x03
@@ -94,6 +97,10 @@ struct nct6694 {
void *priv;
};
+int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
+ const struct mfd_cell *cells, int n_cells);
+void nct6694_core_remove(struct nct6694 *nct6694);
+
int nct6694_usb_read_msg(struct nct6694 *nct6694,
const struct nct6694_cmd_header *cmd_hd,
void *buf);
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (4 preceding siblings ...)
2026-07-01 3:50 ` [PATCH v6 5/7] mfd: nct6694: Extract core device management into a separate module a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-09 9:27 ` Lee Jones
2026-07-01 3:50 ` [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
6 siblings, 1 reply; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu, Andi Shyti; +Cc: linux-kernel, Ming Yu, linux-i2c
From: Ming Yu <a0282524688@gmail.com>
Wrap the USB transport behind a regmap_bus so that sub-device drivers
talk to the firmware exclusively through nct6694_{read,write}_msg(),
without referencing the underlying transport. This unblocks adding the
upcoming HIF (eSPI) backend without touching any sub-device driver.
Encode the firmware addressing scheme (host control, module id and
16-bit offset) into a single 32-bit regmap register so that struct
nct6694_cmd_header maps cleanly onto regmap_bulk_{read,write}():
bits [31:24] host control
bits [23:16] module id
bits [15:0] offset (low byte = command, high byte = selector)
Add nct6694_write_read_msg() for the few commands (e.g. the I2C
"deliver" command) that send a request and read the reply back in a
single transaction, and convert the I2C transfer path to use it.
The USB backend gains a scratch xfer_buf because nct6694_usb_write_msg()
writes the firmware response back into its payload buffer, which is
incompatible with the const buffer handed to regmap_bus->write. The
per-transport access_lock is removed: regmap already serialises bus
accesses on its own.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
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 | 118 +++++++++++++++++++++----------
include/linux/mfd/nct6694.h | 63 ++++++++++++++---
4 files changed, 134 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 5506a0adf3ec..742fc26e6ff7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1166,6 +1166,7 @@ config MFD_MENF21BMC
config MFD_NCT6694
tristate
select MFD_CORE
+ select REGMAP
help
Core MFD support for the Nuvoton NCT6694 peripheral expander.
This provides the common APIs and shared structures used by all
diff --git a/drivers/mfd/nct6694-usb.c b/drivers/mfd/nct6694-usb.c
index 2289ebfde7fa..8e7f3b07de53 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
@@ -34,10 +37,10 @@ union __packed nct6694_usb_msg {
};
struct nct6694_usb_data {
- struct mutex access_lock;
struct urb *int_in_urb;
struct usb_device *udev;
union nct6694_usb_msg *usb_msg;
+ void *xfer_buf;
__le32 *int_buffer;
};
@@ -101,29 +104,15 @@ static int nct6694_usb_err_handling(struct nct6694 *nct6694, unsigned char err_s
return -EIO;
}
-/**
- * nct6694_usb_read_msg() - Read message from NCT6694 device
- * @nct6694: NCT6694 device pointer
- * @cmd_hd: command header structure
- * @buf: buffer to store the response data
- *
- * Sends a command to the NCT6694 device and reads the response.
- * The command header is specified in @cmd_hd, and the response
- * data is stored in @buf.
- *
- * Return: Negative value on error or 0 on success.
- */
-int nct6694_usb_read_msg(struct nct6694 *nct6694,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+static int nct6694_usb_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *udata = nct6694->priv;
union nct6694_usb_msg *msg = udata->usb_msg;
struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&udata->access_lock);
-
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
@@ -153,30 +142,16 @@ int nct6694_usb_read_msg(struct nct6694 *nct6694,
return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_usb_read_msg);
-/**
- * nct6694_usb_write_msg() - Write message to NCT6694 device
- * @nct6694: NCT6694 device pointer
- * @cmd_hd: command header structure
- * @buf: buffer containing the data to be sent
- *
- * Sends a command to the NCT6694 device and writes the data
- * from @buf. The command header is specified in @cmd_hd.
- *
- * Return: Negative value on error or 0 on success.
- */
-int nct6694_usb_write_msg(struct nct6694 *nct6694,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+static int nct6694_usb_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
struct nct6694_usb_data *udata = nct6694->priv;
union nct6694_usb_msg *msg = udata->usb_msg;
struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&udata->access_lock);
-
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
@@ -212,7 +187,67 @@ int nct6694_usb_write_msg(struct nct6694 *nct6694,
return nct6694_usb_err_handling(nct6694, msg->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 *nct6694 = 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),
+ };
+
+ /*
+ * A SET transaction is a request/response exchange: send the payload
+ * already present in @val_buf and read the response back into it.
+ */
+ if (FIELD_GET(NCT6694_REG_HCTRL, reg) == NCT6694_HCTRL_SET)
+ return nct6694_usb_write_msg(nct6694, &cmd_hd, val_buf);
+
+ return nct6694_usb_read_msg(nct6694, &cmd_hd, val_buf);
+}
+
+static int nct6694_usb_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct nct6694 *nct6694 = context;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ u32 reg = get_unaligned_be32(data);
+ size_t 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(len),
+ };
+
+ if (len > NCT6694_MAX_PACKET_SIZE)
+ return -EINVAL;
+
+ /*
+ * nct6694_usb_write_msg() reads the firmware response back into the
+ * payload buffer, so copy the const regmap data into a scratch buffer
+ * it can write to.
+ */
+ memcpy(udata->xfer_buf, data + sizeof(reg), len);
+
+ return nct6694_usb_write_msg(nct6694, &cmd_hd, udata->xfer_buf);
+}
+
+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,
+ .reg_stride = 1,
+ .max_raw_read = NCT6694_MAX_PACKET_SIZE,
+ .max_raw_write = NCT6694_MAX_PACKET_SIZE,
+};
static void nct6694_usb_int_callback(struct urb *urb)
{
@@ -270,6 +305,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (!udata->usb_msg)
return -ENOMEM;
+ udata->xfer_buf = devm_kzalloc(dev, NCT6694_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!udata->xfer_buf)
+ return -ENOMEM;
+
udata->int_buffer = devm_kzalloc(dev, sizeof(*udata->int_buffer), GFP_KERNEL);
if (!udata->int_buffer)
return -ENOMEM;
@@ -283,9 +322,12 @@ static int nct6694_usb_probe(struct usb_interface *iface,
nct6694->dev = dev;
nct6694->priv = udata;
- ret = devm_mutex_init(dev, &udata->access_lock);
- if (ret)
+ nct6694->regmap = devm_regmap_init(dev, &nct6694_usb_regmap_bus, nct6694,
+ &nct6694_usb_regmap_config);
+ if (IS_ERR(nct6694->regmap)) {
+ ret = PTR_ERR(nct6694->regmap);
goto err_urb;
+ }
interface = iface->cur_altsetting;
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 853b1530755d..d655d726d4de 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -9,7 +9,9 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#include <linux/bitfield.h>
#include <linux/idr.h>
+#include <linux/regmap.h>
#include <linux/spinlock.h>
#include <linux/types.h>
@@ -29,6 +31,9 @@ struct mfd_cell;
#define NCT6694_HCTRL_SET 0x40
#define NCT6694_HCTRL_GET 0x80
+/* Maximum message payload length exchanged with the firmware in one command */
+#define NCT6694_MAX_PACKET_SIZE 0x3F0
+
enum nct6694_irq_id {
NCT6694_IRQ_GPIO0 = 0,
NCT6694_IRQ_GPIO1,
@@ -87,6 +92,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;
@@ -97,29 +103,64 @@ struct nct6694 {
void *priv;
};
-int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
- const struct mfd_cell *cells, int n_cells);
-void nct6694_core_remove(struct nct6694 *nct6694);
+/*
+ * The NCT6694 firmware is reached through messages that carry a module id
+ * together with an offset (or command/selector pair). These fields are packed
+ * into a single 32-bit regmap register so that the sub-device drivers can use
+ * the standard regmap bulk accessors, while each transport driver implements
+ * the actual I/O through its own 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)
-int nct6694_usb_read_msg(struct nct6694 *nct6694,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
-int nct6694_usb_write_msg(struct nct6694 *nct6694,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
+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 *nct6694,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_read_msg(nct6694, cmd_hd, buf);
+ return regmap_bulk_read(nct6694->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 *nct6694,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_write_msg(nct6694, cmd_hd, buf);
+ return regmap_bulk_write(nct6694->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
}
+/*
+ * A few firmware commands (e.g. the I2C "deliver") transmit a request and
+ * read the reply back in a single transaction. Model this as a regmap read of
+ * a SET register: @buf carries the request on entry and holds the reply on
+ * return, so the firmware exchange stays a single message as before.
+ */
+static inline int nct6694_write_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return regmap_bulk_read(nct6694->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
+}
+
+int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
+ const struct mfd_cell *cells, int n_cells);
+void nct6694_core_remove(struct nct6694 *nct6694);
+
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
` (5 preceding siblings ...)
2026-07-01 3:50 ` [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
@ 2026-07-01 3:50 ` a0282524688
2026-07-02 19:08 ` Julian Braha
2026-07-09 9:27 ` Lee Jones
6 siblings, 2 replies; 11+ messages in thread
From: a0282524688 @ 2026-07-01 3:50 UTC (permalink / raw)
To: lee, Ming Yu; +Cc: linux-kernel, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
Add support for the Host Interface (HIF) transport via eSPI for the
Nuvoton NCT6694 peripheral expander.
This transport driver initializes the Super-I/O to configure the
device's shared memory base address and SIRQ. It provides a regmap_bus
implementation that drives the firmware command/response exchange over
the shared-memory window, plus an internal regmap_mmio for the report
region. The initialized device config is then passed to the MFD core
(nct6694_core_probe) for sub-device registration and IRQ domain setup.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
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 | 16 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/nct6694-hif.c | 565 ++++++++++++++++++++++++++++++++++++++
3 files changed, 582 insertions(+)
create mode 100644 drivers/mfd/nct6694-hif.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 742fc26e6ff7..f37acf585671 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1175,6 +1175,22 @@ config MFD_NCT6694
It is selected automatically by the transport interface drivers.
+config MFD_NCT6694_HIF
+ tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
+ depends on HAS_IOPORT && ACPI
+ select MFD_NCT6694
+ select REGMAP_MMIO
+ help
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the Host Interface (HIF) using eSPI transport.
+
+ The transport driver uses Super-I/O mapping and shared memory to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 over an eSPI interface on an ACPI platform.
+
+ 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"
select MFD_NCT6694
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 10c19a19541e..97924dd3552c 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..c47534a1e526
--- /dev/null
+++ b/drivers/mfd/nct6694-hif.c
@@ -0,0 +1,565 @@
+// 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/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/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_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 regmap *rpt_regmap;
+ struct nct6694_sio_data *sio_data;
+ void __iomem *msg_base;
+ void *xfer_buf;
+ unsigned int shm_base;
+};
+
+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 *nct6694, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(nct6694->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(nct6694->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(nct6694->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+static int nct6694_hif_xfer_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl, void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ void __iomem *hdr = hdata->msg_base + offsetof(struct nct6694_hif_msg, cmd_header);
+ struct nct6694_cmd_header cmd = *cmd_hd;
+ struct nct6694_response_header resp;
+ u16 len = le16_to_cpu(cmd.len);
+ u8 status;
+ int ret;
+
+ /* 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 (hctrl == NCT6694_HCTRL_SET && len)
+ memcpy_toio(hdata->msg_base + offsetof(struct nct6694_hif_msg, data),
+ buf, 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, hdata->msg_base + offsetof(struct nct6694_hif_msg, response_header),
+ sizeof(resp));
+
+ ret = nct6694_hif_err_handling(nct6694, resp.sts);
+ if (ret)
+ return ret;
+
+ if (le16_to_cpu(resp.len))
+ memcpy_fromio(buf, hdata->msg_base + offsetof(struct nct6694_hif_msg, data),
+ min(len, le16_to_cpu(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 *nct6694 = context;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ 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_MOD, reg) == NCT6694_RPT_MOD)
+ return regmap_bulk_read(hdata->rpt_regmap,
+ FIELD_GET(NCT6694_REG_OFFSET, reg),
+ val_buf, val_size);
+
+ return nct6694_hif_xfer_msg(nct6694, &cmd_hd,
+ FIELD_GET(NCT6694_REG_HCTRL, reg), val_buf);
+}
+
+static int nct6694_hif_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct nct6694 *nct6694 = context;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u32 reg = get_unaligned_be32(data);
+ size_t 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(len),
+ };
+
+ if (FIELD_GET(NCT6694_REG_MOD, reg) == NCT6694_RPT_MOD)
+ return regmap_bulk_write(hdata->rpt_regmap,
+ FIELD_GET(NCT6694_REG_OFFSET, reg),
+ data + sizeof(reg), len);
+
+ if (len > NCT6694_MAX_PACKET_SIZE)
+ return -EINVAL;
+
+ /*
+ * nct6694_hif_xfer_msg() reads the firmware response back into the
+ * payload buffer, so copy the const regmap data into a scratch buffer
+ * it can write to.
+ */
+ memcpy(hdata->xfer_buf, data + sizeof(reg), len);
+
+ return nct6694_hif_xfer_msg(nct6694, &cmd_hd, NCT6694_HCTRL_SET,
+ hdata->xfer_buf);
+}
+
+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,
+ .reg_stride = 1,
+ .max_raw_read = NCT6694_MAX_PACKET_SIZE,
+ .max_raw_write = NCT6694_MAX_PACKET_SIZE,
+};
+
+static const struct regmap_config nct6694_hif_rpt_regmap_config = {
+ .name = "rpt",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+};
+
+static irqreturn_t nct6694_hif_irq_handler(int irq, void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u8 reg_data[4];
+ u32 intr_status;
+ int ret;
+
+ /* Check interrupt status is set */
+ if (!(inb(hdata->shm_base + NCT6694_SHM_COFS_STS) & NCT6694_SHM_COFS_STS_COFS4W))
+ return IRQ_NONE;
+
+ /* Clear interrupt status */
+ outb(NCT6694_SHM_COFS_STS_COFS4W, hdata->shm_base + NCT6694_SHM_COFS_STS);
+
+ ret = regmap_bulk_read(hdata->rpt_regmap, NCT6694_SHM_INTR_STATUS,
+ reg_data, ARRAY_SIZE(reg_data));
+ if (ret)
+ return IRQ_NONE;
+
+ intr_status = get_unaligned_le32(reg_data);
+
+ while (intr_status) {
+ int hwirq = __ffs(intr_status);
+
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, hwirq));
+ intr_status &= ~BIT(hwirq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void nct6694_hif_irq_disable(void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u8 cofs_ctl2;
+
+ /* Disable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 &= ~NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static void nct6694_hif_irq_enable(struct nct6694 *nct6694)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u8 cofs_ctl2;
+
+ /* Enable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 |= NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static int nct6694_hif_irq_init(struct nct6694 *nct6694, int irq)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ struct nct6694_sio_data *sio_data = hdata->sio_data;
+ int ret;
+
+ /* Set SIRQ number */
+ 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;
+ }
+
+ hdata->shm_base = superio_inw(sio_data, SIO_REG_SHM_BASE_ADDR);
+ superio_outb(sio_data, SIO_REG_SHM_IRQ_NR, irq);
+
+ superio_exit(sio_data);
+
+ if (!devm_request_region(nct6694->dev,
+ hdata->shm_base + NCT6694_SHM_COFS_STS,
+ NCT6694_SHM_COFS_LEN, DRVNAME))
+ return -EBUSY;
+
+ return 0;
+}
+
+static void nct6694_hif_core_remove_action(void *data)
+{
+ struct nct6694 *nct6694 = data;
+
+ nct6694_core_remove(nct6694);
+}
+
+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 *hdata;
+ struct nct6694 *nct6694;
+ void __iomem *rpt_base, *msg_base;
+ 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);
+
+ nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+ if (!nct6694)
+ return -ENOMEM;
+
+ hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL);
+ if (!hdata)
+ return -ENOMEM;
+
+ hdata->xfer_buf = devm_kzalloc(dev, NCT6694_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!hdata->xfer_buf)
+ return -ENOMEM;
+
+ hdata->sio_data = sio_data;
+ hdata->msg_base = msg_base;
+ hdata->rpt_regmap = devm_regmap_init_mmio(dev, rpt_base,
+ &nct6694_hif_rpt_regmap_config);
+ if (IS_ERR(hdata->rpt_regmap))
+ return PTR_ERR(hdata->rpt_regmap);
+
+ nct6694->dev = dev;
+ nct6694->priv = hdata;
+ nct6694->regmap = devm_regmap_init(dev, &nct6694_hif_regmap_bus, nct6694,
+ &nct6694_hif_msg_regmap_config);
+ if (IS_ERR(nct6694->regmap))
+ return PTR_ERR(nct6694->regmap);
+
+ ret = nct6694_hif_irq_init(nct6694, irq);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, nct6694);
+
+ ret = nct6694_core_probe(dev, nct6694, nct6694_hif_devs,
+ ARRAY_SIZE(nct6694_hif_devs));
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_hif_core_remove_action, nct6694);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_hif_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), nct6694);
+ if (ret)
+ return ret;
+
+ nct6694_hif_irq_enable(nct6694);
+
+ return devm_add_action_or_reset(dev, nct6694_hif_irq_disable, nct6694);
+}
+
+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");
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver
2026-07-01 3:50 ` [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
@ 2026-07-02 19:08 ` Julian Braha
2026-07-09 9:27 ` Lee Jones
1 sibling, 0 replies; 11+ messages in thread
From: Julian Braha @ 2026-07-02 19:08 UTC (permalink / raw)
To: a0282524688, lee, Ming Yu; +Cc: linux-kernel
Hi Ming,
On 7/1/26 04:50, a0282524688@gmail.com wrote:
> +++ b/drivers/mfd/Kconfig
> @@ -1175,6 +1175,22 @@ config MFD_NCT6694
>
> It is selected automatically by the transport interface drivers.
>
> +config MFD_NCT6694_HIF
> + tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
> + depends on HAS_IOPORT && ACPI
> + select MFD_NCT6694
> + select REGMAP_MMIO
> + help
> + This enables support for the Nuvoton NCT6694 peripheral expander
> + connected via the Host Interface (HIF) using eSPI transport.
> +
> + The transport driver uses Super-I/O mapping and shared memory to
> + communicate with the NCT6694 firmware. Enable this option if you
> + are using the NCT6694 over an eSPI interface on an ACPI platform.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called nct6694-hif.
> +
MFD_NCT6694_HIF likely should have a dependency on USB, since it's
selecting MFD_NCT6694. Else you may have an unmet dependency.
- Julian Braha
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver
2026-07-01 3:50 ` [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
2026-07-02 19:08 ` Julian Braha
@ 2026-07-09 9:27 ` Lee Jones
1 sibling, 0 replies; 11+ messages in thread
From: Lee Jones @ 2026-07-09 9:27 UTC (permalink / raw)
To: a0282524688; +Cc: Ming Yu, linux-kernel
/* Sashiko Automation: Issues Found (7 Findings) */
Some relevant ones here.
On Wed, 01 Jul 2026, a0282524688@gmail.com wrote:
> From: Ming Yu <a0282524688@gmail.com>
>
> Add support for the Host Interface (HIF) transport via eSPI for the
> Nuvoton NCT6694 peripheral expander.
>
> This transport driver initializes the Super-I/O to configure the
> device's shared memory base address and SIRQ. It provides a regmap_bus
> implementation that drives the firmware command/response exchange over
> the shared-memory window, plus an internal regmap_mmio for the report
> region. The initialized device config is then passed to the MFD core
> (nct6694_core_probe) for sub-device registration and IRQ domain setup.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---
> 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 | 16 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/nct6694-hif.c | 565 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 582 insertions(+)
> create mode 100644 drivers/mfd/nct6694-hif.c
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 742fc26e6ff7..f37acf585671 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1175,6 +1175,22 @@ config MFD_NCT6694
>
> It is selected automatically by the transport interface drivers.
>
> +config MFD_NCT6694_HIF
> + tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
> + depends on HAS_IOPORT && ACPI
> + select MFD_NCT6694
> + select REGMAP_MMIO
> + help
> + This enables support for the Nuvoton NCT6694 peripheral expander
> + connected via the Host Interface (HIF) using eSPI transport.
> +
> + The transport driver uses Super-I/O mapping and shared memory to
> + communicate with the NCT6694 firmware. Enable this option if you
> + are using the NCT6694 over an eSPI interface on an ACPI platform.
> +
> + 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"
> select MFD_NCT6694
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 10c19a19541e..97924dd3552c 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..c47534a1e526
> --- /dev/null
> +++ b/drivers/mfd/nct6694-hif.c
> @@ -0,0 +1,565 @@
> +// 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/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/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_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 regmap *rpt_regmap;
> + struct nct6694_sio_data *sio_data;
> + void __iomem *msg_base;
> + void *xfer_buf;
> + unsigned int shm_base;
> +};
> +
> +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;
> +}
[Severity: High]
If the hardware returns a response length smaller than the requested length,
does this leave the remainder of the caller buffer uninitialized? Can this
lead to an information leak or logic corruption when caller drivers parse
the buffer?
> +
> +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;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + /* 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;
> +}
[Severity: High]
If the hardware returns a response length smaller than the requested length,
does this leave the remainder of the caller buffer uninitialized? Can this
lead to an information leak or logic corruption when caller drivers parse
the buffer?
> +
> +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 *nct6694, unsigned char err_status)
> +{
> + switch (err_status) {
> + case NCT6694_NO_ERROR:
> + return 0;
> + case NCT6694_NOT_SUPPORT_ERROR:
> + dev_err(nct6694->dev, "Command is not supported!\n");
> + break;
> + case NCT6694_NO_RESPONSE_ERROR:
> + dev_warn(nct6694->dev, "Command received no response!\n");
> + break;
> + case NCT6694_TIMEOUT_ERROR:
> + dev_warn(nct6694->dev, "Command timed out!\n");
> + break;
> + case NCT6694_PENDING:
> + dev_err(nct6694->dev, "Command is pending!\n");
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return -EIO;
> +}
> +
> +static int nct6694_hif_xfer_msg(struct nct6694 *nct6694,
> + const struct nct6694_cmd_header *cmd_hd,
> + u8 hctrl, void *buf)
> +{
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + void __iomem *hdr = hdata->msg_base + offsetof(struct nct6694_hif_msg, cmd_header);
> + struct nct6694_cmd_header cmd = *cmd_hd;
> + struct nct6694_response_header resp;
> + u16 len = le16_to_cpu(cmd.len);
> + u8 status;
> + int ret;
> +
> + /* 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;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + /*
> + * 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 (hctrl == NCT6694_HCTRL_SET && len)
> + memcpy_toio(hdata->msg_base + offsetof(struct nct6694_hif_msg, data),
> + buf, 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;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + memcpy_fromio(&resp, hdata->msg_base + offsetof(struct nct6694_hif_msg, response_header),
> + sizeof(resp));
> +
> + ret = nct6694_hif_err_handling(nct6694, resp.sts);
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + if (le16_to_cpu(resp.len))
> + memcpy_fromio(buf, hdata->msg_base + offsetof(struct nct6694_hif_msg, data),
> + min(len, le16_to_cpu(resp.len)));
> +
> + return 0;
> +}
[Severity: High]
If the hardware returns a response length smaller than the requested length,
does this leave the remainder of the caller buffer uninitialized? Can this
lead to an information leak or logic corruption when caller drivers parse
the buffer?
> +
> +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 *nct6694 = context;
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + 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_MOD, reg) == NCT6694_RPT_MOD)
> + return regmap_bulk_read(hdata->rpt_regmap,
> + FIELD_GET(NCT6694_REG_OFFSET, reg),
> + val_buf, val_size);
> +
> + return nct6694_hif_xfer_msg(nct6694, &cmd_hd,
> + FIELD_GET(NCT6694_REG_HCTRL, reg), val_buf);
> +}
> +
> +static int nct6694_hif_regmap_write(void *context, const void *data,
> + size_t count)
> +{
> + struct nct6694 *nct6694 = context;
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + u32 reg = get_unaligned_be32(data);
> + size_t 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(len),
> + };
> +
> + if (FIELD_GET(NCT6694_REG_MOD, reg) == NCT6694_RPT_MOD)
> + return regmap_bulk_write(hdata->rpt_regmap,
> + FIELD_GET(NCT6694_REG_OFFSET, reg),
> + data + sizeof(reg), len);
> +
> + if (len > NCT6694_MAX_PACKET_SIZE)
> + return -EINVAL;
> +
> + /*
> + * nct6694_hif_xfer_msg() reads the firmware response back into the
> + * payload buffer, so copy the const regmap data into a scratch buffer
> + * it can write to.
> + */
> + memcpy(hdata->xfer_buf, data + sizeof(reg), len);
> +
> + return nct6694_hif_xfer_msg(nct6694, &cmd_hd, NCT6694_HCTRL_SET,
> + hdata->xfer_buf);
> +}
> +
> +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,
> + .reg_stride = 1,
> + .max_raw_read = NCT6694_MAX_PACKET_SIZE,
> + .max_raw_write = NCT6694_MAX_PACKET_SIZE,
> +};
> +
> +static const struct regmap_config nct6694_hif_rpt_regmap_config = {
> + .name = "rpt",
> + .reg_bits = 8,
> + .val_bits = 8,
> + .reg_stride = 1,
> +};
> +
> +static irqreturn_t nct6694_hif_irq_handler(int irq, void *data)
> +{
> + struct nct6694 *nct6694 = data;
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + u8 reg_data[4];
> + u32 intr_status;
> + int ret;
> +
> + /* Check interrupt status is set */
> + if (!(inb(hdata->shm_base + NCT6694_SHM_COFS_STS) & NCT6694_SHM_COFS_STS_COFS4W))
> + return IRQ_NONE;
> +
> + /* Clear interrupt status */
> + outb(NCT6694_SHM_COFS_STS_COFS4W, hdata->shm_base + NCT6694_SHM_COFS_STS);
> +
> + ret = regmap_bulk_read(hdata->rpt_regmap, NCT6694_SHM_INTR_STATUS,
> + reg_data, ARRAY_SIZE(reg_data));
> + if (ret)
> + return IRQ_NONE;
[Severity: Medium]
Is it safe to return IRQ_NONE here after the hardware interrupt has already
been successfully cleared via outb()? Could this mislead the kernel's
spurious interrupt detector into permanently disabling the shared IRQ line
if the read fails repeatedly?
> +
> + intr_status = get_unaligned_le32(reg_data);
> +
> + while (intr_status) {
> + int hwirq = __ffs(intr_status);
> +
> + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, hwirq));
[Severity: High]
If an unmapped hardware interrupt or spurious bit is processed,
irq_find_mapping() will return 0. Does passing this directly into
generic_handle_irq_safe(0) risk invoking the system timer handler from this
device's interrupt context?
[Severity: High]
Since child drivers access their registers over a slow eSPI bus, does
dispatching them via generic_handle_irq_safe() cause a sleep-in-atomic bug?
Would handle_nested_irq() be more appropriate for a threaded parent
dispatching to slow child devices?
> + intr_status &= ~BIT(hwirq);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void nct6694_hif_irq_disable(void *data)
> +{
> + struct nct6694 *nct6694 = data;
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + u8 cofs_ctl2;
> +
> + /* Disable SIRQ interrupt */
> + cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
> + cofs_ctl2 &= ~NCT6694_SHM_COFS_CTL2_COFS4W_IE;
> + outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
> +}
> +
> +static void nct6694_hif_irq_enable(struct nct6694 *nct6694)
> +{
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + u8 cofs_ctl2;
> +
> + /* Enable SIRQ interrupt */
> + cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
> + cofs_ctl2 |= NCT6694_SHM_COFS_CTL2_COFS4W_IE;
> + outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
> +}
> +
> +static int nct6694_hif_irq_init(struct nct6694 *nct6694, int irq)
> +{
> + struct nct6694_hif_data *hdata = nct6694->priv;
> + struct nct6694_sio_data *sio_data = hdata->sio_data;
> + int ret;
> +
> + /* Set SIRQ number */
> + ret = superio_enter(sio_data);
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + superio_select(sio_data, SIO_REG_LD_SHM);
> +
> + if (!superio_inb(sio_data, SIO_REG_SHM_ENABLE)) {
> + superio_exit(sio_data);
> + return -EIO;
> + }
> +
> + hdata->shm_base = superio_inw(sio_data, SIO_REG_SHM_BASE_ADDR);
[Severity: High]
What happens if the system BIOS leaves the Logical Device unconfigured and
SIO_REG_SHM_BASE_ADDR reads as 0? Will this cause the driver to incorrectly
map and write to the Super-I/O index port (0x2E) during interrupt handling?
> + superio_outb(sio_data, SIO_REG_SHM_IRQ_NR, irq);
[Severity: High]
Does writing the irq parameter directly into SIO_REG_SHM_IRQ_NR write a Linux
virtual IRQ number (which might be dynamically allocated) into the physical
hardware configuration register?
> +
> + superio_exit(sio_data);
> +
> + if (!devm_request_region(nct6694->dev,
> + hdata->shm_base + NCT6694_SHM_COFS_STS,
> + NCT6694_SHM_COFS_LEN, DRVNAME))
> + return -EBUSY;
> +
> + return 0;
> +}
[Severity: High]
If the hardware returns a response length smaller than the requested length,
does this leave the remainder of the caller buffer uninitialized? Can this
lead to an information leak or logic corruption when caller drivers parse
the buffer?
> +
> +static void nct6694_hif_core_remove_action(void *data)
> +{
> + struct nct6694 *nct6694 = data;
> +
> + nct6694_core_remove(nct6694);
> +}
> +
> +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 *hdata;
> + struct nct6694 *nct6694;
> + void __iomem *rpt_base, *msg_base;
> + 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);
> +
> + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> + if (!nct6694)
> + return -ENOMEM;
> +
> + hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL);
> + if (!hdata)
> + return -ENOMEM;
> +
> + hdata->xfer_buf = devm_kzalloc(dev, NCT6694_MAX_PACKET_SIZE, GFP_KERNEL);
> + if (!hdata->xfer_buf)
> + return -ENOMEM;
> +
> + hdata->sio_data = sio_data;
> + hdata->msg_base = msg_base;
> + hdata->rpt_regmap = devm_regmap_init_mmio(dev, rpt_base,
> + &nct6694_hif_rpt_regmap_config);
> + if (IS_ERR(hdata->rpt_regmap))
> + return PTR_ERR(hdata->rpt_regmap);
> +
> + nct6694->dev = dev;
> + nct6694->priv = hdata;
> + nct6694->regmap = devm_regmap_init(dev, &nct6694_hif_regmap_bus, nct6694,
> + &nct6694_hif_msg_regmap_config);
> + if (IS_ERR(nct6694->regmap))
> + return PTR_ERR(nct6694->regmap);
> +
> + ret = nct6694_hif_irq_init(nct6694, irq);
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + platform_set_drvdata(pdev, nct6694);
> +
> + ret = nct6694_core_probe(dev, nct6694, nct6694_hif_devs,
> + ARRAY_SIZE(nct6694_hif_devs));
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + ret = devm_add_action_or_reset(dev, nct6694_hif_core_remove_action, nct6694);
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_hif_irq_handler,
> + IRQF_ONESHOT | IRQF_SHARED,
> + dev_name(dev), nct6694);
> + if (ret)
> + return ret;
[Severity: High]
Because nct6694_core_probe() is called before the threaded IRQ is requested,
can child devices that rely on interrupts during initialization hang or fail?
Furthermore, due to LIFO devres unwinding, will the parent IRQ be freed before
child devices are removed on driver unload?
> +
> + nct6694_hif_irq_enable(nct6694);
> +
> + return devm_add_action_or_reset(dev, nct6694_hif_irq_disable, nct6694);
> +}
> +
> +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");
> --
> 2.34.1
>
--
Lee Jones
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction
2026-07-01 3:50 ` [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
@ 2026-07-09 9:27 ` Lee Jones
0 siblings, 0 replies; 11+ messages in thread
From: Lee Jones @ 2026-07-09 9:27 UTC (permalink / raw)
To: a0282524688; +Cc: Ming Yu, Andi Shyti, linux-kernel, linux-i2c
/* Sashiko Automation: Issues Found (4 Findings) */
Some relevant ones I'd like you to look at please.
On Wed, 01 Jul 2026, a0282524688@gmail.com wrote:
> From: Ming Yu <a0282524688@gmail.com>
>
> Wrap the USB transport behind a regmap_bus so that sub-device drivers
> talk to the firmware exclusively through nct6694_{read,write}_msg(),
> without referencing the underlying transport. This unblocks adding the
> upcoming HIF (eSPI) backend without touching any sub-device driver.
>
> Encode the firmware addressing scheme (host control, module id and
> 16-bit offset) into a single 32-bit regmap register so that struct
> nct6694_cmd_header maps cleanly onto regmap_bulk_{read,write}():
>
> bits [31:24] host control
> bits [23:16] module id
> bits [15:0] offset (low byte = command, high byte = selector)
>
> Add nct6694_write_read_msg() for the few commands (e.g. the I2C
> "deliver" command) that send a request and read the reply back in a
> single transaction, and convert the I2C transfer path to use it.
>
> The USB backend gains a scratch xfer_buf because nct6694_usb_write_msg()
> writes the firmware response back into its payload buffer, which is
> incompatible with the const buffer handed to regmap_bus->write. The
> per-transport access_lock is removed: regmap already serialises bus
> accesses on its own.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---
> 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 | 118 +++++++++++++++++++++----------
> include/linux/mfd/nct6694.h | 63 ++++++++++++++---
> 4 files changed, 134 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);
[Severity: High]
This is a pre-existing issue, but does passing the embedded deliver struct
(from struct nct6694_i2c_data) as the data buffer for I2C read/write
transactions risk memory corruption on non-cache-coherent architectures?
The USB transport calls usb_bulk_msg() in nct6694_usb_read_msg(), which maps
this unaligned buffer for DMA. Because deliver is embedded adjacent to other
fields like the i2c_adapter, it shares cachelines with them.
DMA invalidations during the I2C read phase could silently overwrite concurrent
CPU writes to adjacent fields, such as sysfs statistics in the i2c_adapter.
> if (ret < 0)
> return ret;
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 5506a0adf3ec..742fc26e6ff7 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1166,6 +1166,7 @@ config MFD_MENF21BMC
> config MFD_NCT6694
> tristate
> select MFD_CORE
> + select REGMAP
> help
> Core MFD support for the Nuvoton NCT6694 peripheral expander.
> This provides the common APIs and shared structures used by all
> diff --git a/drivers/mfd/nct6694-usb.c b/drivers/mfd/nct6694-usb.c
> index 2289ebfde7fa..8e7f3b07de53 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
> @@ -34,10 +37,10 @@ union __packed nct6694_usb_msg {
> };
>
> struct nct6694_usb_data {
> - struct mutex access_lock;
> struct urb *int_in_urb;
> struct usb_device *udev;
> union nct6694_usb_msg *usb_msg;
> + void *xfer_buf;
> __le32 *int_buffer;
> };
>
> @@ -101,29 +104,15 @@ static int nct6694_usb_err_handling(struct nct6694 *nct6694, unsigned char err_s
> return -EIO;
> }
>
> -/**
> - * nct6694_usb_read_msg() - Read message from NCT6694 device
> - * @nct6694: NCT6694 device pointer
> - * @cmd_hd: command header structure
> - * @buf: buffer to store the response data
> - *
> - * Sends a command to the NCT6694 device and reads the response.
> - * The command header is specified in @cmd_hd, and the response
> - * data is stored in @buf.
> - *
> - * Return: Negative value on error or 0 on success.
> - */
> -int nct6694_usb_read_msg(struct nct6694 *nct6694,
> - const struct nct6694_cmd_header *cmd_hd,
> - void *buf)
> +static int nct6694_usb_read_msg(struct nct6694 *nct6694,
> + const struct nct6694_cmd_header *cmd_hd,
> + void *buf)
> {
> struct nct6694_usb_data *udata = nct6694->priv;
> union nct6694_usb_msg *msg = udata->usb_msg;
> struct usb_device *udev = udata->udev;
> int tx_len, rx_len, ret;
>
> - guard(mutex)(&udata->access_lock);
> -
> memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
>
> @@ -153,30 +142,16 @@ int nct6694_usb_read_msg(struct nct6694 *nct6694,
>
> return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
> }
> -EXPORT_SYMBOL_GPL(nct6694_usb_read_msg);
>
> -/**
> - * nct6694_usb_write_msg() - Write message to NCT6694 device
> - * @nct6694: NCT6694 device pointer
> - * @cmd_hd: command header structure
> - * @buf: buffer containing the data to be sent
> - *
> - * Sends a command to the NCT6694 device and writes the data
> - * from @buf. The command header is specified in @cmd_hd.
> - *
> - * Return: Negative value on error or 0 on success.
> - */
> -int nct6694_usb_write_msg(struct nct6694 *nct6694,
> - const struct nct6694_cmd_header *cmd_hd,
> - void *buf)
> +static int nct6694_usb_write_msg(struct nct6694 *nct6694,
> + const struct nct6694_cmd_header *cmd_hd,
> + void *buf)
> {
> struct nct6694_usb_data *udata = nct6694->priv;
> union nct6694_usb_msg *msg = udata->usb_msg;
> struct usb_device *udev = udata->udev;
> int tx_len, rx_len, ret;
>
> - guard(mutex)(&udata->access_lock);
> -
> memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
>
> @@ -212,7 +187,67 @@ int nct6694_usb_write_msg(struct nct6694 *nct6694,
>
> return nct6694_usb_err_handling(nct6694, msg->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 *nct6694 = 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),
> + };
> +
> + /*
> + * A SET transaction is a request/response exchange: send the payload
> + * already present in @val_buf and read the response back into it.
> + */
> + if (FIELD_GET(NCT6694_REG_HCTRL, reg) == NCT6694_HCTRL_SET)
> + return nct6694_usb_write_msg(nct6694, &cmd_hd, val_buf);
> +
> + return nct6694_usb_read_msg(nct6694, &cmd_hd, val_buf);
> +}
> +
> +static int nct6694_usb_regmap_write(void *context, const void *data,
> + size_t count)
> +{
> + struct nct6694 *nct6694 = context;
> + struct nct6694_usb_data *udata = nct6694->priv;
> + u32 reg = get_unaligned_be32(data);
> + size_t 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(len),
> + };
> +
> + if (len > NCT6694_MAX_PACKET_SIZE)
> + return -EINVAL;
> +
> + /*
> + * nct6694_usb_write_msg() reads the firmware response back into the
> + * payload buffer, so copy the const regmap data into a scratch buffer
> + * it can write to.
> + */
> + memcpy(udata->xfer_buf, data + sizeof(reg), len);
> +
> + return nct6694_usb_write_msg(nct6694, &cmd_hd, udata->xfer_buf);
> +}
> +
> +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,
> + .reg_stride = 1,
> + .max_raw_read = NCT6694_MAX_PACKET_SIZE,
> + .max_raw_write = NCT6694_MAX_PACKET_SIZE,
> +};
>
> static void nct6694_usb_int_callback(struct urb *urb)
> {
> @@ -270,6 +305,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
> if (!udata->usb_msg)
> return -ENOMEM;
>
> + udata->xfer_buf = devm_kzalloc(dev, NCT6694_MAX_PACKET_SIZE, GFP_KERNEL);
> + if (!udata->xfer_buf)
> + return -ENOMEM;
> +
> udata->int_buffer = devm_kzalloc(dev, sizeof(*udata->int_buffer), GFP_KERNEL);
> if (!udata->int_buffer)
> return -ENOMEM;
> @@ -283,9 +322,12 @@ static int nct6694_usb_probe(struct usb_interface *iface,
> nct6694->dev = dev;
> nct6694->priv = udata;
>
> - ret = devm_mutex_init(dev, &udata->access_lock);
> - if (ret)
> + nct6694->regmap = devm_regmap_init(dev, &nct6694_usb_regmap_bus, nct6694,
> + &nct6694_usb_regmap_config);
> + if (IS_ERR(nct6694->regmap)) {
> + ret = PTR_ERR(nct6694->regmap);
> goto err_urb;
> + }
>
> interface = iface->cur_altsetting;
>
[Severity: High]
This isn't a bug introduced by this patch, but could an out-of-bounds read
occur if a malicious USB device presents an interface with zero endpoints?
In nct6694_usb_probe() just below this diff, the code unconditionally accesses
the first endpoint:
int_endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(int_endpoint)) {
If the device provides 0 endpoints, the endpoint array is empty, leading to an
out-of-bounds read of kernel memory.
[Severity: High]
Could an early URB completion allow privilege escalation?
In nct6694_usb_probe(), just below this diff, the interrupt URB is submitted
before nct6694_core_probe() is called:
ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
...
ret = nct6694_core_probe(dev, nct6694, nct6694_usb_devs, ...);
A malicious or fast USB device can complete the URB immediately, triggering
nct6694_usb_int_callback() while nct6694->domain (created in the core probe)
is still NULL.
If the callback runs with a NULL domain:
generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
It calls irq_find_mapping(NULL, irq), which causes the kernel to fall back to
the irq_default_domain. This allows an attacker to supply crafted hardware IRQ
numbers in the payload, spoofing arbitrary system interrupts (like timers or
keyboards).
> diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> index 853b1530755d..d655d726d4de 100644
> --- a/include/linux/mfd/nct6694.h
> +++ b/include/linux/mfd/nct6694.h
> @@ -9,7 +9,9 @@
> #ifndef __MFD_NCT6694_H
> #define __MFD_NCT6694_H
>
> +#include <linux/bitfield.h>
> #include <linux/idr.h>
> +#include <linux/regmap.h>
> #include <linux/spinlock.h>
> #include <linux/types.h>
>
> @@ -29,6 +31,9 @@ struct mfd_cell;
> #define NCT6694_HCTRL_SET 0x40
> #define NCT6694_HCTRL_GET 0x80
>
> +/* Maximum message payload length exchanged with the firmware in one command */
> +#define NCT6694_MAX_PACKET_SIZE 0x3F0
> +
> enum nct6694_irq_id {
> NCT6694_IRQ_GPIO0 = 0,
> NCT6694_IRQ_GPIO1,
> @@ -87,6 +92,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;
> @@ -97,29 +103,64 @@ struct nct6694 {
> void *priv;
> };
>
> -int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
> - const struct mfd_cell *cells, int n_cells);
> -void nct6694_core_remove(struct nct6694 *nct6694);
> +/*
> + * The NCT6694 firmware is reached through messages that carry a module id
> + * together with an offset (or command/selector pair). These fields are packed
> + * into a single 32-bit regmap register so that the sub-device drivers can use
> + * the standard regmap bulk accessors, while each transport driver implements
> + * the actual I/O through its own 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)
>
> -int nct6694_usb_read_msg(struct nct6694 *nct6694,
> - const struct nct6694_cmd_header *cmd_hd,
> - void *buf);
> -int nct6694_usb_write_msg(struct nct6694 *nct6694,
> - const struct nct6694_cmd_header *cmd_hd,
> - void *buf);
> +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 *nct6694,
> const struct nct6694_cmd_header *cmd_hd,
> void *buf)
> {
> - return nct6694_usb_read_msg(nct6694, cmd_hd, buf);
> + return regmap_bulk_read(nct6694->regmap,
> + nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_GET),
> + buf, le16_to_cpu(cmd_hd->len));
> }
[Severity: Medium]
Are we misusing the regmap API by passing an input payload through
regmap_bulk_read() in nct6694_write_read_msg()?
The driver handles command exchanges by modeling them as a regmap_bulk_read()
of a SET register, passing the request payload inside the buf parameter.
However, the regmap API strictly defines this as an output buffer.
This currently works because regmap bypasses bounce buffers and cache for this
specific configuration. Any future changes in the regmap subsystem, such as
initializing output buffers, enabling a regmap cache, or adding bounce buffers
for DMA alignment, will silently discard the outgoing request payload.
>
> static inline int nct6694_write_msg(struct nct6694 *nct6694,
> const struct nct6694_cmd_header *cmd_hd,
> void *buf)
> {
> - return nct6694_usb_write_msg(nct6694, cmd_hd, buf);
> + return regmap_bulk_write(nct6694->regmap,
> + nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
> + buf, le16_to_cpu(cmd_hd->len));
> }
[Severity: Medium]
Are we misusing the regmap API by passing an input payload through
regmap_bulk_read() in nct6694_write_read_msg()?
The driver handles command exchanges by modeling them as a regmap_bulk_read()
of a SET register, passing the request payload inside the buf parameter.
However, the regmap API strictly defines this as an output buffer.
This currently works because regmap bypasses bounce buffers and cache for this
specific configuration. Any future changes in the regmap subsystem, such as
initializing output buffers, enabling a regmap cache, or adding bounce buffers
for DMA alignment, will silently discard the outgoing request payload.
>
> +/*
> + * A few firmware commands (e.g. the I2C "deliver") transmit a request and
> + * read the reply back in a single transaction. Model this as a regmap read of
> + * a SET register: @buf carries the request on entry and holds the reply on
> + * return, so the firmware exchange stays a single message as before.
> + */
> +static inline int nct6694_write_read_msg(struct nct6694 *nct6694,
> + const struct nct6694_cmd_header *cmd_hd,
> + void *buf)
> +{
> + return regmap_bulk_read(nct6694->regmap,
> + nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
> + buf, le16_to_cpu(cmd_hd->len));
> +}
[Severity: Medium]
Are we misusing the regmap API by passing an input payload through
regmap_bulk_read() in nct6694_write_read_msg()?
The driver handles command exchanges by modeling them as a regmap_bulk_read()
of a SET register, passing the request payload inside the buf parameter.
However, the regmap API strictly defines this as an output buffer.
This currently works because regmap bypasses bounce buffers and cache for this
specific configuration. Any future changes in the regmap subsystem, such as
initializing output buffers, enabling a regmap cache, or adding bounce buffers
for DMA alignment, will silently discard the outgoing request payload.
> +
> +int nct6694_core_probe(struct device *dev, struct nct6694 *nct6694,
> + const struct mfd_cell *cells, int n_cells);
> +void nct6694_core_remove(struct nct6694 *nct6694);
> +
> #endif
> --
> 2.34.1
>
--
Lee Jones
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-09 9:28 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-01 3:50 [PATCH v6 0/7] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support a0282524688
2026-07-01 3:50 ` [PATCH v6 1/7] mfd: nct6694: Move module type macros to shared header a0282524688
2026-07-01 3:50 ` [PATCH v6 2/7] mfd: nct6694: Refactor USB-specific data into nct6694_usb_data a0282524688
2026-07-01 3:50 ` [PATCH v6 3/7] mfd: nct6694: Rename USB transport functions with _usb_ prefix a0282524688
2026-07-01 3:50 ` [PATCH v6 4/7] mfd: nct6694: Rename driver to nct6694-usb and update Kconfig a0282524688
2026-07-01 3:50 ` [PATCH v6 5/7] mfd: nct6694: Extract core device management into a separate module a0282524688
2026-07-01 3:50 ` [PATCH v6 6/7] mfd: nct6694: Introduce regmap-based transport abstraction a0282524688
2026-07-09 9:27 ` Lee Jones
2026-07-01 3:50 ` [PATCH v6 7/7] mfd: nct6694: Add Host Interface (HIF) eSPI transport driver a0282524688
2026-07-02 19:08 ` Julian Braha
2026-07-09 9:27 ` Lee Jones
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox