aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS12
-rw-r--r--drivers/gpio/Kconfig12
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-nct6694.c499
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/nct6694-hwmon.c949
-rw-r--r--drivers/i2c/busses/Kconfig10
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-nct6694.c196
-rw-r--r--drivers/mfd/Kconfig15
-rw-r--r--drivers/mfd/Makefile2
-rw-r--r--drivers/mfd/nct6694.c388
-rw-r--r--drivers/net/can/usb/Kconfig11
-rw-r--r--drivers/net/can/usb/Makefile1
-rw-r--r--drivers/net/can/usb/nct6694_canfd.c832
-rw-r--r--drivers/rtc/Kconfig10
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-nct6694.c297
-rw-r--r--drivers/watchdog/Kconfig11
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/nct6694_wdt.c307
-rw-r--r--include/linux/mfd/nct6694.h102
23 files changed, 3669 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index cd7ff55b5d3217..27ef50c05454d3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18111,6 +18111,18 @@ F: drivers/nubus/
F: include/linux/nubus.h
F: include/uapi/linux/nubus.h
+NUVOTON NCT6694 MFD DRIVER
+M: Ming Yu <tmyu0@nuvoton.com>
+S: Supported
+F: drivers/gpio/gpio-nct6694.c
+F: drivers/hwmon/nct6694-hwmon.c
+F: drivers/i2c/busses/i2c-nct6694.c
+F: drivers/mfd/nct6694.c
+F: drivers/net/can/usb/nct6694_canfd.c
+F: drivers/rtc/rtc-nct6694.c
+F: drivers/watchdog/nct6694_wdt.c
+F: include/linux/mfd/nct6694.h
+
NUVOTON NCT7201 IIO DRIVER
M: Eason Yang <j2anfernee@gmail.com>
L: linux-iio@vger.kernel.org
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 09cb144f076661..c4dc0b2667b399 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1527,6 +1527,18 @@ config GPIO_MAX77759
This driver can also be built as a module. If so, the module will be
called gpio-max77759.
+config GPIO_NCT6694
+ tristate "Nuvoton NCT6694 GPIO controller support"
+ depends on MFD_NCT6694
+ select GENERIC_IRQ_CHIP
+ select GPIOLIB_IRQCHIP
+ help
+ This driver supports 8 GPIO pins per bank that can all be interrupt
+ sources.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-nct6694.
+
config GPIO_PALMAS
tristate "TI PALMAS series PMICs GPIO"
depends on MFD_PALMAS
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 379f55e9ed1e69..f3e837fccdd2c5 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -128,6 +128,7 @@ obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o
obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
+obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o
obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o
obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 00000000000000..a8607f0d99153a
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 GPIO controller.
+ * This defines the module type used for communication with the NCT6694
+ * GPIO controller over the USB interface.
+ */
+#define NCT6694_GPIO_MOD 0xFF
+
+#define NCT6694_GPIO_VER 0x90
+#define NCT6694_GPIO_VALID 0x110
+#define NCT6694_GPI_DATA 0x120
+#define NCT6694_GPO_DIR 0x170
+#define NCT6694_GPO_TYPE 0x180
+#define NCT6694_GPO_DATA 0x190
+
+#define NCT6694_GPI_STS 0x130
+#define NCT6694_GPI_CLR 0x140
+#define NCT6694_GPI_FALLING 0x150
+#define NCT6694_GPI_RISING 0x160
+
+#define NCT6694_NR_GPIO 8
+
+struct nct6694_gpio_data {
+ struct nct6694 *nct6694;
+ struct gpio_chip gpio;
+ struct mutex lock;
+ /* Protect irq operation */
+ struct mutex irq_lock;
+
+ unsigned char reg_val;
+ unsigned char irq_trig_falling;
+ unsigned char irq_trig_rising;
+
+ /* Current gpio group */
+ unsigned char group;
+ int irq;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !(BIT(offset) & data->reg_val);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ data->reg_val &= ~BIT(offset);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+ unsigned int offset, int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ /* Set direction to output */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ data->reg_val |= BIT(offset);
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ /* Then set output level */
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ data->reg_val |= BIT(offset);
+ else
+ data->reg_val &= ~BIT(offset);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ if (BIT(offset) & data->reg_val) {
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->reg_val);
+ }
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->reg_val);
+}
+
+static int nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+ int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DATA + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ data->reg_val |= BIT(offset);
+ else
+ data->reg_val &= ~BIT(offset);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+ unsigned long config)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_TYPE + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ data->reg_val |= BIT(offset);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ data->reg_val &= ~BIT(offset);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_init_valid_mask(struct gpio_chip *gpio,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPIO_VALID + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ *valid_mask = data->reg_val;
+
+ return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+ struct nct6694_gpio_data *data = priv;
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_STS + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ unsigned char status;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret)
+ return IRQ_NONE;
+
+ status = data->reg_val;
+
+ while (status) {
+ int bit = __ffs(status);
+
+ data->reg_val = BIT(bit);
+ handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+ status &= ~BIT(bit);
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_CLR + data->group);
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+ if (ret)
+ return ret;
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+ return nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+}
+
+static void nct6694_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_disable_irq(gpio, hwirq);
+}
+
+static void nct6694_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gpio, hwirq);
+}
+
+static int nct6694_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ guard(mutex)(&data->lock);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ data->irq_trig_rising |= BIT(hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ data->irq_trig_falling |= BIT(hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ data->irq_trig_rising |= BIT(hwirq);
+ data->irq_trig_falling |= BIT(hwirq);
+ break;
+
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static void nct6694_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+ mutex_lock(&data->irq_lock);
+}
+
+static void nct6694_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+
+ scoped_guard(mutex, &data->lock) {
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+ }
+
+ mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "gpio-nct6694",
+ .irq_mask = nct6694_irq_mask,
+ .irq_unmask = nct6694_irq_unmask,
+ .irq_set_type = nct6694_irq_set_type,
+ .irq_bus_lock = nct6694_irq_bus_lock,
+ .irq_bus_sync_unlock = nct6694_irq_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void nct6694_irq_dispose_mapping(void *d)
+{
+ struct nct6694_gpio_data *data = d;
+
+ irq_dispose_mapping(data->irq);
+}
+
+static void nct6694_gpio_ida_free(void *d)
+{
+ struct nct6694_gpio_data *data = d;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ ida_free(&nct6694->gpio_ida, data->group);
+}
+
+static int nct6694_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
+ struct nct6694_gpio_data *data;
+ struct gpio_irq_chip *girq;
+ int ret, i;
+ char **names;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+
+ ret = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+ data->group = ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_gpio_ida_free, data);
+ if (ret)
+ return ret;
+
+ names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
+ GFP_KERNEL);
+ if (!names)
+ return -ENOMEM;
+
+ for (i = 0; i < NCT6694_NR_GPIO; i++) {
+ names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
+ data->group, i);
+ if (!names[i])
+ return -ENOMEM;
+ }
+
+ data->irq = irq_create_mapping(nct6694->domain,
+ NCT6694_IRQ_GPIO0 + data->group);
+ if (!data->irq)
+ return -EINVAL;
+
+ ret = devm_add_action_or_reset(dev, nct6694_irq_dispose_mapping, data);
+ if (ret)
+ return ret;
+
+ data->gpio.names = (const char * const*)names;
+ data->gpio.label = pdev->name;
+ data->gpio.direction_input = nct6694_direction_input;
+ data->gpio.get = nct6694_get_value;
+ data->gpio.direction_output = nct6694_direction_output;
+ data->gpio.set = nct6694_set_value;
+ data->gpio.get_direction = nct6694_get_direction;
+ data->gpio.set_config = nct6694_set_config;
+ data->gpio.init_valid_mask = nct6694_init_valid_mask;
+ data->gpio.base = -1;
+ data->gpio.can_sleep = false;
+ data->gpio.owner = THIS_MODULE;
+ data->gpio.ngpio = NCT6694_NR_GPIO;
+
+ platform_set_drvdata(pdev, data);
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &data->irq_lock);
+ if (ret)
+ return ret;
+
+ ret = nct6694_get_irq_trig(data);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+ return ret;
+ }
+
+ girq = &data->gpio.irq;
+ gpio_irq_chip_set_chip(girq, &nct6694_irq_chip);
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ girq->threaded = true;
+
+ ret = devm_request_threaded_irq(dev, data->irq, NULL, nct6694_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "gpio-nct6694", data);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to request irq\n");
+ return ret;
+ }
+
+ return devm_gpiochip_add_data(dev, &data->gpio, data);
+}
+
+static struct platform_driver nct6694_gpio_driver = {
+ .driver = {
+ .name = "nct6694-gpio",
+ },
+ .probe = nct6694_gpio_probe,
+};
+
+module_platform_driver(nct6694_gpio_driver);
+
+MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-gpio");
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 9d28fcf7cd2a6f..19f660d9a0c540 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1698,6 +1698,16 @@ config SENSORS_NCT6683
This driver can also be built as a module. If so, the module
will be called nct6683.
+config SENSORS_NCT6694
+ tristate "Nuvoton NCT6694 Hardware Monitor support"
+ depends on MFD_NCT6694
+ help
+ Say Y here to support Nuvoton NCT6694 hardware monitoring
+ functionality.
+
+ This driver can also be built as a module. If so, the module
+ will be called nct6694-hwmon.
+
config SENSORS_NCT6775_CORE
tristate
select REGMAP
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index cd8bc4752b4dbf..9bce91611dc379 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -174,6 +174,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
+obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o
obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
nct6775-objs := nct6775-platform.o
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
new file mode 100644
index 00000000000000..6dcf22ca5018a5
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 report channel
+ * This defines the module type used for communication with the NCT6694
+ * report channel over the USB interface.
+ */
+#define NCT6694_RPT_MOD 0xFF
+
+/* Report channel */
+/*
+ * The report channel is used to report the status of the hardware monitor
+ * devices, such as voltage, temperature, fan speed, and PWM.
+ */
+#define NCT6694_VIN_IDX(x) (0x00 + (x))
+#define NCT6694_TIN_IDX(x) \
+ ({ typeof(x) (_x) = (x); \
+ ((_x) < 10) ? (0x10 + ((_x) * 2)) : \
+ (0x30 + (((_x) - 10) * 2)); })
+#define NCT6694_FIN_IDX(x) (0x50 + ((x) * 2))
+#define NCT6694_PWM_IDX(x) (0x70 + (x))
+#define NCT6694_VIN_STS(x) (0x68 + (x))
+#define NCT6694_TIN_STS(x) (0x6A + (x))
+#define NCT6694_FIN_STS(x) (0x6E + (x))
+
+/*
+ * USB command module type for NCT6694 HWMON controller.
+ * This defines the module type used for communication with the NCT6694
+ * HWMON controller over the USB interface.
+ */
+#define NCT6694_HWMON_MOD 0x00
+
+/* Command 00h - Hardware Monitor Control */
+#define NCT6694_HWMON_CONTROL 0x00
+#define NCT6694_HWMON_CONTROL_SEL 0x00
+
+/* Command 02h - Alarm Control */
+#define NCT6694_HWMON_ALARM 0x02
+#define NCT6694_HWMON_ALARM_SEL 0x00
+
+/*
+ * USB command module type for NCT6694 PWM controller.
+ * This defines the module type used for communication with the NCT6694
+ * PWM controller over the USB interface.
+ */
+#define NCT6694_PWM_MOD 0x01
+
+/* PWM Command - Manual Control */
+#define NCT6694_PWM_CONTROL 0x01
+#define NCT6694_PWM_CONTROL_SEL 0x00
+
+#define NCT6694_FREQ_FROM_REG(reg) ((reg) * 25000 / 255)
+#define NCT6694_FREQ_TO_REG(val) \
+ (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
+
+#define NCT6694_LSB_REG_MASK GENMASK(7, 5)
+#define NCT6694_TIN_HYST_MASK GENMASK(7, 5)
+
+enum nct6694_hwmon_temp_mode {
+ NCT6694_HWMON_TWOTIME_IRQ = 0,
+ NCT6694_HWMON_ONETIME_IRQ,
+ NCT6694_HWMON_REALTIME_IRQ,
+ NCT6694_HWMON_COMPARE_IRQ,
+};
+
+struct __packed nct6694_hwmon_control {
+ u8 vin_en[2];
+ u8 tin_en[2];
+ u8 fin_en[2];
+ u8 pwm_en[2];
+ u8 reserved1[40];
+ u8 pwm_freq[10];
+ u8 reserved2[6];
+};
+
+struct __packed nct6694_hwmon_alarm {
+ u8 smi_ctrl;
+ u8 reserved1[15];
+ struct {
+ u8 hl;
+ u8 ll;
+ } vin_limit[16];
+ struct {
+ u8 hyst;
+ s8 hl;
+ } tin_cfg[32];
+ __be16 fin_ll[10];
+ u8 reserved2[4];
+};
+
+struct __packed nct6694_pwm_control {
+ u8 mal_en[2];
+ u8 mal_val[10];
+ u8 reserved[12];
+};
+
+union __packed nct6694_hwmon_rpt {
+ u8 vin;
+ struct {
+ u8 msb;
+ u8 lsb;
+ } tin;
+ __be16 fin;
+ u8 pwm;
+ u8 status;
+};
+
+union __packed nct6694_hwmon_msg {
+ struct nct6694_hwmon_alarm hwmon_alarm;
+ struct nct6694_pwm_control pwm_ctrl;
+};
+
+struct nct6694_hwmon_data {
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ struct nct6694_hwmon_control hwmon_en;
+ union nct6694_hwmon_rpt *rpt;
+ union nct6694_hwmon_msg *msg;
+};
+
+static inline long in_from_reg(u8 reg)
+{
+ return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 16);
+}
+
+static inline long temp_from_reg(s8 reg)
+{
+ return reg * 1000;
+}
+
+static inline s8 temp_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 1000);
+}
+
+#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE | \
+ HWMON_I_MAX | HWMON_I_MIN | \
+ HWMON_I_ALARM)
+#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \
+ HWMON_T_MAX | HWMON_T_MAX_HYST | \
+ HWMON_T_MAX_ALARM)
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \
+ HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \
+ HWMON_PWM_FREQ)
+static const struct hwmon_channel_info *nct6694_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ NCT6694_HWMON_IN_CONFIG, /* VIN0 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN1 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN2 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN3 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN5 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN6 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN7 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN14 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN15 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN16 */
+ NCT6694_HWMON_IN_CONFIG, /* VBAT */
+ NCT6694_HWMON_IN_CONFIG, /* VSB */
+ NCT6694_HWMON_IN_CONFIG, /* AVSB */
+ NCT6694_HWMON_IN_CONFIG, /* VCC */
+ NCT6694_HWMON_IN_CONFIG, /* VHIF */
+ NCT6694_HWMON_IN_CONFIG), /* VTT */
+
+ HWMON_CHANNEL_INFO(temp,
+ NCT6694_HWMON_TEMP_CONFIG, /* THR1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR14 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR15 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR16 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */
+ NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */
+
+ HWMON_CHANNEL_INFO(fan,
+ NCT6694_HWMON_FAN_CONFIG, /* FIN0 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN1 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN2 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN3 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN4 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN5 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN6 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN7 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN8 */
+ NCT6694_HWMON_FAN_CONFIG), /* FIN9 */
+
+ HWMON_CHANNEL_INFO(pwm,
+ NCT6694_HWMON_PWM_CONFIG, /* PWM0 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM1 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM2 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM3 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM4 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM5 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM6 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM7 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM8 */
+ NCT6694_HWMON_PWM_CONFIG), /* PWM9 */
+ NULL
+};
+
+static int nct6694_in_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char vin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ vin_en = data->hwmon_en.vin_en[(channel / 8)];
+ *val = !!(vin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_in_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->vin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->vin);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->rpt->vin);
+
+ return 0;
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl);
+
+ return 0;
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll);
+
+ return 0;
+ case hwmon_in_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_en, temp_hyst;
+ signed char temp_max;
+ int ret, temp_raw;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ temp_en = data->hwmon_en.tin_en[channel / 8];
+ *val = !!(temp_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_temp_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->tin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->tin);
+ if (ret)
+ return ret;
+
+ temp_raw = data->rpt->tin.msb << 3;
+ temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb);
+
+ /* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */
+ *val = sign_extend32(temp_raw, 10) * 125;
+
+ return 0;
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl);
+
+ return 0;
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst);
+ *val = temp_from_reg(temp_max - temp_hyst);
+
+ return 0;
+ case hwmon_temp_max_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char fanin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ fanin_en = data->hwmon_en.fin_en[channel / 8];
+ *val = !!(fanin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_fan_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->fin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->fin);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->rpt->fin);
+
+ return 0;
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]);
+
+ return 0;
+ case hwmon_fan_min_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char pwm_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ pwm_en = data->hwmon_en.pwm_en[channel / 8];
+ *val = !!(pwm_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_pwm_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_PWM_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->pwm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->pwm);
+ if (ret)
+ return ret;
+
+ *val = data->rpt->pwm;
+
+ return 0;
+ case hwmon_pwm_freq:
+ *val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]);
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ if (val == 0)
+ data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_hyst;
+ signed char temp_max;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ if (val == 0)
+ data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, -127000, 127000);
+ data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+
+ val = clamp_val(val, -127000, 127000);
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = temp_max - temp_to_reg(val);
+ temp_hyst = clamp_val(temp_hyst, 0, 7);
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst =
+ (data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) |
+ FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ if (val == 0)
+ data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 1, 65535);
+ data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ if (val == 0)
+ data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_PWM_MOD,
+ .cmd = NCT6694_PWM_CONTROL,
+ .sel = NCT6694_PWM_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->pwm_ctrl))
+ };
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ if (ret)
+ return ret;
+
+ data->msg->pwm_ctrl.mal_val[channel] = val;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ case hwmon_pwm_freq:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ /* in mV */
+ return nct6694_in_read(dev, attr, channel, val);
+ case hwmon_temp:
+ /* in mC */
+ return nct6694_temp_read(dev, attr, channel, val);
+ case hwmon_fan:
+ /* in RPM */
+ return nct6694_fan_read(dev, attr, channel, val);
+ case hwmon_pwm:
+ /* in value 0~255 */
+ return nct6694_pwm_read(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_in:
+ return nct6694_in_write(dev, attr, channel, val);
+ case hwmon_temp:
+ return nct6694_temp_write(dev, attr, channel, val);
+ case hwmon_fan:
+ return nct6694_fan_write(dev, attr, channel, val);
+ case hwmon_pwm:
+ return nct6694_pwm_write(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct6694_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_enable:
+ case hwmon_in_max:
+ case hwmon_in_min:
+ return 0644;
+ case hwmon_in_alarm:
+ case hwmon_in_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_enable:
+ case hwmon_temp_max:
+ case hwmon_temp_max_hyst:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_enable:
+ case hwmon_fan_min:
+ return 0644;
+ case hwmon_fan_input:
+ case hwmon_fan_min_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_freq:
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops nct6694_hwmon_ops = {
+ .is_visible = nct6694_is_visible,
+ .read = nct6694_read,
+ .write = nct6694_write,
+};
+
+static const struct hwmon_chip_info nct6694_chip_info = {
+ .ops = &nct6694_hwmon_ops,
+ .info = nct6694_info,
+};
+
+static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
+{
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+ int ret;
+
+ /*
+ * Record each Hardware Monitor Channel enable status
+ * and PWM frequency register
+ */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ if (ret)
+ return ret;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+
+ /* Select hwmon device alarm mode */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+}
+
+static int nct6694_hwmon_probe(struct platform_device *pdev)
+{
+ struct nct6694_hwmon_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt),
+ GFP_KERNEL);
+ if (!data->rpt)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ ret = devm_mutex_init(&pdev->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = nct6694_hwmon_init(data);
+ if (ret)
+ return ret;
+
+ /* Register hwmon device to HWMON framework */
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "nct6694", data,
+ &nct6694_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+ .driver = {
+ .name = "nct6694-hwmon",
+ },
+ .probe = nct6694_hwmon_probe,
+};
+
+module_platform_driver(nct6694_hwmon_driver);
+
+MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-hwmon");
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 070d014fdc5d5d..63a2b5a9abc39f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1357,6 +1357,16 @@ config I2C_LJCA
This driver can also be built as a module. If so, the module
will be called i2c-ljca.
+config I2C_NCT6694
+ tristate "Nuvoton NCT6694 I2C adapter support"
+ depends on MFD_NCT6694
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB to I2C interface.
+
+ This driver can also be built as a module. If so, the module will
+ be called i2c-nct6694.
+
config I2C_CP2615
tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 04db855fdfd66f..fe8cf6325fc989 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o
+obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 00000000000000..1413ab6f94628b
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 I2C adapter driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 I2C controller.
+ * This defines the module type used for communication with the NCT6694
+ * I2C controller over the USB interface.
+ */
+#define NCT6694_I2C_MOD 0x03
+
+/* Command 00h - I2C Deliver */
+#define NCT6694_I2C_DELIVER 0x00
+#define NCT6694_I2C_DELIVER_SEL 0x00
+
+#define NCT6694_I2C_MAX_XFER_SIZE 64
+#define NCT6694_I2C_MAX_DEVS 6
+
+static unsigned char br_reg[NCT6694_I2C_MAX_DEVS] = {[0 ... (NCT6694_I2C_MAX_DEVS - 1)] = 0xFF};
+
+module_param_array(br_reg, byte, NULL, 0644);
+MODULE_PARM_DESC(br_reg,
+ "I2C Baudrate register per adapter: (0=25K, 1=50K, 2=100K, 3=200K, 4=400K, 5=800K, 6=1M), default=2");
+
+enum nct6694_i2c_baudrate {
+ NCT6694_I2C_BR_25K = 0,
+ NCT6694_I2C_BR_50K,
+ NCT6694_I2C_BR_100K,
+ NCT6694_I2C_BR_200K,
+ NCT6694_I2C_BR_400K,
+ NCT6694_I2C_BR_800K,
+ NCT6694_I2C_BR_1M
+};
+
+struct __packed nct6694_i2c_deliver {
+ u8 port;
+ u8 br;
+ u8 addr;
+ u8 w_cnt;
+ u8 r_cnt;
+ u8 rsv[11];
+ u8 write_data[NCT6694_I2C_MAX_XFER_SIZE];
+ u8 read_data[NCT6694_I2C_MAX_XFER_SIZE];
+};
+
+struct nct6694_i2c_data {
+ struct device *dev;
+ struct nct6694 *nct6694;
+ struct i2c_adapter adapter;
+ struct nct6694_i2c_deliver deliver;
+ unsigned char port;
+ unsigned char br;
+};
+
+static int nct6694_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct nct6694_i2c_data *data = adap->algo_data;
+ struct nct6694_i2c_deliver *deliver = &data->deliver;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_I2C_MOD,
+ .cmd = NCT6694_I2C_DELIVER,
+ .sel = NCT6694_I2C_DELIVER_SEL,
+ .len = cpu_to_le16(sizeof(*deliver))
+ };
+ int ret, i;
+
+ for (i = 0; i < num; i++) {
+ struct i2c_msg *msg_temp = &msgs[i];
+
+ memset(deliver, 0, sizeof(*deliver));
+
+ deliver->port = data->port;
+ deliver->br = data->br;
+ deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
+ if (msg_temp->flags & I2C_M_RD) {
+ deliver->r_cnt = msg_temp->len;
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+ if (ret < 0)
+ return ret;
+
+ memcpy(msg_temp->buf, deliver->read_data, msg_temp->len);
+ } else {
+ deliver->w_cnt = msg_temp->len;
+ memcpy(deliver->write_data, msg_temp->buf, msg_temp->len);
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return num;
+}
+
+static u32 nct6694_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_adapter_quirks nct6694_i2c_quirks = {
+ .max_read_len = NCT6694_I2C_MAX_XFER_SIZE,
+ .max_write_len = NCT6694_I2C_MAX_XFER_SIZE,
+};
+
+static const struct i2c_algorithm nct6694_i2c_algo = {
+ .xfer = nct6694_i2c_xfer,
+ .functionality = nct6694_i2c_func,
+};
+
+static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data)
+{
+ if (data->port >= NCT6694_I2C_MAX_DEVS) {
+ dev_err(data->dev, "Invalid I2C port index %d\n", data->port);
+ return -EINVAL;
+ }
+
+ if (br_reg[data->port] > NCT6694_I2C_BR_1M) {
+ dev_warn(data->dev, "Invalid baudrate %d for I2C%d, using 100K\n",
+ br_reg[data->port], data->port);
+ br_reg[data->port] = NCT6694_I2C_BR_100K;
+ }
+
+ data->br = br_reg[data->port];
+
+ return 0;
+}
+
+static void nct6694_i2c_ida_free(void *d)
+{
+ struct nct6694_i2c_data *data = d;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ ida_free(&nct6694->i2c_ida, data->port);
+}
+
+static int nct6694_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
+ struct nct6694_i2c_data *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->nct6694 = nct6694;
+
+ ret = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+ data->port = ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_i2c_ida_free, data);
+ if (ret)
+ return ret;
+
+ ret = nct6694_i2c_set_baudrate(data);
+ if (ret)
+ return ret;
+
+ sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", data->port);
+ data->adapter.owner = THIS_MODULE;
+ data->adapter.algo = &nct6694_i2c_algo;
+ data->adapter.quirks = &nct6694_i2c_quirks;
+ data->adapter.dev.parent = dev;
+ data->adapter.algo_data = data;
+
+ platform_set_drvdata(pdev, data);
+
+ return devm_i2c_add_adapter(dev, &data->adapter);
+}
+
+static struct platform_driver nct6694_i2c_driver = {
+ .driver = {
+ .name = "nct6694-i2c",
+ },
+ .probe = nct6694_i2c_probe,
+};
+
+module_platform_driver(nct6694_i2c_driver);
+
+MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-i2c");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index bfaffb74c99113..863de0aad21538 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1134,6 +1134,21 @@ config MFD_MENF21BMC
This driver can also be built as a module. If so the module
will be called menf21bmc.
+config MFD_NCT6694
+ tristate "Nuvoton NCT6694 support"
+ select MFD_CORE
+ depends on USB
+ help
+ This enables support for the Nuvoton USB device NCT6694, which shares
+ peripherals.
+ The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
+ 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
+ PWM, and RTC.
+ This driver provides core APIs to access the NCT6694 hardware
+ monitoring and control features.
+ Additional drivers must be enabled to utilize the specific
+ functionalities of the device.
+
config MFD_OCELOT
tristate "Microsemi Ocelot External Control Support"
depends on SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f7bdedd5a66d16..1e7738c02b2c97 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -121,6 +121,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
+obj-$(CONFIG_MFD_NCT6694) += nct6694.o
+
obj-$(CONFIG_MFD_CORE) += mfd-core.o
ocelot-soc-objs := ocelot-core.o ocelot-spi.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
new file mode 100644
index 00000000000000..308b2fda3055c5
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 core driver using USB interface to provide
+ * access to the NCT6694 hardware monitoring and control features.
+ *
+ * The NCT6694 is an integrated controller that provides GPIO, I2C,
+ * CAN, WDT, HWMON and RTC management.
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/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/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+
+static const struct mfd_cell nct6694_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"),
+
+ MFD_CELL_NAME("nct6694-wdt"),
+ MFD_CELL_NAME("nct6694-wdt"),
+
+ MFD_CELL_NAME("nct6694-hwmon"),
+
+ MFD_CELL_NAME("nct6694-rtc"),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(nct6694->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(nct6694->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(nct6694->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+/**
+ * nct6694_read_msg() - Read message from NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer to store the response data
+ *
+ * Sends a command to the NCT6694 device and reads the response.
+ * The command header is specified in @cmd_hd, and the response
+ * data is stored in @buf.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+
+ /* Send command packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+ sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+ sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+ le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
+ le16_to_cpu(cmd_hd->len), rx_len);
+ return -EIO;
+ }
+
+ return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_read_msg);
+
+/**
+ * nct6694_write_msg() - Write message to NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer containing the data to be sent
+ *
+ * Sends a command to the NCT6694 device and writes the data
+ * from @buf. The command header is specified in @cmd_hd.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+
+ /* Send command packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+ sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Send data packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
+ le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+ sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+ le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
+ le16_to_cpu(cmd_hd->len), rx_len);
+ return -EIO;
+ }
+
+ return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+ struct nct6694 *nct6694 = urb->context;
+ __le32 *status_le = urb->transfer_buffer;
+ u32 int_status;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default:
+ goto resubmit;
+ }
+
+ int_status = le32_to_cpu(*status_le);
+
+ while (int_status) {
+ int irq = __ffs(int_status);
+
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+ int_status &= ~BIT(irq);
+ }
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret));
+}
+
+static void nct6694_irq_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)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct usb_endpoint_descriptor *int_endpoint;
+ struct usb_host_interface *interface;
+ struct device *dev = &iface->dev;
+ struct nct6694 *nct6694;
+ int ret;
+
+ nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+ if (!nct6694)
+ return -ENOMEM;
+
+ nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
+ if (!nct6694->usb_msg)
+ return -ENOMEM;
+
+ nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
+ if (!nct6694->int_buffer)
+ return -ENOMEM;
+
+ nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!nct6694->int_in_urb)
+ return -ENOMEM;
+
+ nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ nct6694);
+ if (!nct6694->domain) {
+ ret = -ENODEV;
+ goto err_urb;
+ }
+
+ nct6694->dev = dev;
+ nct6694->udev = udev;
+
+ 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);
+
+ ret = devm_mutex_init(dev, &nct6694->access_lock);
+ if (ret)
+ goto err_ida;
+
+ interface = iface->cur_altsetting;
+
+ int_endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(int_endpoint)) {
+ ret = -ENODEV;
+ goto err_ida;
+ }
+
+ usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
+ nct6694, int_endpoint->bInterval);
+
+ ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ if (ret)
+ goto err_ida;
+
+ usb_set_intfdata(iface, nct6694);
+
+ ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
+ if (ret)
+ goto err_mfd;
+
+ return 0;
+
+err_mfd:
+ usb_kill_urb(nct6694->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(nct6694->int_in_urb);
+ return ret;
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+ struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+ mfd_remove_devices(nct6694->dev);
+ usb_kill_urb(nct6694->int_in_urb);
+ ida_destroy(&nct6694->wdt_ida);
+ ida_destroy(&nct6694->canfd_ida);
+ ida_destroy(&nct6694->i2c_ida);
+ ida_destroy(&nct6694->gpio_ida);
+ irq_domain_remove(nct6694->domain);
+ usb_free_urb(nct6694->int_in_urb);
+}
+
+static const struct usb_device_id nct6694_ids[] = {
+ { USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, nct6694_ids);
+
+static struct usb_driver nct6694_usb_driver = {
+ .name = "nct6694",
+ .id_table = nct6694_ids,
+ .probe = nct6694_usb_probe,
+ .disconnect = nct6694_usb_disconnect,
+};
+module_usb_driver(nct6694_usb_driver);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 core driver");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
index a7547a83120e84..cf65a90816b9e4 100644
--- a/drivers/net/can/usb/Kconfig
+++ b/drivers/net/can/usb/Kconfig
@@ -134,6 +134,17 @@ config CAN_MCBA_USB
This driver supports the CAN BUS Analyzer interface
from Microchip (http://www.microchip.com/development-tools/).
+config CAN_NCT6694
+ tristate "Nuvoton NCT6694 Socket CANfd support"
+ depends on MFD_NCT6694
+ select CAN_RX_OFFLOAD
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to socket CANfd controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called nct6694_canfd.
+
config CAN_PEAK_USB
tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
help
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 8b11088e9a5956..fcafb1ac262eaf 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
+obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
obj-$(CONFIG_CAN_UCAN) += ucan.o
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
new file mode 100644
index 00000000000000..8deff16491a1a3
--- /dev/null
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -0,0 +1,832 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/can/dev.h>
+#include <linux/can/rx-offload.h>
+#include <linux/ethtool.h>
+#include <linux/idr.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#define DEVICE_NAME "nct6694-canfd"
+
+/* USB command module type for NCT6694 CANfd controller.
+ * This defines the module type used for communication with the NCT6694
+ * CANfd controller over the USB interface.
+ */
+#define NCT6694_CANFD_MOD 0x05
+
+/* Command 00h - CAN Setting and Initialization */
+#define NCT6694_CANFD_SETTING 0x00
+#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1 BIT(0)
+#define NCT6694_CANFD_SETTING_ACTIVE_CTRL2 BIT(1)
+#define NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP BIT(2)
+#define NCT6694_CANFD_SETTING_CTRL1_MON BIT(0)
+#define NCT6694_CANFD_SETTING_CTRL1_NISO BIT(1)
+#define NCT6694_CANFD_SETTING_CTRL1_LBCK BIT(2)
+#define NCT6694_CANFD_SETTING_NBTP_NTSEG2 GENMASK(6, 0)
+#define NCT6694_CANFD_SETTING_NBTP_NTSEG1 GENMASK(15, 8)
+#define NCT6694_CANFD_SETTING_NBTP_NBRP GENMASK(24, 16)
+#define NCT6694_CANFD_SETTING_NBTP_NSJW GENMASK(31, 25)
+#define NCT6694_CANFD_SETTING_DBTP_DSJW GENMASK(3, 0)
+#define NCT6694_CANFD_SETTING_DBTP_DTSEG2 GENMASK(7, 4)
+#define NCT6694_CANFD_SETTING_DBTP_DTSEG1 GENMASK(12, 8)
+#define NCT6694_CANFD_SETTING_DBTP_DBRP GENMASK(20, 16)
+#define NCT6694_CANFD_SETTING_DBTP_TDC BIT(23)
+
+/* Command 01h - CAN Information */
+#define NCT6694_CANFD_INFORMATION 0x01
+#define NCT6694_CANFD_INFORMATION_SEL 0x00
+
+/* Command 02h - CAN Event */
+#define NCT6694_CANFD_EVENT 0x02
+#define NCT6694_CANFD_EVENT_SEL(idx, mask) \
+ ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
+
+#define NCT6694_CANFD_EVENT_MASK GENMASK(5, 0)
+#define NCT6694_CANFD_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */
+#define NCT6694_CANFD_EVT_RX_DATA_LOST BIT(5) /* Read-clear */
+#define NCT6694_CANFD_EVT_RX_DATA_IN BIT(7) /* Read-clear */
+
+/* Command 10h - CAN Deliver */
+#define NCT6694_CANFD_DELIVER 0x10
+#define NCT6694_CANFD_DELIVER_SEL(buf_cnt) \
+ ((buf_cnt) & 0xFF)
+
+/* Command 11h - CAN Receive */
+#define NCT6694_CANFD_RECEIVE 0x11
+#define NCT6694_CANFD_RECEIVE_SEL(idx, buf_cnt) \
+ ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
+
+#define NCT6694_CANFD_FRAME_TAG(idx) (0xC0 | (idx))
+#define NCT6694_CANFD_FRAME_FLAG_EFF BIT(0)
+#define NCT6694_CANFD_FRAME_FLAG_RTR BIT(1)
+#define NCT6694_CANFD_FRAME_FLAG_FD BIT(2)
+#define NCT6694_CANFD_FRAME_FLAG_BRS BIT(3)
+#define NCT6694_CANFD_FRAME_FLAG_ERR BIT(4)
+
+#define NCT6694_NAPI_WEIGHT 32
+
+enum nct6694_event_err {
+ NCT6694_CANFD_EVT_ERR_NO_ERROR = 0,
+ NCT6694_CANFD_EVT_ERR_CRC_ERROR,
+ NCT6694_CANFD_EVT_ERR_STUFF_ERROR,
+ NCT6694_CANFD_EVT_ERR_ACK_ERROR,
+ NCT6694_CANFD_EVT_ERR_FORM_ERROR,
+ NCT6694_CANFD_EVT_ERR_BIT_ERROR,
+ NCT6694_CANFD_EVT_ERR_TIMEOUT_ERROR,
+ NCT6694_CANFD_EVT_ERR_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+ NCT6694_CANFD_EVT_STS_ERROR_ACTIVE = 0,
+ NCT6694_CANFD_EVT_STS_ERROR_PASSIVE,
+ NCT6694_CANFD_EVT_STS_BUS_OFF,
+ NCT6694_CANFD_EVT_STS_WARNING,
+};
+
+struct __packed nct6694_canfd_setting {
+ __le32 nbr;
+ __le32 dbr;
+ u8 active;
+ u8 reserved[3];
+ __le16 ctrl1;
+ __le16 ctrl2;
+ __le32 nbtp;
+ __le32 dbtp;
+};
+
+struct __packed nct6694_canfd_information {
+ u8 tx_fifo_cnt;
+ u8 rx_fifo_cnt;
+ u8 reserved[2];
+ __le32 can_clk;
+};
+
+struct __packed nct6694_canfd_event {
+ u8 err;
+ u8 status;
+ u8 tx_evt;
+ u8 rx_evt;
+ u8 rec;
+ u8 tec;
+ u8 reserved[2];
+};
+
+struct __packed nct6694_canfd_frame {
+ u8 tag;
+ u8 flag;
+ u8 reserved;
+ u8 length;
+ __le32 id;
+ u8 data[CANFD_MAX_DLEN];
+};
+
+struct nct6694_canfd_priv {
+ struct can_priv can; /* must be the first member */
+ struct can_rx_offload offload;
+ struct net_device *ndev;
+ struct nct6694 *nct6694;
+ struct workqueue_struct *wq;
+ struct work_struct tx_work;
+ struct nct6694_canfd_frame tx;
+ struct nct6694_canfd_frame rx;
+ struct nct6694_canfd_event event[2];
+ struct can_berr_counter bec;
+};
+
+static inline struct nct6694_canfd_priv *rx_offload_to_priv(struct can_rx_offload *offload)
+{
+ return container_of(offload, struct nct6694_canfd_priv, offload);
+}
+
+static const struct can_bittiming_const nct6694_canfd_bittiming_nominal_const = {
+ .name = DEVICE_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = 256,
+ .tseg2_min = 1,
+ .tseg2_max = 128,
+ .sjw_max = 128,
+ .brp_min = 1,
+ .brp_max = 512,
+ .brp_inc = 1,
+};
+
+static const struct can_bittiming_const nct6694_canfd_bittiming_data_const = {
+ .name = DEVICE_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = 32,
+ .tseg2_min = 1,
+ .tseg2_max = 16,
+ .sjw_max = 16,
+ .brp_min = 1,
+ .brp_max = 32,
+ .brp_inc = 1,
+};
+
+static void nct6694_canfd_rx_offload(struct can_rx_offload *offload,
+ struct sk_buff *skb)
+{
+ struct nct6694_canfd_priv *priv = rx_offload_to_priv(offload);
+ int ret;
+
+ ret = can_rx_offload_queue_tail(offload, skb);
+ if (ret)
+ priv->ndev->stats.rx_fifo_errors++;
+}
+
+static void nct6694_canfd_handle_lost_msg(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ netdev_dbg(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+ stats->rx_errors++;
+ stats->rx_over_errors++;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (!skb)
+ return;
+
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+ nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt)
+{
+ struct net_device_stats *stats = &ndev->stats;
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_canfd_frame *frame = &priv->rx;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_RECEIVE,
+ .sel = NCT6694_CANFD_RECEIVE_SEL(ndev->dev_port, 1),
+ .len = cpu_to_le16(sizeof(*frame))
+ };
+ struct sk_buff *skb;
+ int ret;
+
+ ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
+ if (ret)
+ return;
+
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_FD) {
+ struct canfd_frame *cfd;
+
+ skb = alloc_canfd_skb(priv->ndev, &cfd);
+ if (!skb) {
+ stats->rx_dropped++;
+ return;
+ }
+
+ cfd->can_id = le32_to_cpu(frame->id);
+ cfd->len = canfd_sanitize_len(frame->length);
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
+ cfd->can_id |= CAN_EFF_FLAG;
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_BRS)
+ cfd->flags |= CANFD_BRS;
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_ERR)
+ cfd->flags |= CANFD_ESI;
+
+ memcpy(cfd->data, frame->data, cfd->len);
+ } else {
+ struct can_frame *cf;
+
+ skb = alloc_can_skb(priv->ndev, &cf);
+ if (!skb) {
+ stats->rx_dropped++;
+ return;
+ }
+
+ cf->can_id = le32_to_cpu(frame->id);
+ cf->len = can_cc_dlc2len(frame->length);
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF)
+ cf->can_id |= CAN_EFF_FLAG;
+
+ if (frame->flag & NCT6694_CANFD_FRAME_FLAG_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+ else
+ memcpy(cf->data, frame->data, cf->len);
+ }
+
+ nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ *bec = priv->bec;
+
+ return 0;
+}
+
+static void nct6694_canfd_handle_state_change(struct net_device *ndev, u8 status)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ enum can_state new_state, rx_state, tx_state;
+ struct can_berr_counter bec;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ nct6694_canfd_get_berr_counter(ndev, &bec);
+ can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);
+
+ new_state = max(tx_state, rx_state);
+
+ /* state hasn't changed */
+ if (new_state == priv->can.state)
+ return;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+
+ can_change_state(ndev, cf, tx_state, rx_state);
+
+ if (new_state == CAN_STATE_BUS_OFF) {
+ can_bus_off(ndev);
+ } else if (cf) {
+ cf->can_id |= CAN_ERR_CNT;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+
+ if (skb)
+ nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_err)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ priv->can.can_stats.bus_error++;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (cf)
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+ switch (bus_err) {
+ case NCT6694_CANFD_EVT_ERR_CRC_ERROR:
+ netdev_dbg(ndev, "CRC error\n");
+ ndev->stats.rx_errors++;
+ if (cf)
+ cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+ break;
+
+ case NCT6694_CANFD_EVT_ERR_STUFF_ERROR:
+ netdev_dbg(ndev, "Stuff error\n");
+ ndev->stats.rx_errors++;
+ if (cf)
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+
+ case NCT6694_CANFD_EVT_ERR_ACK_ERROR:
+ netdev_dbg(ndev, "Ack error\n");
+ ndev->stats.tx_errors++;
+ if (cf) {
+ cf->can_id |= CAN_ERR_ACK;
+ cf->data[2] |= CAN_ERR_PROT_TX;
+ }
+ break;
+
+ case NCT6694_CANFD_EVT_ERR_FORM_ERROR:
+ netdev_dbg(ndev, "Form error\n");
+ ndev->stats.rx_errors++;
+ if (cf)
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+
+ case NCT6694_CANFD_EVT_ERR_BIT_ERROR:
+ netdev_dbg(ndev, "Bit error\n");
+ ndev->stats.tx_errors++;
+ if (cf)
+ cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
+ break;
+
+ default:
+ break;
+ }
+
+ if (skb)
+ nct6694_canfd_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_canfd_handle_tx(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+
+ stats->tx_bytes += can_rx_offload_get_echo_skb_queue_tail(&priv->offload,
+ 0, NULL);
+ stats->tx_packets++;
+ netif_wake_queue(ndev);
+}
+
+static irqreturn_t nct6694_canfd_irq(int irq, void *data)
+{
+ struct net_device *ndev = data;
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_canfd_event *event = &priv->event[ndev->dev_port];
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_EVENT,
+ .sel = NCT6694_CANFD_EVENT_SEL(ndev->dev_port, NCT6694_CANFD_EVENT_MASK),
+ .len = cpu_to_le16(sizeof(priv->event))
+ };
+ irqreturn_t handled = IRQ_NONE;
+ int ret;
+
+ ret = nct6694_read_msg(priv->nct6694, &cmd_hd, priv->event);
+ if (ret < 0)
+ return handled;
+
+ if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_IN) {
+ nct6694_canfd_handle_rx(ndev, event->rx_evt);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_LOST) {
+ nct6694_canfd_handle_lost_msg(ndev);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->status) {
+ nct6694_canfd_handle_state_change(ndev, event->status);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->err != NCT6694_CANFD_EVT_ERR_NO_ERROR) {
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+ nct6694_canfd_handle_bus_err(ndev, event->err);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->tx_evt & NCT6694_CANFD_EVT_TX_FIFO_EMPTY) {
+ nct6694_canfd_handle_tx(ndev);
+ handled = IRQ_HANDLED;
+ }
+
+ if (handled)
+ can_rx_offload_threaded_irq_finish(&priv->offload);
+
+ priv->bec.rxerr = event->rec;
+ priv->bec.txerr = event->tec;
+
+ return handled;
+}
+
+static void nct6694_canfd_tx_work(struct work_struct *work)
+{
+ struct nct6694_canfd_priv *priv = container_of(work,
+ struct nct6694_canfd_priv,
+ tx_work);
+ struct nct6694_canfd_frame *frame = &priv->tx;
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ struct sk_buff *skb = priv->can.echo_skb[0];
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_DELIVER,
+ .sel = NCT6694_CANFD_DELIVER_SEL(1),
+ .len = cpu_to_le16(sizeof(*frame))
+ };
+ u32 txid;
+ int err;
+
+ memset(frame, 0, sizeof(*frame));
+
+ frame->tag = NCT6694_CANFD_FRAME_TAG(ndev->dev_port);
+
+ if (can_is_canfd_skb(skb)) {
+ struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
+
+ if (cfd->flags & CANFD_BRS)
+ frame->flag |= NCT6694_CANFD_FRAME_FLAG_BRS;
+
+ if (cfd->can_id & CAN_EFF_FLAG) {
+ txid = cfd->can_id & CAN_EFF_MASK;
+ frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
+ } else {
+ txid = cfd->can_id & CAN_SFF_MASK;
+ }
+ frame->flag |= NCT6694_CANFD_FRAME_FLAG_FD;
+ frame->id = cpu_to_le32(txid);
+ frame->length = canfd_sanitize_len(cfd->len);
+
+ memcpy(frame->data, cfd->data, frame->length);
+ } else {
+ struct can_frame *cf = (struct can_frame *)skb->data;
+
+ if (cf->can_id & CAN_EFF_FLAG) {
+ txid = cf->can_id & CAN_EFF_MASK;
+ frame->flag |= NCT6694_CANFD_FRAME_FLAG_EFF;
+ } else {
+ txid = cf->can_id & CAN_SFF_MASK;
+ }
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ frame->flag |= NCT6694_CANFD_FRAME_FLAG_RTR;
+ else
+ memcpy(frame->data, cf->data, cf->len);
+
+ frame->id = cpu_to_le32(txid);
+ frame->length = cf->len;
+ }
+
+ err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
+ if (err) {
+ can_free_echo_skb(ndev, 0, NULL);
+ stats->tx_dropped++;
+ stats->tx_errors++;
+ netif_wake_queue(ndev);
+ }
+}
+
+static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ if (can_dev_dropped_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ netif_stop_queue(ndev);
+ can_put_echo_skb(skb, ndev, 0, 0);
+ queue_work(priv->wq, &priv->tx_work);
+
+ return NETDEV_TX_OK;
+}
+
+static int nct6694_canfd_start(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ const struct can_bittiming *n_bt = &priv->can.bittiming;
+ const struct can_bittiming *d_bt = &priv->can.fd.data_bittiming;
+ struct nct6694_canfd_setting *setting __free(kfree) = NULL;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_SETTING,
+ .sel = ndev->dev_port,
+ .len = cpu_to_le16(sizeof(*setting))
+ };
+ u32 en_tdc;
+ int ret;
+
+ setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+ if (!setting)
+ return -ENOMEM;
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK);
+
+ /* Disable clock divider */
+ setting->ctrl2 = 0;
+
+ setting->nbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW,
+ n_bt->sjw - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP,
+ n_bt->brp - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2,
+ n_bt->phase_seg2 - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1,
+ n_bt->prop_seg + n_bt->phase_seg1 - 1));
+
+ if (d_bt->brp <= 2)
+ en_tdc = NCT6694_CANFD_SETTING_DBTP_TDC;
+ else
+ en_tdc = 0;
+
+ setting->dbtp = cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW,
+ d_bt->sjw - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP,
+ d_bt->brp - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2,
+ d_bt->phase_seg2 - 1) |
+ FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1,
+ d_bt->prop_seg + d_bt->phase_seg1 - 1) |
+ en_tdc);
+
+ setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1 |
+ NCT6694_CANFD_SETTING_ACTIVE_CTRL2 |
+ NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP;
+
+ ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+ if (ret)
+ return ret;
+
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ return 0;
+}
+
+static void nct6694_canfd_stop(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_canfd_setting *setting __free(kfree) = NULL;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_SETTING,
+ .sel = ndev->dev_port,
+ .len = cpu_to_le16(sizeof(*setting))
+ };
+
+ /* The NCT6694 cannot be stopped. To ensure safe operation and avoid
+ * interference, the control mode is set to Listen-Only mode. This
+ * mode allows the device to monitor bus activity without actively
+ * participating in communication.
+ */
+ setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+ if (!setting)
+ return;
+
+ nct6694_read_msg(priv->nct6694, &cmd_hd, setting);
+ setting->ctrl1 = cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON);
+ setting->active = NCT6694_CANFD_SETTING_ACTIVE_CTRL1;
+ nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int nct6694_canfd_close(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ nct6694_canfd_stop(ndev);
+ destroy_workqueue(priv->wq);
+ free_irq(ndev->irq, ndev);
+ can_rx_offload_disable(&priv->offload);
+ close_candev(ndev);
+ return 0;
+}
+
+static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ ret = nct6694_canfd_start(ndev);
+ if (ret)
+ return ret;
+
+ netif_wake_queue(ndev);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int nct6694_canfd_open(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = open_candev(ndev);
+ if (ret)
+ return ret;
+
+ can_rx_offload_enable(&priv->offload);
+
+ ret = request_threaded_irq(ndev->irq, NULL,
+ nct6694_canfd_irq, IRQF_ONESHOT,
+ "nct6694_canfd", ndev);
+ if (ret) {
+ netdev_err(ndev, "Failed to request IRQ\n");
+ goto can_rx_offload_disable;
+ }
+
+ priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
+ WQ_FREEZABLE | WQ_MEM_RECLAIM,
+ ndev->name);
+ if (!priv->wq) {
+ ret = -ENOMEM;
+ goto free_irq;
+ }
+
+ ret = nct6694_canfd_start(ndev);
+ if (ret)
+ goto destroy_wq;
+
+ netif_start_queue(ndev);
+
+ return 0;
+
+destroy_wq:
+ destroy_workqueue(priv->wq);
+free_irq:
+ free_irq(ndev->irq, ndev);
+can_rx_offload_disable:
+ can_rx_offload_disable(&priv->offload);
+ close_candev(ndev);
+ return ret;
+}
+
+static const struct net_device_ops nct6694_canfd_netdev_ops = {
+ .ndo_open = nct6694_canfd_open,
+ .ndo_stop = nct6694_canfd_close,
+ .ndo_start_xmit = nct6694_canfd_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+static const struct ethtool_ops nct6694_canfd_ethtool_ops = {
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
+{
+ struct nct6694_canfd_information *info __free(kfree) = NULL;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CANFD_MOD,
+ .cmd = NCT6694_CANFD_INFORMATION,
+ .sel = NCT6694_CANFD_INFORMATION_SEL,
+ .len = cpu_to_le16(sizeof(*info))
+ };
+ int ret;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
+ if (ret)
+ return ret;
+
+ return le32_to_cpu(info->can_clk);
+}
+
+static int nct6694_canfd_probe(struct platform_device *pdev)
+{
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_canfd_priv *priv;
+ struct net_device *ndev;
+ int port, irq, ret, can_clk;
+
+ port = ida_alloc(&nct6694->canfd_ida, GFP_KERNEL);
+ if (port < 0)
+ return port;
+
+ irq = irq_create_mapping(nct6694->domain,
+ NCT6694_IRQ_CAN0 + port);
+ if (!irq) {
+ ret = -EINVAL;
+ goto free_ida;
+ }
+
+ ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
+ if (!ndev) {
+ ret = -ENOMEM;
+ goto dispose_irq;
+ }
+
+ ndev->irq = irq;
+ ndev->flags |= IFF_ECHO;
+ ndev->dev_port = port;
+ ndev->netdev_ops = &nct6694_canfd_netdev_ops;
+ ndev->ethtool_ops = &nct6694_canfd_ethtool_ops;
+
+ priv = netdev_priv(ndev);
+ priv->nct6694 = nct6694;
+ priv->ndev = ndev;
+
+ can_clk = nct6694_canfd_get_clock(priv);
+ if (can_clk < 0) {
+ ret = dev_err_probe(&pdev->dev, can_clk,
+ "Failed to get clock\n");
+ goto free_candev;
+ }
+
+ INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
+
+ priv->can.clock.freq = can_clk;
+ priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
+ priv->can.fd.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
+ priv->can.do_set_mode = nct6694_canfd_set_mode;
+ priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+ CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
+ CAN_CTRLMODE_FD_NON_ISO;
+
+ ret = can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD);
+ if (ret)
+ goto free_candev;
+
+ ret = can_rx_offload_add_manual(ndev, &priv->offload,
+ NCT6694_NAPI_WEIGHT);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
+ goto free_candev;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ SET_NETDEV_DEV(priv->ndev, &pdev->dev);
+
+ ret = register_candev(priv->ndev);
+ if (ret)
+ goto rx_offload_del;
+
+ return 0;
+
+rx_offload_del:
+ can_rx_offload_del(&priv->offload);
+free_candev:
+ free_candev(ndev);
+dispose_irq:
+ irq_dispose_mapping(irq);
+free_ida:
+ ida_free(&nct6694->canfd_ida, port);
+ return ret;
+}
+
+static void nct6694_canfd_remove(struct platform_device *pdev)
+{
+ struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
+ struct nct6694 *nct6694 = priv->nct6694;
+ struct net_device *ndev = priv->ndev;
+ int port = ndev->dev_port;
+ int irq = ndev->irq;
+
+ unregister_candev(ndev);
+ can_rx_offload_del(&priv->offload);
+ free_candev(ndev);
+ irq_dispose_mapping(irq);
+ ida_free(&nct6694->canfd_ida, port);
+}
+
+static struct platform_driver nct6694_canfd_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ },
+ .probe = nct6694_canfd_probe,
+ .remove = nct6694_canfd_remove,
+};
+
+module_platform_driver(nct6694_canfd_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 64f6e9756aff4a..4a8dc8d0a4b7c0 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -416,6 +416,16 @@ config RTC_DRV_NCT3018Y
This driver can also be built as a module, if so, the module will be
called "rtc-nct3018y".
+config RTC_DRV_NCT6694
+ tristate "Nuvoton NCT6694 RTC support"
+ depends on MFD_NCT6694
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to RTC.
+
+ This driver can also be built as a module. If so, the module will
+ be called rtc-nct6694.
+
config RTC_DRV_RK808
tristate "Rockchip RK805/RK808/RK809/RK817/RK818 RTC"
depends on MFD_RK8XX
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 789bddfea99d8f..610a9ee5fd33c3 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -119,6 +119,7 @@ obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o
obj-$(CONFIG_RTC_DRV_GAMECUBE) += rtc-gamecube.o
obj-$(CONFIG_RTC_DRV_NCT3018Y) += rtc-nct3018y.o
+obj-$(CONFIG_RTC_DRV_NCT6694) += rtc-nct6694.o
obj-$(CONFIG_RTC_DRV_NTXEC) += rtc-ntxec.o
obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_OPAL) += rtc-opal.o
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
new file mode 100644
index 00000000000000..35401a0d9cf53b
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/bcd.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 RTC controller.
+ * This defines the module type used for communication with the NCT6694
+ * RTC controller over the USB interface.
+ */
+#define NCT6694_RTC_MOD 0x08
+
+/* Command 00h - RTC Time */
+#define NCT6694_RTC_TIME 0x0000
+#define NCT6694_RTC_TIME_SEL 0x00
+
+/* Command 01h - RTC Alarm */
+#define NCT6694_RTC_ALARM 0x01
+#define NCT6694_RTC_ALARM_SEL 0x00
+
+/* Command 02h - RTC Status */
+#define NCT6694_RTC_STATUS 0x02
+#define NCT6694_RTC_STATUS_SEL 0x00
+
+#define NCT6694_RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */
+#define NCT6694_RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */
+
+#define NCT6694_RTC_IRQ_EN (NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_EN)
+#define NCT6694_RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */
+
+struct __packed nct6694_rtc_time {
+ u8 sec;
+ u8 min;
+ u8 hour;
+ u8 week;
+ u8 day;
+ u8 month;
+ u8 year;
+};
+
+struct __packed nct6694_rtc_alarm {
+ u8 sec;
+ u8 min;
+ u8 hour;
+ u8 alarm_en;
+ u8 alarm_pend;
+};
+
+struct __packed nct6694_rtc_status {
+ u8 irq_en;
+ u8 irq_pend;
+};
+
+union __packed nct6694_rtc_msg {
+ struct nct6694_rtc_time time;
+ struct nct6694_rtc_alarm alarm;
+ struct nct6694_rtc_status sts;
+};
+
+struct nct6694_rtc_data {
+ struct nct6694 *nct6694;
+ struct rtc_device *rtc;
+ union nct6694_rtc_msg *msg;
+ int irq;
+};
+
+static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ struct nct6694_rtc_time *time = &data->msg->time;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_TIME,
+ .sel = NCT6694_RTC_TIME_SEL,
+ .len = cpu_to_le16(sizeof(*time))
+ };
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, time);
+ if (ret)
+ return ret;
+
+ tm->tm_sec = bcd2bin(time->sec); /* tm_sec expect 0 ~ 59 */
+ tm->tm_min = bcd2bin(time->min); /* tm_min expect 0 ~ 59 */
+ tm->tm_hour = bcd2bin(time->hour); /* tm_hour expect 0 ~ 23 */
+ tm->tm_wday = bcd2bin(time->week) - 1; /* tm_wday expect 0 ~ 6 */
+ tm->tm_mday = bcd2bin(time->day); /* tm_mday expect 1 ~ 31 */
+ tm->tm_mon = bcd2bin(time->month) - 1; /* tm_month expect 0 ~ 11 */
+ tm->tm_year = bcd2bin(time->year) + 100; /* tm_year expect since 1900 */
+
+ return ret;
+}
+
+static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ struct nct6694_rtc_time *time = &data->msg->time;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_TIME,
+ .sel = NCT6694_RTC_TIME_SEL,
+ .len = cpu_to_le16(sizeof(*time))
+ };
+
+ time->sec = bin2bcd(tm->tm_sec);
+ time->min = bin2bcd(tm->tm_min);
+ time->hour = bin2bcd(tm->tm_hour);
+ time->week = bin2bcd(tm->tm_wday + 1);
+ time->day = bin2bcd(tm->tm_mday);
+ time->month = bin2bcd(tm->tm_mon + 1);
+ time->year = bin2bcd(tm->tm_year - 100);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, time);
+}
+
+static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ struct nct6694_rtc_alarm *alarm = &data->msg->alarm;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_ALARM,
+ .sel = NCT6694_RTC_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(*alarm))
+ };
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, alarm);
+ if (ret)
+ return ret;
+
+ alrm->time.tm_sec = bcd2bin(alarm->sec);
+ alrm->time.tm_min = bcd2bin(alarm->min);
+ alrm->time.tm_hour = bcd2bin(alarm->hour);
+ alrm->enabled = alarm->alarm_en;
+ alrm->pending = alarm->alarm_pend;
+
+ return ret;
+}
+
+static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ struct nct6694_rtc_alarm *alarm = &data->msg->alarm;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_ALARM,
+ .sel = NCT6694_RTC_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(*alarm))
+ };
+
+ alarm->sec = bin2bcd(alrm->time.tm_sec);
+ alarm->min = bin2bcd(alrm->time.tm_min);
+ alarm->hour = bin2bcd(alrm->time.tm_hour);
+ alarm->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0;
+ alarm->alarm_pend = 0;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, alarm);
+}
+
+static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ struct nct6694_rtc_status *sts = &data->msg->sts;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_STATUS,
+ .sel = NCT6694_RTC_STATUS_SEL,
+ .len = cpu_to_le16(sizeof(*sts))
+ };
+
+ if (enabled)
+ sts->irq_en |= NCT6694_RTC_IRQ_EN;
+ else
+ sts->irq_en &= ~NCT6694_RTC_IRQ_EN;
+
+ sts->irq_pend = 0;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+}
+
+static const struct rtc_class_ops nct6694_rtc_ops = {
+ .read_time = nct6694_rtc_read_time,
+ .set_time = nct6694_rtc_set_time,
+ .read_alarm = nct6694_rtc_read_alarm,
+ .set_alarm = nct6694_rtc_set_alarm,
+ .alarm_irq_enable = nct6694_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t nct6694_irq(int irq, void *dev_id)
+{
+ struct nct6694_rtc_data *data = dev_id;
+ struct nct6694_rtc_status *sts = &data->msg->sts;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_RTC_MOD,
+ .cmd = NCT6694_RTC_STATUS,
+ .sel = NCT6694_RTC_STATUS_SEL,
+ .len = cpu_to_le16(sizeof(*sts))
+ };
+ int ret;
+
+ rtc_lock(data->rtc);
+
+ sts->irq_en = NCT6694_RTC_IRQ_EN;
+ sts->irq_pend = NCT6694_RTC_IRQ_STS;
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+ if (ret) {
+ rtc_unlock(data->rtc);
+ return IRQ_NONE;
+ }
+
+ rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF);
+
+ rtc_unlock(data->rtc);
+
+ return IRQ_HANDLED;
+}
+
+static void nct6694_irq_dispose_mapping(void *d)
+{
+ struct nct6694_rtc_data *data = d;
+
+ irq_dispose_mapping(data->irq);
+}
+
+static int nct6694_rtc_probe(struct platform_device *pdev)
+{
+ struct nct6694_rtc_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
+ if (!data->irq)
+ return -EINVAL;
+
+ ret = devm_add_action_or_reset(&pdev->dev, nct6694_irq_dispose_mapping,
+ data);
+ if (ret)
+ return ret;
+
+ ret = devm_device_init_wakeup(&pdev->dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to init wakeup\n");
+
+ data->rtc = devm_rtc_allocate_device(&pdev->dev);
+ if (IS_ERR(data->rtc))
+ return PTR_ERR(data->rtc);
+
+ data->nct6694 = nct6694;
+ data->rtc->ops = &nct6694_rtc_ops;
+ data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+ data->rtc->range_max = RTC_TIMESTAMP_END_2099;
+
+ platform_set_drvdata(pdev, data);
+
+ ret = devm_request_threaded_irq(&pdev->dev, data->irq, NULL,
+ nct6694_irq, IRQF_ONESHOT,
+ "rtc-nct6694", data);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+ return devm_rtc_register_device(data->rtc);
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+ .driver = {
+ .name = "nct6694-rtc",
+ },
+ .probe = nct6694_rtc_probe,
+};
+
+module_platform_driver(nct6694_rtc_driver);
+
+MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-rtc");
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 0c25b2ed44eb4f..05008d937e405b 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -760,6 +760,17 @@ config MAX77620_WATCHDOG
MAX77620 chips. To compile this driver as a module,
choose M here: the module will be called max77620_wdt.
+config NCT6694_WATCHDOG
+ tristate "Nuvoton NCT6694 watchdog support"
+ depends on MFD_NCT6694
+ select WATCHDOG_CORE
+ help
+ Say Y here to support Nuvoton NCT6694 watchdog timer
+ functionality.
+
+ This driver can also be built as a module. If so, the module
+ will be called nct6694_wdt.
+
config IMX2_WDT
tristate "IMX2+ Watchdog"
depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index bbd4d62d2cc3bf..b680e4d3c1bc20 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -235,6 +235,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
+obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o
obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
new file mode 100644
index 00000000000000..bc3689bd4b6bb0
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ */
+
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define DEVICE_NAME "nct6694-wdt"
+
+#define NCT6694_DEFAULT_TIMEOUT 10
+#define NCT6694_DEFAULT_PRETIMEOUT 0
+
+#define NCT6694_WDT_MAX_DEVS 2
+
+/*
+ * USB command module type for NCT6694 WDT controller.
+ * This defines the module type used for communication with the NCT6694
+ * WDT controller over the USB interface.
+ */
+#define NCT6694_WDT_MOD 0x07
+
+/* Command 00h - WDT Setup */
+#define NCT6694_WDT_SETUP 0x00
+#define NCT6694_WDT_SETUP_SEL(idx) (idx ? 0x01 : 0x00)
+
+/* Command 01h - WDT Command */
+#define NCT6694_WDT_COMMAND 0x01
+#define NCT6694_WDT_COMMAND_SEL(idx) (idx ? 0x01 : 0x00)
+
+static unsigned int timeout[NCT6694_WDT_MAX_DEVS] = {
+ [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_TIMEOUT
+};
+module_param_array(timeout, int, NULL, 0644);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] = {
+ [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_PRETIMEOUT
+};
+module_param_array(pretimeout, int, NULL, 0644);
+MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum {
+ NCT6694_ACTION_NONE = 0,
+ NCT6694_ACTION_SIRQ,
+ NCT6694_ACTION_GPO,
+};
+
+struct __packed nct6694_wdt_setup {
+ __le32 pretimeout;
+ __le32 timeout;
+ u8 owner;
+ u8 scratch;
+ u8 control;
+ u8 status;
+ __le32 countdown;
+};
+
+struct __packed nct6694_wdt_cmd {
+ __le32 wdt_cmd;
+ __le32 reserved;
+};
+
+union __packed nct6694_wdt_msg {
+ struct nct6694_wdt_setup setup;
+ struct nct6694_wdt_cmd cmd;
+};
+
+struct nct6694_wdt_data {
+ struct watchdog_device wdev;
+ struct device *dev;
+ struct nct6694 *nct6694;
+ union nct6694_wdt_msg *msg;
+ unsigned char wdev_idx;
+};
+
+static int nct6694_wdt_setting(struct watchdog_device *wdev,
+ u32 timeout_val, u8 timeout_act,
+ u32 pretimeout_val, u8 pretimeout_act)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_setup *setup = &data->msg->setup;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_WDT_MOD,
+ .cmd = NCT6694_WDT_SETUP,
+ .sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+ .len = cpu_to_le16(sizeof(*setup))
+ };
+ unsigned int timeout_fmt, pretimeout_fmt;
+
+ if (pretimeout_val == 0)
+ pretimeout_act = NCT6694_ACTION_NONE;
+
+ timeout_fmt = (timeout_val * 1000) | (timeout_act << 24);
+ pretimeout_fmt = (pretimeout_val * 1000) | (pretimeout_act << 24);
+
+ memset(setup, 0, sizeof(*setup));
+ setup->timeout = cpu_to_le32(timeout_fmt);
+ setup->pretimeout = cpu_to_le32(pretimeout_fmt);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, setup);
+}
+
+static int nct6694_wdt_start(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ int ret;
+
+ ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+ wdev->pretimeout, NCT6694_ACTION_GPO);
+ if (ret)
+ return ret;
+
+ dev_dbg(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
+ data->wdev_idx, wdev->timeout, wdev->pretimeout);
+
+ return ret;
+}
+
+static int nct6694_wdt_stop(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_WDT_MOD,
+ .cmd = NCT6694_WDT_COMMAND,
+ .sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+ .len = cpu_to_le16(sizeof(*cmd))
+ };
+
+ memcpy(&cmd->wdt_cmd, "WDTC", 4);
+ cmd->reserved = 0;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_WDT_MOD,
+ .cmd = NCT6694_WDT_COMMAND,
+ .sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+ .len = cpu_to_le16(sizeof(*cmd))
+ };
+
+ memcpy(&cmd->wdt_cmd, "WDTS", 4);
+ cmd->reserved = 0;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int new_timeout)
+{
+ int ret;
+
+ ret = nct6694_wdt_setting(wdev, new_timeout, NCT6694_ACTION_GPO,
+ wdev->pretimeout, NCT6694_ACTION_GPO);
+ if (ret)
+ return ret;
+
+ wdev->timeout = new_timeout;
+
+ return 0;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+ unsigned int new_pretimeout)
+{
+ int ret;
+
+ ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+ new_pretimeout, NCT6694_ACTION_GPO);
+ if (ret)
+ return ret;
+
+ wdev->pretimeout = new_pretimeout;
+
+ return 0;
+}
+
+static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_setup *setup = &data->msg->setup;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_WDT_MOD,
+ .cmd = NCT6694_WDT_SETUP,
+ .sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+ .len = cpu_to_le16(sizeof(*setup))
+ };
+ unsigned int timeleft_ms;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, setup);
+ if (ret)
+ return 0;
+
+ timeleft_ms = le32_to_cpu(setup->countdown);
+
+ return timeleft_ms / 1000;
+}
+
+static const struct watchdog_info nct6694_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE |
+ WDIOF_PRETIMEOUT,
+ .identity = DEVICE_NAME,
+};
+
+static const struct watchdog_ops nct6694_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = nct6694_wdt_start,
+ .stop = nct6694_wdt_stop,
+ .set_timeout = nct6694_wdt_set_timeout,
+ .set_pretimeout = nct6694_wdt_set_pretimeout,
+ .get_timeleft = nct6694_wdt_get_time,
+ .ping = nct6694_wdt_ping,
+};
+
+static void nct6694_wdt_ida_free(void *d)
+{
+ struct nct6694_wdt_data *data = d;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ ida_free(&nct6694->wdt_ida, data->wdev_idx);
+}
+
+static int nct6694_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
+ struct nct6694_wdt_data *data;
+ struct watchdog_device *wdev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->nct6694 = nct6694;
+
+ ret = ida_alloc(&nct6694->wdt_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+ data->wdev_idx = ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_wdt_ida_free, data);
+ if (ret)
+ return ret;
+
+ wdev = &data->wdev;
+ wdev->info = &nct6694_wdt_info;
+ wdev->ops = &nct6694_wdt_ops;
+ wdev->timeout = timeout[data->wdev_idx];
+ wdev->pretimeout = pretimeout[data->wdev_idx];
+ if (timeout[data->wdev_idx] < pretimeout[data->wdev_idx]) {
+ dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+ wdev->pretimeout = 0;
+ }
+
+ wdev->min_timeout = 1;
+ wdev->max_timeout = 255;
+
+ platform_set_drvdata(pdev, data);
+
+ watchdog_set_drvdata(&data->wdev, data);
+ watchdog_set_nowayout(&data->wdev, nowayout);
+ watchdog_stop_on_reboot(&data->wdev);
+
+ return devm_watchdog_register_device(dev, &data->wdev);
+}
+
+static struct platform_driver nct6694_wdt_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ },
+ .probe = nct6694_wdt_probe,
+};
+
+module_platform_driver(nct6694_wdt_driver);
+
+MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-wdt");
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
new file mode 100644
index 00000000000000..6eb9be2cd4a011
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 USB transaction and data structure.
+ */
+
+#ifndef __MFD_NCT6694_H
+#define __MFD_NCT6694_H
+
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_HCTRL_SET 0x40
+#define NCT6694_HCTRL_GET 0x80
+
+#define NCT6694_URB_TIMEOUT 1000
+
+enum nct6694_irq_id {
+ NCT6694_IRQ_GPIO0 = 0,
+ NCT6694_IRQ_GPIO1,
+ NCT6694_IRQ_GPIO2,
+ NCT6694_IRQ_GPIO3,
+ NCT6694_IRQ_GPIO4,
+ NCT6694_IRQ_GPIO5,
+ NCT6694_IRQ_GPIO6,
+ NCT6694_IRQ_GPIO7,
+ NCT6694_IRQ_GPIO8,
+ NCT6694_IRQ_GPIO9,
+ NCT6694_IRQ_GPIOA,
+ NCT6694_IRQ_GPIOB,
+ NCT6694_IRQ_GPIOC,
+ NCT6694_IRQ_GPIOD,
+ NCT6694_IRQ_GPIOE,
+ NCT6694_IRQ_GPIOF,
+ NCT6694_IRQ_CAN0,
+ NCT6694_IRQ_CAN1,
+ NCT6694_IRQ_RTC,
+ NCT6694_NR_IRQS,
+};
+
+enum nct6694_response_err_status {
+ NCT6694_NO_ERROR = 0,
+ NCT6694_FORMAT_ERROR,
+ NCT6694_RESERVED1,
+ NCT6694_RESERVED2,
+ NCT6694_NOT_SUPPORT_ERROR,
+ NCT6694_NO_RESPONSE_ERROR,
+ NCT6694_TIMEOUT_ERROR,
+ NCT6694_PENDING,
+};
+
+struct __packed nct6694_cmd_header {
+ u8 rsv1;
+ u8 mod;
+ union __packed {
+ __le16 offset;
+ struct __packed {
+ u8 cmd;
+ u8 sel;
+ };
+ };
+ u8 hctrl;
+ u8 rsv2;
+ __le16 len;
+};
+
+struct __packed nct6694_response_header {
+ u8 sequence_id;
+ u8 sts;
+ u8 reserved[4];
+ __le16 len;
+};
+
+union __packed nct6694_usb_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694 {
+ struct device *dev;
+ struct ida gpio_ida;
+ struct ida i2c_ida;
+ 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;
+};
+
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+
+#endif