[PATCH v9 10/12] mfd: nct6694: Introduce regmap-based transport abstraction

From: a0282524688

Date: Tue Oct 13 2026 - 02:36:23 EST


From: Ming Yu <a0282524688@xxxxxxxxx>

Sub-device drivers call into the USB transport directly, so a second
transport cannot be added without touching all of them.

Wrap the transport behind a regmap bus. The command header is mapped
onto a single 32-bit register by packing the host control byte, the
module id and the 16-bit offset, so that the sub-device drivers can use
the regmap bulk accessors. The access_lock is dropped as regmap already
serialises the bus accesses. Transfers larger than the transport buffer
are rejected, as the length field of the command header is 16 bits wide.

Add nct6694_write_read_msg() for the commands that send a request and
read the reply back in the same firmware message, and use it for the
I2C deliver command.

Acked-by: Andi Shyti <andi.shyti@xxxxxxxxxx>
Signed-off-by: Ming Yu <a0282524688@xxxxxxxxx>
---
Changes in v9:
- Rejected transfers larger than NCT6694_MAX_DATA_LEN in the regmap bus
callbacks, as the length field of the command header is 16 bits wide.
- Added Andi's Acked-by.

Changes in v8:
- Renamed the transport buffer arguments to tx_buf/rx_buf.
- Dropped max_raw_read/max_raw_write from the regmap_config, as they
are ignored when a regmap_bus is provided.

Changes in v7:
- Made the USB transport helpers static and gave nct6694_usb_write_msg()
separate @tx/@rx buffers, so a SET command only copies the firmware
reply back when the caller asks for it.
- Documented in the shared header why nct6694_write_read_msg() is
expressed as a read of a SET register.

Changes in v6:
- New patch. Replaces the v5 function-pointer abstraction with a
regmap_bus based transport: the firmware command header is packed into
a single 32-bit regmap register and sub-device drivers use the regmap
bulk accessors. Adds nct6694_write_read_msg() for request/response
commands and drops the per-transport access_lock (regmap already
serialises bus accesses).

drivers/i2c/busses/i2c-nct6694.c | 2 +-
drivers/mfd/Kconfig | 1 +
drivers/mfd/nct6694-usb.c | 108 +++++++++++++++++++------------
include/linux/mfd/nct6694.h | 53 ++++++++++++---
4 files changed, 114 insertions(+), 50 deletions(-)

diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index ef3329f34246..7e32dab6e759 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -77,7 +77,7 @@ static int nct6694_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int
deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
if (msg_temp->flags & I2C_M_RD) {
deliver->r_cnt = msg_temp->len;
- ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+ ret = nct6694_write_read_msg(data->nct6694, &cmd_hd, deliver);
if (ret < 0)
return ret;

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e4fd4572472f..a0d9d6ccfb0c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1166,6 +1166,7 @@ config MFD_MENF21BMC
config MFD_NCT6694
tristate "Nuvoton NCT6694 support"
select MFD_CORE
+ select REGMAP
depends on USB
help
This enables support for the Nuvoton USB device NCT6694, which shares
diff --git a/drivers/mfd/nct6694-usb.c b/drivers/mfd/nct6694-usb.c
index 455eeed62dce..c2443cd1278a 100644
--- a/drivers/mfd/nct6694-usb.c
+++ b/drivers/mfd/nct6694-usb.c
@@ -9,6 +9,7 @@
* CAN, WDT, HWMON and RTC management.
*/

+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -17,7 +18,9 @@
#include <linux/mfd/core.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/unaligned.h>
#include <linux/usb.h>

#define NCT6694_VENDOR_ID 0x0416
@@ -35,7 +38,6 @@ union __packed nct6694_usb_hdr {

struct nct6694_usb_data {
struct nct6694 core;
- struct mutex access_lock;
struct usb_device *usb_dev;
struct urb *int_urb;
union nct6694_usb_hdr *hdr_buf;
@@ -108,21 +110,9 @@ static int nct6694_usb_err_handling(struct nct6694 *ddata, unsigned char err_sta
return -EIO;
}

-/**
- * nct6694_usb_read_msg() - Read message from NCT6694 device
- * @ddata: NCT6694 device pointer
- * @cmd_hd: command header structure
- * @buf: buffer to store the response data
- *
- * Sends a command to the NCT6694 device and reads the response.
- * The command header is specified in @cmd_hd, and the response
- * data is stored in @buf.
- *
- * Return: Negative value on error or 0 on success.
- */
-int nct6694_usb_read_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+static int nct6694_usb_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *rx_buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -133,8 +123,6 @@ int nct6694_usb_read_msg(struct nct6694 *ddata,
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;

- guard(mutex)(&usb_data->access_lock);
-
memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
hdr->cmd_header.hctrl = NCT6694_HCTRL_GET;

@@ -168,26 +156,15 @@ int nct6694_usb_read_msg(struct nct6694 *ddata,
return -EIO;
}

- memcpy(buf, usb_data->data_buf, data_len);
+ memcpy(rx_buf, usb_data->data_buf, data_len);

return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_usb_read_msg);

-/**
- * nct6694_usb_write_msg() - Write message to NCT6694 device
- * @ddata: NCT6694 device pointer
- * @cmd_hd: command header structure
- * @buf: buffer containing the data to be sent
- *
- * Sends a command to the NCT6694 device and writes the data
- * from @buf. The command header is specified in @cmd_hd.
- *
- * Return: Negative value on error or 0 on success.
- */
-int nct6694_usb_write_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf)
+/* @rx_buf may be NULL when the reply to the SET command is not needed */
+static int nct6694_usb_write_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ const void *tx_buf, void *rx_buf)
{
struct nct6694_usb_data *usb_data = to_nct6694_usb_data(ddata);
union nct6694_usb_hdr *hdr = usb_data->hdr_buf;
@@ -198,11 +175,9 @@ int nct6694_usb_write_msg(struct nct6694 *ddata,
if (data_len > NCT6694_MAX_DATA_LEN)
return -EINVAL;

- guard(mutex)(&usb_data->access_lock);
-
memcpy(&hdr->cmd_header, cmd_hd, sizeof(*cmd_hd));
hdr->cmd_header.hctrl = NCT6694_HCTRL_SET;
- memcpy(usb_data->data_buf, buf, data_len);
+ memcpy(usb_data->data_buf, tx_buf, data_len);

/* Send command packet to USB device */
ret = usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, NCT6694_BULK_OUT_EP),
@@ -240,11 +215,59 @@ int nct6694_usb_write_msg(struct nct6694 *ddata,
return -EIO;
}

- memcpy(buf, usb_data->data_buf, data_len);
+ if (rx_buf)
+ memcpy(rx_buf, usb_data->data_buf, data_len);

return nct6694_usb_err_handling(ddata, hdr->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_usb_write_msg);
+
+static int nct6694_usb_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct nct6694 *ddata = context;
+ u32 reg = get_unaligned_be32(reg_buf);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(val_size),
+ };
+
+ if (val_size > NCT6694_MAX_DATA_LEN)
+ return -EINVAL;
+
+ if (FIELD_GET(NCT6694_REG_HCTRL, reg) == NCT6694_HCTRL_SET)
+ return nct6694_usb_write_msg(ddata, &cmd_hd, val_buf, val_buf);
+
+ return nct6694_usb_read_msg(ddata, &cmd_hd, val_buf);
+}
+
+static int nct6694_usb_regmap_write(void *context, const void *data,
+ size_t count)
+{
+ struct nct6694 *ddata = context;
+ u32 reg = get_unaligned_be32(data);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = FIELD_GET(NCT6694_REG_MOD, reg),
+ .offset = cpu_to_le16(FIELD_GET(NCT6694_REG_OFFSET, reg)),
+ .len = cpu_to_le16(count - sizeof(reg)),
+ };
+
+ if (count - sizeof(reg) > NCT6694_MAX_DATA_LEN)
+ return -EINVAL;
+
+ return nct6694_usb_write_msg(ddata, &cmd_hd, data + sizeof(reg), NULL);
+}
+
+static const struct regmap_bus nct6694_usb_regmap_bus = {
+ .read = nct6694_usb_regmap_read,
+ .write = nct6694_usb_regmap_write,
+};
+
+static const struct regmap_config nct6694_usb_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 8,
+};

static void nct6694_usb_int_callback(struct urb *urb)
{
@@ -329,9 +352,12 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ddata->dev = dev;
usb_data->usb_dev = usb_dev;

- ret = devm_mutex_init(dev, &usb_data->access_lock);
- if (ret)
+ ddata->regmap = devm_regmap_init(dev, &nct6694_usb_regmap_bus, ddata,
+ &nct6694_usb_regmap_config);
+ if (IS_ERR(ddata->regmap)) {
+ ret = PTR_ERR(ddata->regmap);
goto err_free_urb;
+ }

int_pipe = usb_rcvintpipe(usb_dev, NCT6694_INT_IN_EP);
usb_fill_int_urb(usb_data->int_urb, usb_dev, int_pipe,
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 03296132ad34..1593806d34fd 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -9,6 +9,9 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H

+#include <linux/bitfield.h>
+#include <linux/regmap.h>
+
#define NCT6694_HWMON_MOD 0x00
#define NCT6694_PWM_MOD 0x01
#define NCT6694_I2C_MOD 0x03
@@ -82,6 +85,7 @@ struct __packed nct6694_response_header {

struct nct6694 {
struct device *dev;
+ struct regmap *regmap;
struct ida gpio_ida;
struct ida i2c_ida;
struct ida canfd_ida;
@@ -94,25 +98,58 @@ struct nct6694 {
int nct6694_device_init(struct device *dev);
void nct6694_device_exit(struct device *dev);

-int nct6694_usb_read_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
-int nct6694_usb_write_msg(struct nct6694 *ddata,
- const struct nct6694_cmd_header *cmd_hd,
- void *buf);
+/*
+ * Each firmware command is mapped to a single 32-bit regmap register, so that
+ * the sub-device drivers use the regmap bulk accessors and each transport only
+ * implements a regmap bus:
+ *
+ * bits [31:24] host control (NCT6694_HCTRL_GET / NCT6694_HCTRL_SET)
+ * bits [23:16] module id
+ * bits [15:0] offset (low byte = command, high byte = selector)
+ */
+#define NCT6694_REG_HCTRL GENMASK(31, 24)
+#define NCT6694_REG_MOD GENMASK(23, 16)
+#define NCT6694_REG_OFFSET GENMASK(15, 0)
+
+static inline u32 nct6694_cmd_to_reg(const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl)
+{
+ return FIELD_PREP(NCT6694_REG_HCTRL, hctrl) |
+ FIELD_PREP(NCT6694_REG_MOD, cmd_hd->mod) |
+ FIELD_PREP(NCT6694_REG_OFFSET, le16_to_cpu(cmd_hd->offset));
+}

static inline int nct6694_read_msg(struct nct6694 *ddata,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_read_msg(ddata, cmd_hd, buf);
+ return regmap_bulk_read(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_GET),
+ buf, le16_to_cpu(cmd_hd->len));
}

static inline int nct6694_write_msg(struct nct6694 *ddata,
const struct nct6694_cmd_header *cmd_hd,
void *buf)
{
- return nct6694_usb_write_msg(ddata, cmd_hd, buf);
+ return regmap_bulk_write(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
+}
+
+/*
+ * Some commands, such as the I2C deliver, send a request and read the reply
+ * back in the same firmware message. Express this as a read of a SET register:
+ * @buf holds the request on entry and the reply on return. This relies on the
+ * regmap being uncached and byte sized, so that @buf reaches the bus as is.
+ */
+static inline int nct6694_write_read_msg(struct nct6694 *ddata,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return regmap_bulk_read(ddata->regmap,
+ nct6694_cmd_to_reg(cmd_hd, NCT6694_HCTRL_SET),
+ buf, le16_to_cpu(cmd_hd->len));
}

#endif
--
2.34.1