* [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-02-28 8:52 ` Marc Kleine-Budde
2025-03-07 1:15 ` Lee Jones
2025-02-25 8:16 ` [PATCH v8 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
` (5 subsequent siblings)
6 siblings, 2 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
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 implements USB device functionality and shares the
chip's peripherals as a child device.
Each child device can use the USB functions nct6694_read_msg()
and nct6694_write_msg() to issue a command. They can also request
interrupt that will be called when the USB device receives its
interrupt pipe.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 7 +
drivers/mfd/Kconfig | 18 ++
drivers/mfd/Makefile | 2 +
drivers/mfd/nct6694.c | 378 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/nct6694.h | 102 ++++++++++
5 files changed, 507 insertions(+)
create mode 100644 drivers/mfd/nct6694.c
create mode 100644 include/linux/mfd/nct6694.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 873aa2cce4d7..c700a0b96960 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16918,6 +16918,13 @@ F: drivers/nubus/
F: include/linux/nubus.h
F: include/uapi/linux/nubus.h
+NUVOTON NCT6694 MFD DRIVER
+M: Ming Yu <tmyu0@nuvoton.com>
+L: linux-kernel@vger.kernel.org
+S: Supported
+F: drivers/mfd/nct6694.c
+F: include/linux/mfd/nct6694.h
+
NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
M: Antonino Daplas <adaplas@gmail.com>
L: linux-fbdev@vger.kernel.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6b0682af6e32..c97a2bdcea0b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1045,6 +1045,24 @@ 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.
+
+ This driver provides core APIs to access the NCT6694 hardware
+ monitoring and control features.
+
+ The NCT6694 is a versatile multi-function device that supports
+ functionalities such as GPIO, I2C, CAN, WDT, HWMON, and RTC
+ management.
+
+ 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 9220eaf7cf12..7725b732e265 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 000000000000..c82457679ca6
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 core driver using USB interface to provide
+ * access to the NCT6694 hardware monitoring and control features.
+ *
+ * The NCT6694 is a versatile multi-function device that supports
+ * functionalities such as GPIO, I2C, CAN, WDT, HWMON and RTC
+ * management.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+static const struct mfd_cell nct6694_dev[] = {
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x0),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
+ MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
+
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x0),
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x1),
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x2),
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x3),
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x4),
+ MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x5),
+
+ MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x0),
+ MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x1),
+
+ MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x0),
+ MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x1),
+
+ MFD_CELL_NAME("nct6694-hwmon"),
+ MFD_CELL_NAME("rtc-nct6694"),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694,
+ unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return err_status;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_warn(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_warn(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+ &msg->cmd_header, sizeof(*msg), &tx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+ &msg->response_header, sizeof(*msg), &rx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+ buf, le16_to_cpu(cmd_hd->len), &rx_len,
+ nct6694->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(nct6694_read_msg);
+
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+ &msg->cmd_header, sizeof(*msg), &tx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Send data packet to USB device */
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+ buf, le16_to_cpu(cmd_hd->len), &tx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+ &msg->response_header, sizeof(*msg), &rx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(nct6694->udev,
+ usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+ buf, le16_to_cpu(cmd_hd->len), &rx_len,
+ nct6694->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(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+ struct nct6694 *nct6694 = urb->context;
+ unsigned int *int_status = urb->transfer_buffer;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default:
+ goto resubmit;
+ }
+
+ while (*int_status) {
+ int irq = __ffs(*int_status);
+
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+ *int_status &= ~BIT(irq);
+ }
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ dev_dbg(nct6694->dev, "%s: Failed to resubmit urb, status %pe",
+ __func__, ERR_PTR(ret));
+}
+
+static void nct6694_irq_lock(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_sync_unlock(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+ mutex_unlock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-irq",
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+ .irq_bus_lock = nct6694_irq_lock,
+ .irq_bus_sync_unlock = nct6694_irq_sync_unlock,
+ .irq_enable = nct6694_irq_enable,
+ .irq_disable = nct6694_irq_disable,
+};
+
+static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ struct nct6694 *nct6694 = d->host_data;
+
+ irq_set_chip_data(irq, nct6694);
+ irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+ return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+ .map = nct6694_irq_domain_map,
+ .unmap = nct6694_irq_domain_unmap,
+};
+
+static int nct6694_usb_probe(struct usb_interface *iface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct usb_endpoint_descriptor *int_endpoint;
+ struct usb_host_interface *interface;
+ struct device *dev = &iface->dev;
+ struct nct6694 *nct6694;
+ int pipe, maxp;
+ int ret;
+
+ nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+ if (!nct6694)
+ return -ENOMEM;
+
+ pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
+ maxp = usb_maxpacket(udev, pipe);
+
+ nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
+ if (!nct6694->usb_msg)
+ return -ENOMEM;
+
+ nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
+ if (!nct6694->int_buffer)
+ return -ENOMEM;
+
+ nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!nct6694->int_in_urb)
+ return -ENOMEM;
+
+ nct6694->domain = irq_domain_add_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;
+ nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until URB completes */
+
+ ret = devm_mutex_init(dev, &nct6694->access_lock);
+ if (ret)
+ goto err_urb;
+
+ ret = devm_mutex_init(dev, &nct6694->irq_lock);
+ if (ret)
+ goto err_urb;
+
+ interface = iface->cur_altsetting;
+ int_endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(int_endpoint)) {
+ ret = -ENODEV;
+ goto err_urb;
+ }
+ usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
+ nct6694->int_buffer, maxp, usb_int_callback,
+ nct6694, int_endpoint->bInterval);
+ ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ if (ret)
+ goto err_urb;
+
+ usb_set_intfdata(iface, nct6694);
+
+ ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
+ if (ret)
+ goto err_mfd;
+
+ return 0;
+
+err_mfd:
+ usb_kill_urb(nct6694->int_in_urb);
+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);
+ 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("USB core driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
new file mode 100644
index 000000000000..8171f975761e
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Nuvoton NCT6694 USB transaction and data structure.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#ifndef __MFD_NCT6694_H
+#define __MFD_NCT6694_H
+
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_HCTRL_SET 0x40
+#define NCT6694_HCTRL_GET 0x80
+
+#define NCT6694_URB_TIMEOUT 1000
+
+enum nct6694_irq_id {
+ NCT6694_IRQ_GPIO0 = 0,
+ NCT6694_IRQ_GPIO1,
+ NCT6694_IRQ_GPIO2,
+ NCT6694_IRQ_GPIO3,
+ NCT6694_IRQ_GPIO4,
+ NCT6694_IRQ_GPIO5,
+ NCT6694_IRQ_GPIO6,
+ NCT6694_IRQ_GPIO7,
+ NCT6694_IRQ_GPIO8,
+ NCT6694_IRQ_GPIO9,
+ NCT6694_IRQ_GPIOA,
+ NCT6694_IRQ_GPIOB,
+ NCT6694_IRQ_GPIOC,
+ NCT6694_IRQ_GPIOD,
+ NCT6694_IRQ_GPIOE,
+ NCT6694_IRQ_GPIOF,
+ NCT6694_IRQ_CAN0,
+ NCT6694_IRQ_CAN1,
+ NCT6694_IRQ_RTC,
+ NCT6694_NR_IRQS,
+};
+
+enum nct6694_response_err_status {
+ NCT6694_NO_ERROR = 0,
+ NCT6694_FORMAT_ERROR,
+ NCT6694_RESERVED1,
+ NCT6694_RESERVED2,
+ NCT6694_NOT_SUPPORT_ERROR,
+ NCT6694_NO_RESPONSE_ERROR,
+ NCT6694_TIMEOUT_ERROR,
+ NCT6694_PENDING,
+};
+
+struct __packed nct6694_cmd_header {
+ u8 rsv1;
+ u8 mod;
+ union __packed {
+ __le16 offset;
+ struct __packed {
+ u8 cmd;
+ u8 sel;
+ };
+ };
+ u8 hctrl;
+ u8 rsv2;
+ __le16 len;
+};
+
+struct __packed nct6694_response_header {
+ u8 sequence_id;
+ u8 sts;
+ u8 reserved[4];
+ __le16 len;
+};
+
+union __packed nct6694_usb_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694 {
+ struct device *dev;
+ struct irq_domain *domain;
+ /* Mutex to protect access to the device */
+ struct mutex access_lock;
+ /* Mutex to protect access to the IRQ */
+ struct mutex irq_lock;
+ struct urb *int_in_urb;
+ struct usb_device *udev;
+ union nct6694_usb_msg *usb_msg;
+ unsigned char *int_buffer;
+ unsigned int irq_enable;
+ /* Time in msec to wait for the URB to the complete */
+ long timeout;
+};
+
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-02-25 8:16 ` [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2025-02-28 8:52 ` Marc Kleine-Budde
2025-03-17 2:26 ` Ming Yu
2025-03-07 1:15 ` Lee Jones
1 sibling, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-02-28 8:52 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 1749 bytes --]
On 25.02.2025 16:16:38, Ming Yu wrote:
[...]
> +static int nct6694_usb_probe(struct usb_interface *iface,
> + const struct usb_device_id *id)
> +{
> + struct usb_device *udev = interface_to_usbdev(iface);
> + struct usb_endpoint_descriptor *int_endpoint;
> + struct usb_host_interface *interface;
> + struct device *dev = &iface->dev;
> + struct nct6694 *nct6694;
> + int pipe, maxp;
> + int ret;
> +
> + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> + if (!nct6694)
> + return -ENOMEM;
> +
> + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> + maxp = usb_maxpacket(udev, pipe);
> +
> + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
> + if (!nct6694->usb_msg)
> + return -ENOMEM;
> +
> + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
> + if (!nct6694->int_buffer)
> + return -ENOMEM;
> +
> + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (!nct6694->int_in_urb)
> + return -ENOMEM;
> +
> + nct6694->domain = irq_domain_add_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;
> + nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until URB completes */
Why do you need this variable? You can directly use NCT6694_URB_TIMEOUT
in the usb_bulk_msg() and friends calls.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-02-28 8:52 ` Marc Kleine-Budde
@ 2025-03-17 2:26 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-03-17 2:26 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Dear Marc,
Thank you for reviewing,
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年2月28日 週五 下午4:52寫道:
>
> > +static int nct6694_usb_probe(struct usb_interface *iface,
> > + const struct usb_device_id *id)
> > +{
> > + struct usb_device *udev = interface_to_usbdev(iface);
> > + struct usb_endpoint_descriptor *int_endpoint;
> > + struct usb_host_interface *interface;
> > + struct device *dev = &iface->dev;
> > + struct nct6694 *nct6694;
> > + int pipe, maxp;
> > + int ret;
> > +
> > + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> > + if (!nct6694)
> > + return -ENOMEM;
> > +
> > + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> > + maxp = usb_maxpacket(udev, pipe);
> > +
> > + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
> > + if (!nct6694->usb_msg)
> > + return -ENOMEM;
> > +
> > + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
> > + if (!nct6694->int_buffer)
> > + return -ENOMEM;
> > +
> > + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> > + if (!nct6694->int_in_urb)
> > + return -ENOMEM;
> > +
> > + nct6694->domain = irq_domain_add_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;
> > + nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until URB completes */
>
> Why do you need this variable? You can directly use NCT6694_URB_TIMEOUT
> in the usb_bulk_msg() and friends calls.
>
Okay, I will make the modifications in the next patch.
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-02-25 8:16 ` [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2025-02-28 8:52 ` Marc Kleine-Budde
@ 2025-03-07 1:15 ` Lee Jones
2025-03-17 2:57 ` Ming Yu
1 sibling, 1 reply; 41+ messages in thread
From: Lee Jones @ 2025-03-07 1:15 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
On Tue, 25 Feb 2025, Ming Yu wrote:
> 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 needs to go into the Kconfig help passage.
> This driver implements USB device functionality and shares the
> chip's peripherals as a child device.
This driver doesn't implement USB functionality.
> Each child device can use the USB functions nct6694_read_msg()
> and nct6694_write_msg() to issue a command. They can also request
> interrupt that will be called when the USB device receives its
> interrupt pipe.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
Why aren't you signing off with your work address?
> ---
> MAINTAINERS | 7 +
> drivers/mfd/Kconfig | 18 ++
> drivers/mfd/Makefile | 2 +
> drivers/mfd/nct6694.c | 378 ++++++++++++++++++++++++++++++++++++
> include/linux/mfd/nct6694.h | 102 ++++++++++
> 5 files changed, 507 insertions(+)
> create mode 100644 drivers/mfd/nct6694.c
> create mode 100644 include/linux/mfd/nct6694.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 873aa2cce4d7..c700a0b96960 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16918,6 +16918,13 @@ F: drivers/nubus/
> F: include/linux/nubus.h
> F: include/uapi/linux/nubus.h
>
> +NUVOTON NCT6694 MFD DRIVER
> +M: Ming Yu <tmyu0@nuvoton.com>
> +L: linux-kernel@vger.kernel.org
This is the default list. You shouldn't need to add that here.
> +S: Supported
> +F: drivers/mfd/nct6694.c
> +F: include/linux/mfd/nct6694.h
> +
> NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> M: Antonino Daplas <adaplas@gmail.com>
> L: linux-fbdev@vger.kernel.org
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 6b0682af6e32..c97a2bdcea0b 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1045,6 +1045,24 @@ 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.
> +
> + This driver provides core APIs to access the NCT6694 hardware
> + monitoring and control features.
> +
> + The NCT6694 is a versatile multi-function device that supports
Please drop the term multi-function device and replace it what a proper
description of the devices.
> + functionalities such as GPIO, I2C, CAN, WDT, HWMON, and RTC
> + management.
All of these line breaks should be removed.
> + 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 9220eaf7cf12..7725b732e265 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 000000000000..c82457679ca6
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,378 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 core driver using USB interface to provide
> + * access to the NCT6694 hardware monitoring and control features.
> + *
> + * The NCT6694 is a versatile multi-function device that supports
Here too.
> + * functionalities such as GPIO, I2C, CAN, WDT, HWMON and RTC
> + * management.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
This goes at the top.
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +
> +static const struct mfd_cell nct6694_dev[] = {
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x0),
"-gpio" usually goes on the end.
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
IDs are usually given in base-10.
Why are you manually adding the device IDs?
PLATFORM_DEVID_AUTO doesn't work for you?
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
> + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
> +
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x0),
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x1),
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x2),
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x3),
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x4),
> + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x5),
> +
> + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x0),
Why has the naming convention changed here?
> + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x1),
> +
> + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x0),
> + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x1),
> +
> + MFD_CELL_NAME("nct6694-hwmon"),
> + MFD_CELL_NAME("rtc-nct6694"),
There doesn't seem to be any consistency here.
> +};
> +
> +static int nct6694_response_err_handling(struct nct6694 *nct6694,
> +{
> + switch (err_status) {
> + case NCT6694_NO_ERROR:
> + return err_status;
This is odd since you already know this will be 0.
> + case NCT6694_NOT_SUPPORT_ERROR:
> + dev_warn(nct6694->dev, "Command is not supported!\n");
Why not dev_err()?
> + 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_warn(nct6694->dev, "Command is pending!\n");
Is this an error?
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return -EIO;
> +}
> +
> +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> +{
> + union nct6694_usb_msg *msg = nct6694->usb_msg;
> + int tx_len, rx_len, ret;
> +
> + guard(mutex)(&nct6694->access_lock);
> +
> + /* Send command packet to USB device */
This doesn't really describe the next 2 lines.
Move it down?
> + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> + msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
> +
> + ret = usb_bulk_msg(nct6694->udev,
Since you use nct6694->udev a bunch - sometimes twice in the same call,
it might be nicer to pull it into it's own variable instead of
dereferencing it all the time.
> + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> + &msg->cmd_header, sizeof(*msg), &tx_len,
> + nct6694->timeout);
> + if (ret)
> + return ret;
> +
> + /* Receive response packet from USB device */
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> + &msg->response_header, sizeof(*msg), &rx_len,
How can you read sizeof(*msg) Bytes (22?) into the smaller
response_header (16?) attribute?
> + nct6694->timeout);
> + if (ret)
> + return ret;
> +
> + /* Receive data packet from USB device */
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> + buf, le16_to_cpu(cmd_hd->len), &rx_len,
> + nct6694->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(nct6694_read_msg);
> +
> +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> +{
> + union nct6694_usb_msg *msg = nct6694->usb_msg;
> + int tx_len, rx_len, ret;
> +
> + guard(mutex)(&nct6694->access_lock);
> +
> + /* Send command packet to USB device */
> + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> + msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
> +
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> + &msg->cmd_header, sizeof(*msg), &tx_len,
> + nct6694->timeout);
> + if (ret)
> + return ret;
> +
> + /* Send data packet to USB device */
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> + buf, le16_to_cpu(cmd_hd->len), &tx_len,
> + nct6694->timeout);
> + if (ret)
> + return ret;
> +
> + /* Receive response packet from USB device */
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> + &msg->response_header, sizeof(*msg), &rx_len,
> + nct6694->timeout);
> + if (ret)
> + return ret;
> +
> + /* Receive data packet from USB device */
> + ret = usb_bulk_msg(nct6694->udev,
> + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> + buf, le16_to_cpu(cmd_hd->len), &rx_len,
> + nct6694->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(nct6694_write_msg);
> +
> +static void usb_int_callback(struct urb *urb)
> +{
> + struct nct6694 *nct6694 = urb->context;
> + unsigned int *int_status = urb->transfer_buffer;
> + int ret;
> +
> + switch (urb->status) {
> + case 0:
> + break;
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + return;
> + default:
> + 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_dbg(nct6694->dev, "%s: Failed to resubmit urb, status %pe",
Why debug?
> + __func__, ERR_PTR(ret));
Remove the __func__ part.
> +}
> +
> +static void nct6694_irq_lock(struct irq_data *data)
> +{
> + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +
> + mutex_lock(&nct6694->irq_lock);
> +}
> +
> +static void nct6694_irq_sync_unlock(struct irq_data *data)
> +{
> + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> +
> + mutex_unlock(&nct6694->irq_lock);
> +}
> +
> +static void nct6694_irq_enable(struct irq_data *data)
> +{
> + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> + irq_hw_number_t hwirq = irqd_to_hwirq(data);
> +
> + nct6694->irq_enable |= BIT(hwirq);
> +}
> +
> +static void nct6694_irq_disable(struct irq_data *data)
> +{
> + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
> + irq_hw_number_t hwirq = irqd_to_hwirq(data);
> +
> + nct6694->irq_enable &= ~BIT(hwirq);
> +}
> +
> +static const struct irq_chip nct6694_irq_chip = {
> + .name = "nct6694-irq",
> + .flags = IRQCHIP_SKIP_SET_WAKE,
> + .irq_bus_lock = nct6694_irq_lock,
> + .irq_bus_sync_unlock = nct6694_irq_sync_unlock,
> + .irq_enable = nct6694_irq_enable,
> + .irq_disable = nct6694_irq_disable,
> +};
> +
> +static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq,
> + irq_hw_number_t hw)
> +{
> + struct nct6694 *nct6694 = d->host_data;
> +
> + irq_set_chip_data(irq, nct6694);
> + irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
> +
> + return 0;
> +}
> +
> +static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
> +{
> + irq_set_chip_and_handler(irq, NULL, NULL);
> + irq_set_chip_data(irq, NULL);
> +}
> +
> +static const struct irq_domain_ops nct6694_irq_domain_ops = {
> + .map = nct6694_irq_domain_map,
> + .unmap = nct6694_irq_domain_unmap,
> +};
> +
> +static int nct6694_usb_probe(struct usb_interface *iface,
> + const struct usb_device_id *id)
> +{
> + struct usb_device *udev = interface_to_usbdev(iface);
> + struct usb_endpoint_descriptor *int_endpoint;
> + struct usb_host_interface *interface;
> + struct device *dev = &iface->dev;
> + struct nct6694 *nct6694;
> + int pipe, maxp;
> + int ret;
> +
> + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> + if (!nct6694)
> + return -ENOMEM;
> +
> + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> + maxp = usb_maxpacket(udev, pipe);
> +
> + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
> + if (!nct6694->usb_msg)
> + return -ENOMEM;
> +
> + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
> + if (!nct6694->int_buffer)
> + return -ENOMEM;
> +
> + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (!nct6694->int_in_urb)
> + return -ENOMEM;
> +
> + nct6694->domain = irq_domain_add_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;
> + nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until URB completes */
No need to save this known value.
> + ret = devm_mutex_init(dev, &nct6694->access_lock);
> + if (ret)
> + goto err_urb;
> +
> + ret = devm_mutex_init(dev, &nct6694->irq_lock);
> + if (ret)
> + goto err_urb;
> +
> + interface = iface->cur_altsetting;
> + int_endpoint = &interface->endpoint[0].desc;
> + if (!usb_endpoint_is_int_in(int_endpoint)) {
> + ret = -ENODEV;
> + goto err_urb;
> + }
> + usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
> + nct6694->int_buffer, maxp, usb_int_callback,
> + nct6694, int_endpoint->bInterval);
> + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
> + if (ret)
> + goto err_urb;
Please unsquash these calls - space them out.
> +
> + usb_set_intfdata(iface, nct6694);
> +
> + ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
> + if (ret)
> + goto err_mfd;
> +
> + return 0;
> +
> +err_mfd:
> + usb_kill_urb(nct6694->int_in_urb);
> +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);
> + 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)},
This should fit on one line. You can use up to 100-chars.
> + {}
> +};
> +MODULE_DEVICE_TABLE(usb, nct6694_ids);
> +
> +static struct usb_driver nct6694_usb_driver = {
> + .name = "nct6694",
Odd spaces.
> + .id_table = nct6694_ids,
> + .probe = nct6694_usb_probe,
> + .disconnect = nct6694_usb_disconnect,
> +};
> +
Remove this line.
> +module_usb_driver(nct6694_usb_driver);
> +
> +MODULE_DESCRIPTION("USB core driver for NCT6694");
This is not a USB driver.
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
Different to SoB.
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> new file mode 100644
> index 000000000000..8171f975761e
> --- /dev/null
> +++ b/include/linux/mfd/nct6694.h
> @@ -0,0 +1,102 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Nuvoton NCT6694 USB transaction and data structure.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
At the top.
> + */
> +
> +#ifndef __MFD_NCT6694_H
> +#define __MFD_NCT6694_H
> +
> +#define NCT6694_VENDOR_ID 0x0416
> +#define NCT6694_PRODUCT_ID 0x200B
> +#define NCT6694_INT_IN_EP 0x81
> +#define NCT6694_BULK_IN_EP 0x02
> +#define NCT6694_BULK_OUT_EP 0x03
> +
> +#define NCT6694_HCTRL_SET 0x40
> +#define NCT6694_HCTRL_GET 0x80
> +
> +#define NCT6694_URB_TIMEOUT 1000
> +
> +enum nct6694_irq_id {
> + NCT6694_IRQ_GPIO0 = 0,
> + NCT6694_IRQ_GPIO1,
> + NCT6694_IRQ_GPIO2,
> + NCT6694_IRQ_GPIO3,
> + NCT6694_IRQ_GPIO4,
> + NCT6694_IRQ_GPIO5,
> + NCT6694_IRQ_GPIO6,
> + NCT6694_IRQ_GPIO7,
> + NCT6694_IRQ_GPIO8,
> + NCT6694_IRQ_GPIO9,
> + NCT6694_IRQ_GPIOA,
> + NCT6694_IRQ_GPIOB,
> + NCT6694_IRQ_GPIOC,
> + NCT6694_IRQ_GPIOD,
> + NCT6694_IRQ_GPIOE,
> + NCT6694_IRQ_GPIOF,
> + NCT6694_IRQ_CAN0,
> + NCT6694_IRQ_CAN1,
> + NCT6694_IRQ_RTC,
> + NCT6694_NR_IRQS,
> +};
> +
> +enum nct6694_response_err_status {
> + NCT6694_NO_ERROR = 0,
> + NCT6694_FORMAT_ERROR,
> + NCT6694_RESERVED1,
> + NCT6694_RESERVED2,
> + NCT6694_NOT_SUPPORT_ERROR,
> + NCT6694_NO_RESPONSE_ERROR,
> + NCT6694_TIMEOUT_ERROR,
> + NCT6694_PENDING,
> +};
> +
> +struct __packed nct6694_cmd_header {
> + u8 rsv1;
> + u8 mod;
> + union __packed {
> + __le16 offset;
> + struct __packed {
> + u8 cmd;
> + u8 sel;
> + };
> + };
> + u8 hctrl;
> + u8 rsv2;
> + __le16 len;
> +};
> +
> +struct __packed nct6694_response_header {
> + u8 sequence_id;
> + u8 sts;
> + u8 reserved[4];
> + __le16 len;
> +};
> +
> +union __packed nct6694_usb_msg {
> + struct nct6694_cmd_header cmd_header;
> + struct nct6694_response_header response_header;
> +};
> +
> +struct nct6694 {
> + struct device *dev;
> + struct irq_domain *domain;
> + /* Mutex to protect access to the device */
> + struct mutex access_lock;
> + /* Mutex to protect access to the IRQ */
> + struct mutex irq_lock;
> + struct urb *int_in_urb;
> + struct usb_device *udev;
> + union nct6694_usb_msg *usb_msg;
> + unsigned char *int_buffer;
> + unsigned int irq_enable;
> + /* Time in msec to wait for the URB to the complete */
> + long timeout;
timeout_ms
> +};
> +
> +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
> +
> +#endif
> --
> 2.34.1
>
--
Lee Jones [李琼斯]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-03-07 1:15 ` Lee Jones
@ 2025-03-17 2:57 ` Ming Yu
2025-03-20 14:50 ` Lee Jones
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-17 2:57 UTC (permalink / raw)
To: Lee Jones
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Dear Lee,
Thank you for reviewing,
Lee Jones <lee@kernel.org> 於 2025年3月7日 週五 上午9:15寫道:
>
> On Tue, 25 Feb 2025, Ming Yu wrote:
>
> > 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 needs to go into the Kconfig help passage.
>
Okay, I will move these to Kconfig in the next patch.
> > This driver implements USB device functionality and shares the
> > chip's peripherals as a child device.
>
> This driver doesn't implement USB functionality.
>
Fix it in v9.
> > Each child device can use the USB functions nct6694_read_msg()
> > and nct6694_write_msg() to issue a command. They can also request
> > interrupt that will be called when the USB device receives its
> > interrupt pipe.
> >
> > Signed-off-by: Ming Yu <a0282524688@gmail.com>
>
> Why aren't you signing off with your work address?
>
Fix it in v9.
> > ---
> > MAINTAINERS | 7 +
> > drivers/mfd/Kconfig | 18 ++
> > drivers/mfd/Makefile | 2 +
> > drivers/mfd/nct6694.c | 378 ++++++++++++++++++++++++++++++++++++
> > include/linux/mfd/nct6694.h | 102 ++++++++++
> > 5 files changed, 507 insertions(+)
> > create mode 100644 drivers/mfd/nct6694.c
> > create mode 100644 include/linux/mfd/nct6694.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 873aa2cce4d7..c700a0b96960 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16918,6 +16918,13 @@ F: drivers/nubus/
> > F: include/linux/nubus.h
> > F: include/uapi/linux/nubus.h
> >
> > +NUVOTON NCT6694 MFD DRIVER
> > +M: Ming Yu <tmyu0@nuvoton.com>
> > +L: linux-kernel@vger.kernel.org
>
> This is the default list. You shouldn't need to add that here.
>
Remove it in v9.
> > +S: Supported
> > +F: drivers/mfd/nct6694.c
> > +F: include/linux/mfd/nct6694.h
> > +
> > NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> > M: Antonino Daplas <adaplas@gmail.com>
> > L: linux-fbdev@vger.kernel.org
> > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > index 6b0682af6e32..c97a2bdcea0b 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -1045,6 +1045,24 @@ 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.
> > +
> > + This driver provides core APIs to access the NCT6694 hardware
> > + monitoring and control features.
> > +
> > + The NCT6694 is a versatile multi-function device that supports
>
> Please drop the term multi-function device and replace it what a proper
> description of the devices.
>
Fix it in v9.
> > + functionalities such as GPIO, I2C, CAN, WDT, HWMON, and RTC
> > + management.
>
> All of these line breaks should be removed.
>
Fix these in v9.
> > + 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 9220eaf7cf12..7725b732e265 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 000000000000..c82457679ca6
> > --- /dev/null
> > +++ b/drivers/mfd/nct6694.c
> > @@ -0,0 +1,378 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 core driver using USB interface to provide
> > + * access to the NCT6694 hardware monitoring and control features.
> > + *
> > + * The NCT6694 is a versatile multi-function device that supports
>
> Here too.
>
Fix it in v9.
> > + * functionalities such as GPIO, I2C, CAN, WDT, HWMON and RTC
> > + * management.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
>
> This goes at the top.
>
Fix it in v9.
> > + */
> > +
> > +#include <linux/bits.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/irq.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <linux/usb.h>
> > +
> > +static const struct mfd_cell nct6694_dev[] = {
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x0),
>
> "-gpio" usually goes on the end.
>
Fix it in v9.
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
>
> IDs are usually given in base-10.
>
Fix it in v9.
> Why are you manually adding the device IDs?
>
> PLATFORM_DEVID_AUTO doesn't work for you?
>
I need to manage these IDs to ensure that child devices can be
properly utilized within their respective modules.
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
> > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
> > +
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x0),
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x1),
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x2),
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x3),
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x4),
> > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x5),
> > +
> > + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x0),
>
> Why has the naming convention changed here?
>
I originally expected the child devices name to directly match its
driver name. Do you think it would be better to standardize the naming
as "nct6694-xxx" ?
> > + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x1),
> > +
> > + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x0),
> > + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x1),
> > +
> > + MFD_CELL_NAME("nct6694-hwmon"),
> > + MFD_CELL_NAME("rtc-nct6694"),
>
> There doesn't seem to be any consistency here.
>
Do you think these two should be changed to use MFD_CELL_BASIC()?
> > +};
> > +
> > +static int nct6694_response_err_handling(struct nct6694 *nct6694,
>
> > +{
> > + switch (err_status) {
> > + case NCT6694_NO_ERROR:
> > + return err_status;
>
> This is odd since you already know this will be 0.
>
Fix it in v9.
> > + case NCT6694_NOT_SUPPORT_ERROR:
> > + dev_warn(nct6694->dev, "Command is not supported!\n");
>
> Why not dev_err()?
>
Fix it in v9.
> > + 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_warn(nct6694->dev, "Command is pending!\n");
>
> Is this an error?
>
Yes, I will fix it to dev_err() in the next patch.
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return -EIO;
> > +}
> > +
> > +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> > +{
> > + union nct6694_usb_msg *msg = nct6694->usb_msg;
> > + int tx_len, rx_len, ret;
> > +
> > + guard(mutex)(&nct6694->access_lock);
> > +
> > + /* Send command packet to USB device */
>
> This doesn't really describe the next 2 lines.
>
> Move it down?
>
Fix it in v9.
> > + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> > + msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
> > +
> > + ret = usb_bulk_msg(nct6694->udev,
>
> Since you use nct6694->udev a bunch - sometimes twice in the same call,
> it might be nicer to pull it into it's own variable instead of
> dereferencing it all the time.
>
Fix it in v9.
> > + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> > + &msg->cmd_header, sizeof(*msg), &tx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + return ret;
> > +
> > + /* Receive response packet from USB device */
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> > + &msg->response_header, sizeof(*msg), &rx_len,
>
> How can you read sizeof(*msg) Bytes (22?) into the smaller
> response_header (16?) attribute?
>
The size of (*msg) is the same as command_header and response_header (8 bytes).
> > + nct6694->timeout);
> > + if (ret)
> > + return ret;
> > +
> > + /* Receive data packet from USB device */
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> > + buf, le16_to_cpu(cmd_hd->len), &rx_len,
> > + nct6694->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(nct6694_read_msg);
> > +
> > +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
> > +{
> > + union nct6694_usb_msg *msg = nct6694->usb_msg;
> > + int tx_len, rx_len, ret;
> > +
> > + guard(mutex)(&nct6694->access_lock);
> > +
> > + /* Send command packet to USB device */
> > + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
> > + msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
> > +
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> > + &msg->cmd_header, sizeof(*msg), &tx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + return ret;
> > +
> > + /* Send data packet to USB device */
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
> > + buf, le16_to_cpu(cmd_hd->len), &tx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + return ret;
> > +
> > + /* Receive response packet from USB device */
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> > + &msg->response_header, sizeof(*msg), &rx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + return ret;
> > +
> > + /* Receive data packet from USB device */
> > + ret = usb_bulk_msg(nct6694->udev,
> > + usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
> > + buf, le16_to_cpu(cmd_hd->len), &rx_len,
> > + nct6694->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(nct6694_write_msg);
> > +
> > +static void usb_int_callback(struct urb *urb)
> > +{
> > + struct nct6694 *nct6694 = urb->context;
> > + unsigned int *int_status = urb->transfer_buffer;
> > + int ret;
> > +
> > + switch (urb->status) {
> > + case 0:
> > + break;
> > + case -ECONNRESET:
> > + case -ENOENT:
> > + case -ESHUTDOWN:
> > + return;
> > + default:
> > + 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_dbg(nct6694->dev, "%s: Failed to resubmit urb, status %pe",
>
> Why debug?
>
Excuse me, do you think it should change to dev_err()?
> > + __func__, ERR_PTR(ret));
>
> Remove the __func__ part.
>
Understood.
> > +}
> > +
...
> > +static int nct6694_usb_probe(struct usb_interface *iface,
> > + const struct usb_device_id *id)
> > +{
> > + struct usb_device *udev = interface_to_usbdev(iface);
> > + struct usb_endpoint_descriptor *int_endpoint;
> > + struct usb_host_interface *interface;
> > + struct device *dev = &iface->dev;
> > + struct nct6694 *nct6694;
> > + int pipe, maxp;
> > + int ret;
> > +
> > + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
> > + if (!nct6694)
> > + return -ENOMEM;
> > +
> > + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
> > + maxp = usb_maxpacket(udev, pipe);
> > +
> > + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
> > + if (!nct6694->usb_msg)
> > + return -ENOMEM;
> > +
> > + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
> > + if (!nct6694->int_buffer)
> > + return -ENOMEM;
> > +
> > + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
> > + if (!nct6694->int_in_urb)
> > + return -ENOMEM;
> > +
> > + nct6694->domain = irq_domain_add_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;
> > + nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until URB completes */
>
> No need to save this known value.
>
Understood, I will drop it in the next patch.
> > + ret = devm_mutex_init(dev, &nct6694->access_lock);
> > + if (ret)
> > + goto err_urb;
> > +
> > + ret = devm_mutex_init(dev, &nct6694->irq_lock);
> > + if (ret)
> > + goto err_urb;
> > +
> > + interface = iface->cur_altsetting;
> > + int_endpoint = &interface->endpoint[0].desc;
> > + if (!usb_endpoint_is_int_in(int_endpoint)) {
> > + ret = -ENODEV;
> > + goto err_urb;
> > + }
> > + usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
> > + nct6694->int_buffer, maxp, usb_int_callback,
> > + nct6694, int_endpoint->bInterval);
> > + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
> > + if (ret)
> > + goto err_urb;
>
> Please unsquash these calls - space them out.
>
Fix these in v9.
> > +
> > + usb_set_intfdata(iface, nct6694);
> > +
> > + ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
> > + if (ret)
> > + goto err_mfd;
> > +
> > + return 0;
> > +
> > +err_mfd:
> > + usb_kill_urb(nct6694->int_in_urb);
> > +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);
> > + 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)},
>
> This should fit on one line. You can use up to 100-chars.
>
Fix it in v9.
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(usb, nct6694_ids);
> > +
> > +static struct usb_driver nct6694_usb_driver = {
> > + .name = "nct6694",
>
> Odd spaces.
>
Fix it in v9.
> > + .id_table = nct6694_ids,
> > + .probe = nct6694_usb_probe,
> > + .disconnect = nct6694_usb_disconnect,
> > +};
> > +
>
> Remove this line.
>
Fix it in v9.
> > +module_usb_driver(nct6694_usb_driver);
> > +
> > +MODULE_DESCRIPTION("USB core driver for NCT6694");
>
> This is not a USB driver.
>
Fix it in v9.
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
>
> Different to SoB.
>
Fix it in v9.
> > +MODULE_LICENSE("GPL");
> > diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
> > new file mode 100644
> > index 000000000000..8171f975761e
> > --- /dev/null
> > +++ b/include/linux/mfd/nct6694.h
> > @@ -0,0 +1,102 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Nuvoton NCT6694 USB transaction and data structure.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
>
> At the top.
>
Fix it in v9.
> > + */
> > +
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-03-17 2:57 ` Ming Yu
@ 2025-03-20 14:50 ` Lee Jones
2025-03-26 2:52 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Lee Jones @ 2025-03-20 14:50 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
On Mon, 17 Mar 2025, Ming Yu wrote:
> Dear Lee,
>
> Thank you for reviewing,
>
> Lee Jones <lee@kernel.org> 於 2025年3月7日 週五 上午9:15寫道:
> >
> > On Tue, 25 Feb 2025, Ming Yu wrote:
> >
> > > 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 needs to go into the Kconfig help passage.
> >
>
> Okay, I will move these to Kconfig in the next patch.
>
> > > This driver implements USB device functionality and shares the
> > > chip's peripherals as a child device.
> >
> > This driver doesn't implement USB functionality.
> >
>
> Fix it in v9.
>
> > > Each child device can use the USB functions nct6694_read_msg()
> > > and nct6694_write_msg() to issue a command. They can also request
> > > interrupt that will be called when the USB device receives its
> > > interrupt pipe.
> > >
> > > Signed-off-by: Ming Yu <a0282524688@gmail.com>
> >
> > Why aren't you signing off with your work address?
> >
>
> Fix it in v9.
>
> > > ---
> > > MAINTAINERS | 7 +
> > > drivers/mfd/Kconfig | 18 ++
> > > drivers/mfd/Makefile | 2 +
> > > drivers/mfd/nct6694.c | 378 ++++++++++++++++++++++++++++++++++++
> > > include/linux/mfd/nct6694.h | 102 ++++++++++
> > > 5 files changed, 507 insertions(+)
> > > create mode 100644 drivers/mfd/nct6694.c
> > > create mode 100644 include/linux/mfd/nct6694.h
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 873aa2cce4d7..c700a0b96960 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -16918,6 +16918,13 @@ F: drivers/nubus/
> > > F: include/linux/nubus.h
> > > F: include/uapi/linux/nubus.h
> > >
> > > +NUVOTON NCT6694 MFD DRIVER
> > > +M: Ming Yu <tmyu0@nuvoton.com>
> > > +L: linux-kernel@vger.kernel.org
> >
> > This is the default list. You shouldn't need to add that here.
>
> Remove it in v9.
Please snip everything that you agree with.
> > > +S: Supported
> > > +F: drivers/mfd/nct6694.c
> > > +F: include/linux/mfd/nct6694.h
> > > +
> > > NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> > > M: Antonino Daplas <adaplas@gmail.com>
> > > L: linux-fbdev@vger.kernel.org
[...]
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> >
> > IDs are usually given in base-10.
> >
>
> Fix it in v9.
>
> > Why are you manually adding the device IDs?
> >
> > PLATFORM_DEVID_AUTO doesn't work for you?
> >
>
> I need to manage these IDs to ensure that child devices can be
> properly utilized within their respective modules.
How? Please explain.
This numbering looks sequential and arbitrary.
What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
>
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
> > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
> > > +
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x0),
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x1),
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x2),
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x3),
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x4),
> > > + MFD_CELL_BASIC("i2c-nct6694", NULL, NULL, 0, 0x5),
> > > +
> > > + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x0),
> >
> > Why has the naming convention changed here?
> >
>
> I originally expected the child devices name to directly match its
> driver name. Do you think it would be better to standardize the naming
> as "nct6694-xxx" ?
Yes, that is the usual procedure.
> > > + MFD_CELL_BASIC("nct6694_canfd", NULL, NULL, 0, 0x1),
> > > +
> > > + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x0),
> > > + MFD_CELL_BASIC("nct6694_wdt", NULL, NULL, 0, 0x1),
> > > +
> > > + MFD_CELL_NAME("nct6694-hwmon"),
> > > + MFD_CELL_NAME("rtc-nct6694"),
> >
> > There doesn't seem to be any consistency here.
> >
>
> Do you think these two should be changed to use MFD_CELL_BASIC()?
No. I mean with the device nomenclature.
[...]
> > > +static void usb_int_callback(struct urb *urb)
> > > +{
> > > + struct nct6694 *nct6694 = urb->context;
> > > + unsigned int *int_status = urb->transfer_buffer;
> > > + int ret;
> > > +
> > > + switch (urb->status) {
> > > + case 0:
> > > + break;
> > > + case -ECONNRESET:
> > > + case -ENOENT:
> > > + case -ESHUTDOWN:
> > > + return;
> > > + default:
> > > + 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_dbg(nct6694->dev, "%s: Failed to resubmit urb, status %pe",
> >
> > Why debug?
> >
>
> Excuse me, do you think it should change to dev_err()?
Probably a dev_warn() since you are not propagating the error.
Is this okay by the way? Is it okay to fail?
--
Lee Jones [李琼斯]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-03-20 14:50 ` Lee Jones
@ 2025-03-26 2:52 ` Ming Yu
2025-04-04 14:21 ` Lee Jones
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-26 2:52 UTC (permalink / raw)
To: Lee Jones
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Lee Jones <lee@kernel.org> 於 2025年3月20日 週四 下午10:50寫道:
>
...
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> > >
> > > IDs are usually given in base-10.
> > >
> >
> > Fix it in v9.
> >
> > > Why are you manually adding the device IDs?
> > >
> > > PLATFORM_DEVID_AUTO doesn't work for you?
> > >
> >
> > I need to manage these IDs to ensure that child devices can be
> > properly utilized within their respective modules.
>
> How? Please explain.
>
> This numbering looks sequential and arbitrary.
>
> What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
>
As far as I know, PLATFORM_DEVID_AUTO assigns dynamic IDs to devices,
but I need fixed IDs.
For example, the GPIO driver relies on these IDs to determine the
group, allowing the firmware to identify which GPIO group to operate
on through the API.
> >
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
> > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-03-26 2:52 ` Ming Yu
@ 2025-04-04 14:21 ` Lee Jones
2025-04-07 2:25 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Lee Jones @ 2025-04-04 14:21 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
On Wed, 26 Mar 2025, Ming Yu wrote:
> Lee Jones <lee@kernel.org> 於 2025年3月20日 週四 下午10:50寫道:
> >
> ...
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> > > >
> > > > IDs are usually given in base-10.
> > > >
> > >
> > > Fix it in v9.
> > >
> > > > Why are you manually adding the device IDs?
> > > >
> > > > PLATFORM_DEVID_AUTO doesn't work for you?
> > > >
> > >
> > > I need to manage these IDs to ensure that child devices can be
> > > properly utilized within their respective modules.
> >
> > How? Please explain.
> >
> > This numbering looks sequential and arbitrary.
> >
> > What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
> >
>
> As far as I know, PLATFORM_DEVID_AUTO assigns dynamic IDs to devices,
> but I need fixed IDs.
> For example, the GPIO driver relies on these IDs to determine the
> group, allowing the firmware to identify which GPIO group to operate
> on through the API.
PLATFORM_DEVID_AUTO will allocate IDs 0 through 16, the same as you've
done here. These lines do not have any differentiating attributes, so
either way we are not allocating specific IDs to specific pieces of the
H/W. I still do not understand why you need to allocate them manually.
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x2),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x3),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x4),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x5),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x6),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x7),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x8),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x9),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xA),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xB),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xC),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xD),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xE),
> > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0xF),
>
>
> Thanks,
> Ming
--
Lee Jones [李琼斯]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-04-04 14:21 ` Lee Jones
@ 2025-04-07 2:25 ` Ming Yu
2025-04-10 8:21 ` Lee Jones
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-04-07 2:25 UTC (permalink / raw)
To: Lee Jones
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Lee Jones <lee@kernel.org> 於 2025年4月4日 週五 下午10:21寫道:
>
> > ...
> > > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> > > > >
> > > > > IDs are usually given in base-10.
> > > > >
> > > >
> > > > Fix it in v9.
> > > >
> > > > > Why are you manually adding the device IDs?
> > > > >
> > > > > PLATFORM_DEVID_AUTO doesn't work for you?
> > > > >
> > > >
> > > > I need to manage these IDs to ensure that child devices can be
> > > > properly utilized within their respective modules.
> > >
> > > How? Please explain.
> > >
> > > This numbering looks sequential and arbitrary.
> > >
> > > What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
> > >
> >
> > As far as I know, PLATFORM_DEVID_AUTO assigns dynamic IDs to devices,
> > but I need fixed IDs.
> > For example, the GPIO driver relies on these IDs to determine the
> > group, allowing the firmware to identify which GPIO group to operate
> > on through the API.
>
> PLATFORM_DEVID_AUTO will allocate IDs 0 through 16, the same as you've
> done here. These lines do not have any differentiating attributes, so
> either way we are not allocating specific IDs to specific pieces of the
> H/W. I still do not understand why you need to allocate them manually.
>
I'm using PLATFORM_DEVID_AUTO to allocate child device IDs with
MFD_CELL_NAME(), like this:
static const struct mfd_cell nct6694_dev[] = {
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"),
......
};
For example, the device IDs retrieved in gpio-nct6694.c is 1~16, and
i2c-nct6694.c is 17~22. Does this mean each driver should
independently handle its dynamically assigned IDs?
Additionally, I originally referred to cgbc-core.c with i2c-cgbc.c,
and ab8500-core.c with pwm-ab8500.c for associating child devices. Do
you think this approach is appropriate in my case?
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-04-07 2:25 ` Ming Yu
@ 2025-04-10 8:21 ` Lee Jones
2025-04-21 11:00 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Lee Jones @ 2025-04-10 8:21 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
On Mon, 07 Apr 2025, Ming Yu wrote:
> Lee Jones <lee@kernel.org> 於 2025年4月4日 週五 下午10:21寫道:
> >
> > > ...
> > > > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> > > > > >
> > > > > > IDs are usually given in base-10.
> > > > > >
> > > > >
> > > > > Fix it in v9.
> > > > >
> > > > > > Why are you manually adding the device IDs?
> > > > > >
> > > > > > PLATFORM_DEVID_AUTO doesn't work for you?
> > > > > >
> > > > >
> > > > > I need to manage these IDs to ensure that child devices can be
> > > > > properly utilized within their respective modules.
> > > >
> > > > How? Please explain.
> > > >
> > > > This numbering looks sequential and arbitrary.
> > > >
> > > > What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
> > > >
> > >
> > > As far as I know, PLATFORM_DEVID_AUTO assigns dynamic IDs to devices,
> > > but I need fixed IDs.
> > > For example, the GPIO driver relies on these IDs to determine the
> > > group, allowing the firmware to identify which GPIO group to operate
> > > on through the API.
> >
> > PLATFORM_DEVID_AUTO will allocate IDs 0 through 16, the same as you've
> > done here. These lines do not have any differentiating attributes, so
> > either way we are not allocating specific IDs to specific pieces of the
> > H/W. I still do not understand why you need to allocate them manually.
> >
>
> I'm using PLATFORM_DEVID_AUTO to allocate child device IDs with
> MFD_CELL_NAME(), like this:
>
> static const struct mfd_cell nct6694_dev[] = {
> 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"),
> ......
> };
>
> For example, the device IDs retrieved in gpio-nct6694.c is 1~16, and
> i2c-nct6694.c is 17~22. Does this mean each driver should
> independently handle its dynamically assigned IDs?
> Additionally, I originally referred to cgbc-core.c with i2c-cgbc.c,
> and ab8500-core.c with pwm-ab8500.c for associating child devices. Do
> you think this approach is appropriate in my case?
Yes, if you _need_ the ranges to start from 0, then you will have to
call mfd_add_devices() separately on those ranges. Otherwise one range
will follow directly on to another range.
But wait, you're using mfd_add_hotplug_devices(), which means you are
using PLATFORM_DEVID_AUTO. So your .id values that you've added are
being ignored anyway. Thus, if you have tested that this works, you
don't need them anyway, right?
--
Lee Jones [李琼斯]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694
2025-04-10 8:21 ` Lee Jones
@ 2025-04-21 11:00 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-04-21 11:00 UTC (permalink / raw)
To: Lee Jones
Cc: tmyu0, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Lee Jones <lee@kernel.org> 於 2025年4月10日 週四 下午4:21寫道:
>
> On Mon, 07 Apr 2025, Ming Yu wrote:
>
> > Lee Jones <lee@kernel.org> 於 2025年4月4日 週五 下午10:21寫道:
> > >
> > > > ...
> > > > > > > > + MFD_CELL_BASIC("gpio-nct6694", NULL, NULL, 0, 0x1),
> > > > > > >
> > > > > > > IDs are usually given in base-10.
> > > > > > >
> > > > > >
> > > > > > Fix it in v9.
> > > > > >
> > > > > > > Why are you manually adding the device IDs?
> > > > > > >
> > > > > > > PLATFORM_DEVID_AUTO doesn't work for you?
> > > > > > >
> > > > > >
> > > > > > I need to manage these IDs to ensure that child devices can be
> > > > > > properly utilized within their respective modules.
> > > > >
> > > > > How? Please explain.
> > > > >
> > > > > This numbering looks sequential and arbitrary.
> > > > >
> > > > > What does PLATFORM_DEVID_AUTO do differently such that it is not useful?
> > > > >
> > > >
> > > > As far as I know, PLATFORM_DEVID_AUTO assigns dynamic IDs to devices,
> > > > but I need fixed IDs.
> > > > For example, the GPIO driver relies on these IDs to determine the
> > > > group, allowing the firmware to identify which GPIO group to operate
> > > > on through the API.
> > >
> > > PLATFORM_DEVID_AUTO will allocate IDs 0 through 16, the same as you've
> > > done here. These lines do not have any differentiating attributes, so
> > > either way we are not allocating specific IDs to specific pieces of the
> > > H/W. I still do not understand why you need to allocate them manually.
> > >
> >
> > I'm using PLATFORM_DEVID_AUTO to allocate child device IDs with
> > MFD_CELL_NAME(), like this:
> >
> > static const struct mfd_cell nct6694_dev[] = {
> > 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"),
> > ......
> > };
> >
> > For example, the device IDs retrieved in gpio-nct6694.c is 1~16, and
> > i2c-nct6694.c is 17~22. Does this mean each driver should
> > independently handle its dynamically assigned IDs?
> > Additionally, I originally referred to cgbc-core.c with i2c-cgbc.c,
> > and ab8500-core.c with pwm-ab8500.c for associating child devices. Do
> > you think this approach is appropriate in my case?
>
> Yes, if you _need_ the ranges to start from 0, then you will have to
> call mfd_add_devices() separately on those ranges. Otherwise one range
> will follow directly on to another range.
>
> But wait, you're using mfd_add_hotplug_devices(), which means you are
> using PLATFORM_DEVID_AUTO. So your .id values that you've added are
> being ignored anyway. Thus, if you have tested that this works, you
> don't need them anyway, right?
>
Yes, it uses PLATFORM_DEVID_AUTO, but in my implementation, the
sub-devices use cell->id instead of platform_device->id, so it doesn't
affect the current behavior.
However, if you think there's a better approach or that this should be
changed for consistency or correctness, I'm happy to update it, please
let me know your recommendation.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v8 2/7] gpio: Add Nuvoton NCT6694 GPIO support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
2025-02-25 8:16 ` [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-02-25 8:16 ` [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
` (4 subsequent siblings)
6 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu,
Bartosz Golaszewski
This driver supports GPIO and IRQ functionality for NCT6694 MFD
device based on USB interface.
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/gpio/Kconfig | 12 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-nct6694.c | 463 ++++++++++++++++++++++++++++++++++++
4 files changed, 477 insertions(+)
create mode 100644 drivers/gpio/gpio-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c700a0b96960..be7cd44a4c88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16922,6 +16922,7 @@ NUVOTON NCT6694 MFD DRIVER
M: Ming Yu <tmyu0@nuvoton.com>
L: linux-kernel@vger.kernel.org
S: Supported
+F: drivers/gpio/gpio-nct6694.c
F: drivers/mfd/nct6694.c
F: include/linux/mfd/nct6694.h
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index add5ad29a673..9396de3ea86d 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1461,6 +1461,18 @@ config GPIO_MAX77650
GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
These chips have a single pin that can be configured as GPIO.
+config GPIO_NCT6694
+ tristate "Nuvoton NCT6694 GPIO controller support"
+ depends on MFD_NCT6694
+ select GENERIC_IRQ_CHIP
+ select GPIOLIB_IRQCHIP
+ help
+ This driver supports 8 GPIO pins per bank that can all be interrupt
+ sources.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-nct6694.
+
config GPIO_PALMAS
bool "TI PALMAS series PMICs GPIO"
depends on MFD_PALMAS
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b58..def8487540ab 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -121,6 +121,7 @@ obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o
obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
+obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o
obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o
obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 000000000000..9201a386a853
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.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;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !(BIT(offset) & data->reg_val);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ data->reg_val &= ~BIT(offset);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+ unsigned int offset, int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ /* Set direction to output */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ data->reg_val |= BIT(offset);
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ /* Then set output level */
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ data->reg_val |= BIT(offset);
+ else
+ data->reg_val &= ~BIT(offset);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ if (BIT(offset) & data->reg_val) {
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->reg_val);
+ }
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_DATA + data->group);
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->reg_val);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+ int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_DATA + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+
+ guard(mutex)(&data->lock);
+
+ nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+
+ if (val)
+ data->reg_val |= BIT(offset);
+ else
+ data->reg_val &= ~BIT(offset);
+
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+ unsigned long config)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPO_TYPE + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ data->reg_val |= BIT(offset);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ data->reg_val &= ~BIT(offset);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_init_valid_mask(struct gpio_chip *gpio,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPIO_VALID + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret < 0)
+ return ret;
+
+ *valid_mask = data->reg_val;
+
+ return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+ struct nct6694_gpio_data *data = priv;
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_STS + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ unsigned char status;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ if (ret)
+ return IRQ_NONE;
+
+ status = data->reg_val;
+
+ while (status) {
+ int bit = __ffs(status);
+
+ data->reg_val = BIT(bit);
+ handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+ status &= ~BIT(bit);
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_CLR + data->group);
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+ if (ret)
+ return ret;
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+ return nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+}
+
+static void nct6694_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_disable_irq(gpio, hwirq);
+}
+
+static void nct6694_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gpio, hwirq);
+}
+
+static int nct6694_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ guard(mutex)(&data->lock);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ data->irq_trig_rising |= BIT(hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ data->irq_trig_falling |= BIT(hwirq);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ data->irq_trig_rising |= BIT(hwirq);
+ data->irq_trig_falling |= BIT(hwirq);
+ break;
+
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static void nct6694_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+ mutex_lock(&data->irq_lock);
+}
+
+static void nct6694_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_GPIO_MOD,
+ .offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+ .len = cpu_to_le16(sizeof(data->reg_val))
+ };
+
+ scoped_guard(mutex, &data->lock) {
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+
+ cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+ nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+ }
+
+ mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "gpio-nct6694",
+ .irq_mask = nct6694_irq_mask,
+ .irq_unmask = nct6694_irq_unmask,
+ .irq_set_type = nct6694_irq_set_type,
+ .irq_bus_lock = nct6694_irq_bus_lock,
+ .irq_bus_sync_unlock = nct6694_irq_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int nct6694_gpio_probe(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct device *dev = &pdev->dev;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_gpio_data *data;
+ struct gpio_irq_chip *girq;
+ int ret, irq, i;
+ char **names;
+
+ irq = irq_create_mapping(nct6694->domain,
+ NCT6694_IRQ_GPIO0 + cell->id);
+ if (!irq)
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
+ GFP_KERNEL);
+ if (!names)
+ return -ENOMEM;
+
+ for (i = 0; i < NCT6694_NR_GPIO; i++) {
+ names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
+ cell->id, i);
+ if (!names[i])
+ return -ENOMEM;
+ }
+
+ data->nct6694 = nct6694;
+ data->group = cell->id;
+
+ data->gpio.names = (const char * const*)names;
+ data->gpio.label = pdev->name;
+ data->gpio.direction_input = nct6694_direction_input;
+ data->gpio.get = nct6694_get_value;
+ data->gpio.direction_output = nct6694_direction_output;
+ data->gpio.set = nct6694_set_value;
+ data->gpio.get_direction = nct6694_get_direction;
+ data->gpio.set_config = nct6694_set_config;
+ data->gpio.init_valid_mask = nct6694_init_valid_mask;
+ data->gpio.base = -1;
+ data->gpio.can_sleep = false;
+ data->gpio.owner = THIS_MODULE;
+ data->gpio.ngpio = NCT6694_NR_GPIO;
+
+ 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)
+ return dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+
+ 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, irq, NULL, nct6694_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "gpio-nct6694", data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+ return devm_gpiochip_add_data(dev, &data->gpio, data);
+}
+
+static struct platform_driver nct6694_gpio_driver = {
+ .driver = {
+ .name = "gpio-nct6694",
+ },
+ .probe = nct6694_gpio_probe,
+};
+
+module_platform_driver(nct6694_gpio_driver);
+
+MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-gpio");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
2025-02-25 8:16 ` [PATCH v8 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2025-02-25 8:16 ` [PATCH v8 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-03-19 23:58 ` Andi Shyti
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
` (3 subsequent siblings)
6 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
This driver supports I2C adapter functionality for NCT6694 MFD
device based on USB interface, each I2C controller use default
baudrate(100K).
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/i2c/busses/Kconfig | 10 ++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-nct6694.c | 152 +++++++++++++++++++++++++++++++
4 files changed, 164 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index be7cd44a4c88..1327e7a6e507 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16923,6 +16923,7 @@ M: Ming Yu <tmyu0@nuvoton.com>
L: linux-kernel@vger.kernel.org
S: Supported
F: drivers/gpio/gpio-nct6694.c
+F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: include/linux/mfd/nct6694.h
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fc438f445771..d0753d0adaae 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1329,6 +1329,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 1c2a4510abe4..c5a60116dd54 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -133,6 +133,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o
+obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 000000000000..fc26fb6e3751
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 I2C adapter driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.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
+
+enum i2c_baudrate {
+ I2C_BR_25K = 0,
+ I2C_BR_50K,
+ I2C_BR_100K,
+ I2C_BR_200K,
+ I2C_BR_400K,
+ I2C_BR_800K,
+ I2C_BR_1M
+};
+
+struct __packed nct6694_i2c_deliver {
+ u8 port;
+ u8 br;
+ u8 addr;
+ u8 w_cnt;
+ u8 r_cnt;
+ u8 rsv[11];
+ u8 write_data[0x40];
+ u8 read_data[0x40];
+};
+
+struct nct6694_i2c_data {
+ struct nct6694 *nct6694;
+ struct i2c_adapter adapter;
+ struct nct6694_i2c_deliver deliver;
+ unsigned char port;
+ unsigned char br;
+};
+
+static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct nct6694_i2c_data *data = adap->algo_data;
+ struct nct6694_i2c_deliver *deliver = &data->deliver;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_I2C_MOD,
+ .cmd = NCT6694_I2C_DELIVER,
+ .sel = NCT6694_I2C_DELIVER_SEL,
+ .len = cpu_to_le16(sizeof(*deliver))
+ };
+ int ret, i;
+
+ for (i = 0; i < num; i++) {
+ struct i2c_msg *msg_temp = &msgs[i];
+
+ memset(deliver, 0, sizeof(*deliver));
+
+ if (msg_temp->len > 64)
+ return -EPROTO;
+
+ deliver->port = data->port;
+ deliver->br = data->br;
+ deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
+ if (msg_temp->flags & I2C_M_RD) {
+ deliver->r_cnt = msg_temp->len;
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+ if (ret < 0)
+ return ret;
+
+ memcpy(msg_temp->buf, deliver->read_data, msg_temp->len);
+ } else {
+ deliver->w_cnt = msg_temp->len;
+ memcpy(deliver->write_data, msg_temp->buf, msg_temp->len);
+ ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return num;
+}
+
+static u32 nct6694_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm algorithm = {
+ .master_xfer = nct6694_xfer,
+ .functionality = nct6694_func,
+};
+
+static int nct6694_i2c_probe(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_i2c_data *data;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ data->port = cell->id;
+ data->br = I2C_BR_100K;
+
+ sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", cell->id);
+ data->adapter.owner = THIS_MODULE;
+ data->adapter.algo = &algorithm;
+ data->adapter.dev.parent = &pdev->dev;
+ data->adapter.algo_data = data;
+
+ platform_set_drvdata(pdev, data);
+
+ return i2c_add_adapter(&data->adapter);
+}
+
+static void nct6694_i2c_remove(struct platform_device *pdev)
+{
+ struct nct6694_i2c_data *data = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&data->adapter);
+}
+
+static struct platform_driver nct6694_i2c_driver = {
+ .driver = {
+ .name = "i2c-nct6694",
+ },
+ .probe = nct6694_i2c_probe,
+ .remove = nct6694_i2c_remove,
+};
+
+module_platform_driver(nct6694_i2c_driver);
+
+MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-i2c");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* Re: [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support
2025-02-25 8:16 ` [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2025-03-19 23:58 ` Andi Shyti
2025-03-26 2:46 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Andi Shyti @ 2025-03-19 23:58 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Hi Ming,
...
> +enum i2c_baudrate {
> + I2C_BR_25K = 0,
> + I2C_BR_50K,
> + I2C_BR_100K,
> + I2C_BR_200K,
> + I2C_BR_400K,
> + I2C_BR_800K,
> + I2C_BR_1M
> +};
do we need all these frequencies? I don't see them use anywhere.
Besides, can you please use a proper prefix? I2C_BR_* prefix
doesn't belong to this driver.
Andi
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support
2025-03-19 23:58 ` Andi Shyti
@ 2025-03-26 2:46 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-03-26 2:46 UTC (permalink / raw)
To: Andi Shyti
Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Dear Andi,
Thank you for reviewing,
Andi Shyti <andi.shyti@kernel.org> 於 2025年3月20日 週四 上午7:58寫道:
>
...
> > +enum i2c_baudrate {
> > + I2C_BR_25K = 0,
> > + I2C_BR_50K,
> > + I2C_BR_100K,
> > + I2C_BR_200K,
> > + I2C_BR_400K,
> > + I2C_BR_800K,
> > + I2C_BR_1M
> > +};
>
> do we need all these frequencies? I don't see them use anywhere.
>
Originally, I used module parameters to configure these I2C's baud
rate, Do you think this approach iis suitable?
> Besides, can you please use a proper prefix? I2C_BR_* prefix
> doesn't belong to this driver.
>
Okay, I will fix these macros in the next patch.
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (2 preceding siblings ...)
2025-02-25 8:16 ` [PATCH v8 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-02-27 2:08 ` Vincent Mailhol
` (3 more replies)
2025-02-25 8:16 ` [PATCH v8 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
` (2 subsequent siblings)
6 siblings, 4 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
This driver supports Socket CANFD functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/net/can/usb/Kconfig | 11 +
drivers/net/can/usb/Makefile | 1 +
drivers/net/can/usb/nct6694_canfd.c | 799 ++++++++++++++++++++++++++++
4 files changed, 812 insertions(+)
create mode 100644 drivers/net/can/usb/nct6694_canfd.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1327e7a6e507..8aa611504172 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16925,6 +16925,7 @@ S: Supported
F: drivers/gpio/gpio-nct6694.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
+F: drivers/net/can/usb/nct6694_canfd.c
F: include/linux/mfd/nct6694.h
NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
index 9dae0c71a2e1..759e724a67cf 100644
--- a/drivers/net/can/usb/Kconfig
+++ b/drivers/net/can/usb/Kconfig
@@ -133,6 +133,17 @@ config CAN_MCBA_USB
This driver supports the CAN BUS Analyzer interface
from Microchip (http://www.microchip.com/development-tools/).
+config CAN_NCT6694
+ tristate "Nuvoton NCT6694 Socket CANfd support"
+ depends on MFD_NCT6694
+ select CAN_RX_OFFLOAD
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to socket CANfd controller.
+
+ This driver can also be built as a module. If so, the module will
+ be called nct6694_canfd.
+
config CAN_PEAK_USB
tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
help
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 8b11088e9a59..fcafb1ac262e 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
+obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
obj-$(CONFIG_CAN_UCAN) += ucan.o
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
new file mode 100644
index 000000000000..d97fce5cdf32
--- /dev/null
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -0,0 +1,799 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/can/dev.h>
+#include <linux/can/rx-offload.h>
+#include <linux/ethtool.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#define DRVNAME "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_CAN_MOD 0x05
+
+/* Command 00h - CAN Setting and Initialization */
+#define NCT6694_CAN_SETTING 0x00
+#define NCT6694_CAN_SETTING_CTRL1_MON BIT(0)
+#define NCT6694_CAN_SETTING_CTRL1_NISO BIT(1)
+#define NCT6694_CAN_SETTING_CTRL1_LBCK BIT(2)
+
+/* Command 01h - CAN Information */
+#define NCT6694_CAN_INFORMATION 0x01
+#define NCT6694_CAN_INFORMATION_SEL 0x00
+
+/* Command 02h - CAN Event */
+#define NCT6694_CAN_EVENT 0x02
+#define NCT6694_CAN_EVENT_SEL(idx, mask) \
+ ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
+
+#define NCT6694_CAN_EVENT_MASK GENMASK(5, 0)
+#define NCT6694_CAN_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_LOST BIT(5) /* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_IN BIT(7) /* Read-clear*/
+
+/* Command 10h - CAN Deliver */
+#define NCT6694_CAN_DELIVER 0x10
+#define NCT6694_CAN_DELIVER_SEL(buf_cnt) \
+ ((buf_cnt) & 0xFF)
+
+/* Command 11h - CAN Receive */
+#define NCT6694_CAN_RECEIVE 0x11
+#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt) \
+ ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
+
+#define NCT6694_CAN_FRAME_TAG(idx) (0xC0 | (idx))
+#define NCT6694_CAN_FRAME_FLAG_EFF BIT(0)
+#define NCT6694_CAN_FRAME_FLAG_RTR BIT(1)
+#define NCT6694_CAN_FRAME_FLAG_FD BIT(2)
+#define NCT6694_CAN_FRAME_FLAG_BRS BIT(3)
+#define NCT6694_CAN_FRAME_FLAG_ERR BIT(4)
+
+#define NCT6694_NAPI_WEIGHT 32
+
+enum nct6694_event_err {
+ NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
+ NCT6694_CAN_EVT_ERR_CRC_ERROR,
+ NCT6694_CAN_EVT_ERR_STUFF_ERROR,
+ NCT6694_CAN_EVT_ERR_ACK_ERROR,
+ NCT6694_CAN_EVT_ERR_FORM_ERROR,
+ NCT6694_CAN_EVT_ERR_BIT_ERROR,
+ NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
+ NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+ NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
+ NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
+ NCT6694_CAN_EVT_STS_BUS_OFF,
+ NCT6694_CAN_EVT_STS_WARNING,
+};
+
+struct __packed nct6694_can_setting {
+ __le32 nbr;
+ __le32 dbr;
+ u8 active;
+ u8 reserved[3];
+ __le16 ctrl1;
+ __le16 ctrl2;
+ __le32 nbtp;
+ __le32 dbtp;
+};
+
+struct __packed nct6694_can_information {
+ u8 tx_fifo_cnt;
+ u8 rx_fifo_cnt;
+ u8 reserved[2];
+ __le32 can_clk;
+};
+
+struct __packed nct6694_can_event {
+ u8 err;
+ u8 status;
+ u8 tx_evt;
+ u8 rx_evt;
+ u8 rec;
+ u8 tec;
+ u8 reserved[2];
+};
+
+struct __packed nct6694_can_frame {
+ u8 tag;
+ u8 flag;
+ u8 reserved;
+ u8 length;
+ __le32 id;
+ u8 data[CANFD_MAX_DLEN];
+};
+
+struct nct6694_can_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_can_frame tx;
+ struct nct6694_can_frame rx;
+ struct nct6694_can_event event[2];
+ struct can_berr_counter bec;
+};
+
+static inline struct nct6694_can_priv *rx_offload_to_priv(struct can_rx_offload *offload)
+{
+ return container_of(offload, struct nct6694_can_priv, offload);
+}
+
+static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
+ .name = DRVNAME,
+ .tseg1_min = 2,
+ .tseg1_max = 256,
+ .tseg2_min = 2,
+ .tseg2_max = 128,
+ .sjw_max = 128,
+ .brp_min = 1,
+ .brp_max = 511,
+ .brp_inc = 1,
+};
+
+static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
+ .name = DRVNAME,
+ .tseg1_min = 1,
+ .tseg1_max = 32,
+ .tseg2_min = 1,
+ .tseg2_max = 16,
+ .sjw_max = 16,
+ .brp_min = 1,
+ .brp_max = 31,
+ .brp_inc = 1,
+};
+
+static void nct6694_can_rx_offload(struct can_rx_offload *offload,
+ struct sk_buff *skb)
+{
+ struct nct6694_can_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_can_handle_lost_msg(struct net_device *ndev)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ netdev_err(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_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_frame *frame = &priv->rx;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CAN_MOD,
+ .cmd = NCT6694_CAN_RECEIVE,
+ .sel = NCT6694_CAN_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_CAN_FRAME_FLAG_FD) {
+ struct canfd_frame *cfd;
+
+ skb = alloc_canfd_skb(priv->ndev, &cfd);
+ if (!skb)
+ return;
+
+ cfd->can_id = le32_to_cpu(frame->id);
+ cfd->len = canfd_sanitize_len(frame->length);
+ if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
+ cfd->can_id |= CAN_EFF_FLAG;
+ if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
+ cfd->flags |= CANFD_BRS;
+ if (frame->flag & NCT6694_CAN_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)
+ return;
+
+ cf->can_id = le32_to_cpu(frame->id);
+ cf->len = can_cc_dlc2len(frame->length);
+ if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
+ cf->can_id |= CAN_EFF_FLAG;
+
+ if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+ else
+ memcpy(cf->data, frame->data, cf->len);
+ }
+
+ nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static int nct6694_can_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+ *bec = priv->bec;
+
+ return 0;
+}
+
+static void nct6694_can_handle_state_change(struct net_device *ndev,
+ enum can_state new_state)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ struct can_berr_counter bec;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+
+ nct6694_can_get_berr_counter(ndev, &bec);
+
+ switch (new_state) {
+ case CAN_STATE_ERROR_ACTIVE:
+ priv->can.can_stats.error_warning++;
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+ if (cf)
+ cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
+ break;
+ case CAN_STATE_ERROR_WARNING:
+ priv->can.can_stats.error_warning++;
+ priv->can.state = CAN_STATE_ERROR_WARNING;
+ if (cf) {
+ cf->can_id |= CAN_ERR_CRTL;
+ if (bec.txerr > bec.rxerr)
+ cf->data[1] = CAN_ERR_CRTL_TX_WARNING;
+ else
+ cf->data[1] = CAN_ERR_CRTL_RX_WARNING;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ priv->can.can_stats.error_passive++;
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ if (cf) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+ if (bec.txerr >= CAN_ERROR_PASSIVE_THRESHOLD)
+ cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ }
+ break;
+ case CAN_STATE_BUS_OFF:
+ priv->can.state = CAN_STATE_BUS_OFF;
+ priv->can.can_stats.bus_off++;
+ if (cf)
+ cf->can_id |= CAN_ERR_BUSOFF;
+ can_free_echo_skb(ndev, 0, NULL);
+ netif_stop_queue(ndev);
+ can_bus_off(ndev);
+ break;
+ default:
+ break;
+ }
+
+ nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_handle_state_errors(struct net_device *ndev, u8 status)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+ if (status == NCT6694_CAN_EVT_STS_ERROR_ACTIVE &&
+ priv->can.state != CAN_STATE_ERROR_ACTIVE) {
+ netdev_dbg(ndev, "Error, entered active state\n");
+ nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_ACTIVE);
+ }
+
+ if (status == NCT6694_CAN_EVT_STS_WARNING &&
+ priv->can.state != CAN_STATE_ERROR_WARNING) {
+ netdev_dbg(ndev, "Error, entered warning state\n");
+ nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_WARNING);
+ }
+
+ if (status == NCT6694_CAN_EVT_STS_ERROR_PASSIVE &&
+ priv->can.state != CAN_STATE_ERROR_PASSIVE) {
+ netdev_dbg(ndev, "Error, entered passive state\n");
+ nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_PASSIVE);
+ }
+
+ if (status == NCT6694_CAN_EVT_STS_BUS_OFF &&
+ priv->can.state != CAN_STATE_BUS_OFF) {
+ netdev_dbg(ndev, "Error, entered bus-off state\n");
+ nct6694_can_handle_state_change(ndev, CAN_STATE_BUS_OFF);
+ }
+}
+
+static void nct6694_can_handle_bus_err(struct net_device *ndev, u8 bus_err)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
+ return;
+
+ priv->can.can_stats.bus_error++;
+
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (skb)
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+ switch (bus_err) {
+ case NCT6694_CAN_EVT_ERR_CRC_ERROR:
+ netdev_dbg(ndev, "CRC error\n");
+ ndev->stats.rx_errors++;
+ if (skb)
+ cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+ break;
+
+ case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
+ netdev_dbg(ndev, "Stuff error\n");
+ ndev->stats.rx_errors++;
+ if (skb)
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+
+ case NCT6694_CAN_EVT_ERR_ACK_ERROR:
+ netdev_dbg(ndev, "Ack error\n");
+ ndev->stats.tx_errors++;
+ if (skb) {
+ cf->can_id |= CAN_ERR_ACK;
+ cf->data[2] |= CAN_ERR_PROT_TX;
+ }
+ break;
+
+ case NCT6694_CAN_EVT_ERR_FORM_ERROR:
+ netdev_dbg(ndev, "Form error\n");
+ ndev->stats.rx_errors++;
+ if (skb)
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+
+ case NCT6694_CAN_EVT_ERR_BIT_ERROR:
+ netdev_dbg(ndev, "Bit error\n");
+ ndev->stats.tx_errors++;
+ if (skb)
+ cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
+ break;
+
+ default:
+ break;
+ }
+
+ if (skb)
+ nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_tx_irq(struct net_device *ndev)
+{
+ struct nct6694_can_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_can_irq(int irq, void *data)
+{
+ struct net_device *ndev = data;
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_event *event = &priv->event[ndev->dev_port];
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CAN_MOD,
+ .cmd = NCT6694_CAN_EVENT,
+ .sel = NCT6694_CAN_EVENT_SEL(ndev->dev_port, NCT6694_CAN_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_CAN_EVT_RX_DATA_IN) {
+ nct6694_can_rx(ndev, event->rx_evt);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
+ nct6694_can_handle_lost_msg(ndev);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->status) {
+ nct6694_can_handle_state_errors(ndev, event->status);
+ handled = IRQ_HANDLED;
+ }
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
+ nct6694_can_handle_bus_err(ndev, event->err);
+ handled = IRQ_HANDLED;
+ }
+
+ if (event->tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY) {
+ nct6694_can_tx_irq(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_can_tx_work(struct work_struct *work)
+{
+ struct nct6694_can_priv *priv = container_of(work,
+ struct nct6694_can_priv,
+ tx_work);
+ struct nct6694_can_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_CAN_MOD,
+ .cmd = NCT6694_CAN_DELIVER,
+ .sel = NCT6694_CAN_DELIVER_SEL(1),
+ .len = cpu_to_le16(sizeof(*frame))
+ };
+ u32 txid;
+ int err;
+
+ memset(frame, 0, sizeof(*frame));
+
+ frame->tag = NCT6694_CAN_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_CAN_FRAME_FLAG_BRS;
+
+ if (cfd->can_id & CAN_EFF_FLAG) {
+ txid = cfd->can_id & CAN_EFF_MASK;
+ frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
+ } else {
+ txid = cfd->can_id & CAN_SFF_MASK;
+ }
+ frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
+ frame->id = cpu_to_le32(txid);
+ frame->length = cfd->len;
+
+ memcpy(frame->data, cfd->data, cfd->len);
+ } 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_CAN_FRAME_FLAG_EFF;
+ } else {
+ txid = cf->can_id & CAN_SFF_MASK;
+ }
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ frame->flag |= NCT6694_CAN_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) {
+ netdev_err(ndev, "%s: TX FIFO is full!\n", __func__);
+ can_free_echo_skb(ndev, 0, NULL);
+ stats->tx_dropped++;
+ stats->tx_errors++;
+ netif_wake_queue(ndev);
+ }
+}
+
+static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct nct6694_can_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_can_start(struct net_device *ndev)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+ const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+ const struct can_bittiming *n_bt = &priv->can.bittiming;
+ struct nct6694_can_setting *setting __free(kfree) = NULL;
+ const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CAN_MOD,
+ .cmd = NCT6694_CAN_SETTING,
+ .sel = ndev->dev_port,
+ .len = cpu_to_le16(sizeof(*setting))
+ };
+ int ret;
+
+ setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+ if (!setting)
+ return -ENOMEM;
+
+ setting->nbr = cpu_to_le32(n_bt->bitrate);
+ setting->dbr = cpu_to_le32(d_bt->bitrate);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
+
+ ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+ if (ret)
+ return ret;
+
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ return 0;
+}
+
+static int nct6694_can_stop(struct net_device *ndev)
+{
+ struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+ priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
+ netif_stop_queue(ndev);
+ free_irq(ndev->irq, ndev);
+ destroy_workqueue(priv->wq);
+ can_rx_offload_disable(&priv->offload);
+ priv->can.state = CAN_STATE_STOPPED;
+ close_candev(ndev);
+
+ return 0;
+}
+
+static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ ret = nct6694_can_start(ndev);
+ if (!ret && netif_queue_stopped(ndev))
+ netif_wake_queue(ndev);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int nct6694_can_open(struct net_device *ndev)
+{
+ struct nct6694_can_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_can_irq, IRQF_ONESHOT,
+ "nct6694_can", ndev);
+ if (ret) {
+ netdev_err(ndev, "Failed to request IRQ\n");
+ goto close_candev;
+ }
+
+ 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_can_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);
+close_candev:
+ can_rx_offload_disable(&priv->offload);
+ close_candev(ndev);
+ return ret;
+}
+
+static const struct net_device_ops nct6694_can_netdev_ops = {
+ .ndo_open = nct6694_can_open,
+ .ndo_stop = nct6694_can_stop,
+ .ndo_start_xmit = nct6694_can_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+static const struct ethtool_ops nct6694_can_ethtool_ops = {
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
+{
+ struct nct6694_can_information *info __free(kfree) = NULL;
+ static const struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_CAN_MOD,
+ .cmd = NCT6694_CAN_INFORMATION,
+ .sel = NCT6694_CAN_INFORMATION_SEL,
+ .len = cpu_to_le16(sizeof(*info))
+ };
+ int ret, can_clk;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
+ if (ret)
+ return ret;
+
+ can_clk = le32_to_cpu(info->can_clk);
+
+ return can_clk;
+}
+
+static int nct6694_can_probe(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_can_priv *priv;
+ struct net_device *ndev;
+ int ret, irq, can_clk;
+
+ irq = irq_create_mapping(nct6694->domain,
+ NCT6694_IRQ_CAN0 + cell->id);
+ if (!irq)
+ return irq;
+
+ ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
+ if (!ndev)
+ return -ENOMEM;
+
+ ndev->irq = irq;
+ ndev->flags |= IFF_ECHO;
+ ndev->dev_port = cell->id;
+ ndev->netdev_ops = &nct6694_can_netdev_ops;
+ ndev->ethtool_ops = &nct6694_can_ethtool_ops;
+
+ priv = netdev_priv(ndev);
+ priv->nct6694 = nct6694;
+ priv->ndev = ndev;
+
+ can_clk = nct6694_can_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_can_tx_work);
+
+ priv->can.state = CAN_STATE_STOPPED;
+ priv->can.clock.freq = can_clk;
+ priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
+ priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
+ priv->can.do_set_mode = nct6694_can_set_mode;
+ priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+ CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
+ CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
+
+ 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);
+ return ret;
+}
+
+static void nct6694_can_remove(struct platform_device *pdev)
+{
+ struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
+
+ unregister_candev(priv->ndev);
+ can_rx_offload_del(&priv->offload);
+ free_candev(priv->ndev);
+}
+
+static struct platform_driver nct6694_can_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_can_probe,
+ .remove = nct6694_can_remove,
+};
+
+module_platform_driver(nct6694_can_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-can");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
@ 2025-02-27 2:08 ` Vincent Mailhol
2025-02-27 6:03 ` Ming Yu
2025-02-27 14:17 ` Marc Kleine-Budde
2025-02-28 10:44 ` Marc Kleine-Budde
` (2 subsequent siblings)
3 siblings, 2 replies; 41+ messages in thread
From: Vincent Mailhol @ 2025-02-27 2:08 UTC (permalink / raw)
To: Ming Yu
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, tmyu0, lee,
linus.walleij, brgl, andi.shyti, mkl, andrew+netdev, davem,
edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni
Hi Ming,
Thanks for the v8. There are a few last things you need to fix. These
are my last comments, so if all of these are addressed, I will give you
my reviewed-by tag in the v9.
On 25/02/2025 at 17:16, Ming Yu wrote:
> This driver supports Socket CANFD functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/net/can/usb/Kconfig | 11 +
> drivers/net/can/usb/Makefile | 1 +
> drivers/net/can/usb/nct6694_canfd.c | 799 ++++++++++++++++++++++++++++
> 4 files changed, 812 insertions(+)
> create mode 100644 drivers/net/can/usb/nct6694_canfd.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1327e7a6e507..8aa611504172 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16925,6 +16925,7 @@ S: Supported
> F: drivers/gpio/gpio-nct6694.c
> F: drivers/i2c/busses/i2c-nct6694.c
> F: drivers/mfd/nct6694.c
> +F: drivers/net/can/usb/nct6694_canfd.c
> F: include/linux/mfd/nct6694.h
>
> NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
> index 9dae0c71a2e1..759e724a67cf 100644
> --- a/drivers/net/can/usb/Kconfig
> +++ b/drivers/net/can/usb/Kconfig
> @@ -133,6 +133,17 @@ config CAN_MCBA_USB
> This driver supports the CAN BUS Analyzer interface
> from Microchip (http://www.microchip.com/development-tools/).
>
> +config CAN_NCT6694
> + tristate "Nuvoton NCT6694 Socket CANfd support"
> + depends on MFD_NCT6694
> + select CAN_RX_OFFLOAD
> + help
> + If you say yes to this option, support will be included for Nuvoton
> + NCT6694, a USB device to socket CANfd controller.
> +
> + This driver can also be built as a module. If so, the module will
> + be called nct6694_canfd.
> +
> config CAN_PEAK_USB
> tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
> help
> diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> index 8b11088e9a59..fcafb1ac262e 100644
> --- a/drivers/net/can/usb/Makefile
> +++ b/drivers/net/can/usb/Makefile
> @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
> obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
> obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
> obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
> obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
> obj-$(CONFIG_CAN_UCAN) += ucan.o
> diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> new file mode 100644
> index 000000000000..d97fce5cdf32
> --- /dev/null
> +++ b/drivers/net/can/usb/nct6694_canfd.c
> @@ -0,0 +1,799 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/can/dev.h>
> +#include <linux/can/rx-offload.h>
> +#include <linux/ethtool.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +
> +#define DRVNAME "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_CAN_MOD 0x05
> +
> +/* Command 00h - CAN Setting and Initialization */
> +#define NCT6694_CAN_SETTING 0x00
> +#define NCT6694_CAN_SETTING_CTRL1_MON BIT(0)
> +#define NCT6694_CAN_SETTING_CTRL1_NISO BIT(1)
> +#define NCT6694_CAN_SETTING_CTRL1_LBCK BIT(2)
> +
> +/* Command 01h - CAN Information */
> +#define NCT6694_CAN_INFORMATION 0x01
> +#define NCT6694_CAN_INFORMATION_SEL 0x00
> +
> +/* Command 02h - CAN Event */
> +#define NCT6694_CAN_EVENT 0x02
> +#define NCT6694_CAN_EVENT_SEL(idx, mask) \
> + ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
> +
> +#define NCT6694_CAN_EVENT_MASK GENMASK(5, 0)
> +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_LOST BIT(5) /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_IN BIT(7) /* Read-clear*/
> +
> +/* Command 10h - CAN Deliver */
> +#define NCT6694_CAN_DELIVER 0x10
> +#define NCT6694_CAN_DELIVER_SEL(buf_cnt) \
> + ((buf_cnt) & 0xFF)
> +
> +/* Command 11h - CAN Receive */
> +#define NCT6694_CAN_RECEIVE 0x11
> +#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt) \
> + ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
> +
> +#define NCT6694_CAN_FRAME_TAG(idx) (0xC0 | (idx))
> +#define NCT6694_CAN_FRAME_FLAG_EFF BIT(0)
> +#define NCT6694_CAN_FRAME_FLAG_RTR BIT(1)
> +#define NCT6694_CAN_FRAME_FLAG_FD BIT(2)
> +#define NCT6694_CAN_FRAME_FLAG_BRS BIT(3)
> +#define NCT6694_CAN_FRAME_FLAG_ERR BIT(4)
> +
> +#define NCT6694_NAPI_WEIGHT 32
> +
> +enum nct6694_event_err {
> + NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
> + NCT6694_CAN_EVT_ERR_CRC_ERROR,
> + NCT6694_CAN_EVT_ERR_STUFF_ERROR,
> + NCT6694_CAN_EVT_ERR_ACK_ERROR,
> + NCT6694_CAN_EVT_ERR_FORM_ERROR,
> + NCT6694_CAN_EVT_ERR_BIT_ERROR,
> + NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
> + NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> + NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
> + NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
> + NCT6694_CAN_EVT_STS_BUS_OFF,
> + NCT6694_CAN_EVT_STS_WARNING,
> +};
> +
> +struct __packed nct6694_can_setting {
> + __le32 nbr;
> + __le32 dbr;
> + u8 active;
> + u8 reserved[3];
> + __le16 ctrl1;
> + __le16 ctrl2;
> + __le32 nbtp;
> + __le32 dbtp;
> +};
> +
> +struct __packed nct6694_can_information {
> + u8 tx_fifo_cnt;
> + u8 rx_fifo_cnt;
> + u8 reserved[2];
> + __le32 can_clk;
> +};
> +
> +struct __packed nct6694_can_event {
> + u8 err;
> + u8 status;
> + u8 tx_evt;
> + u8 rx_evt;
> + u8 rec;
> + u8 tec;
> + u8 reserved[2];
> +};
> +
> +struct __packed nct6694_can_frame {
> + u8 tag;
> + u8 flag;
> + u8 reserved;
> + u8 length;
> + __le32 id;
> + u8 data[CANFD_MAX_DLEN];
> +};
> +
> +struct nct6694_can_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_can_frame tx;
> + struct nct6694_can_frame rx;
> + struct nct6694_can_event event[2];
> + struct can_berr_counter bec;
> +};
> +
> +static inline struct nct6694_can_priv *rx_offload_to_priv(struct can_rx_offload *offload)
> +{
> + return container_of(offload, struct nct6694_can_priv, offload);
> +}
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> + .name = DRVNAME,
> + .tseg1_min = 2,
> + .tseg1_max = 256,
> + .tseg2_min = 2,
> + .tseg2_max = 128,
> + .sjw_max = 128,
> + .brp_min = 1,
> + .brp_max = 511,
> + .brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> + .name = DRVNAME,
> + .tseg1_min = 1,
> + .tseg1_max = 32,
> + .tseg2_min = 1,
> + .tseg2_max = 16,
> + .sjw_max = 16,
> + .brp_min = 1,
> + .brp_max = 31,
> + .brp_inc = 1,
> +};
> +
> +static void nct6694_can_rx_offload(struct can_rx_offload *offload,
> + struct sk_buff *skb)
> +{
> + struct nct6694_can_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_can_handle_lost_msg(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + netdev_err(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_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_frame *frame = &priv->rx;
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_RECEIVE,
> + .sel = NCT6694_CAN_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_CAN_FRAME_FLAG_FD) {
> + struct canfd_frame *cfd;
> +
> + skb = alloc_canfd_skb(priv->ndev, &cfd);
> + if (!skb)
> + return;
> +
> + cfd->can_id = le32_to_cpu(frame->id);
> + cfd->len = canfd_sanitize_len(frame->length);
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> + cfd->can_id |= CAN_EFF_FLAG;
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
> + cfd->flags |= CANFD_BRS;
> + if (frame->flag & NCT6694_CAN_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)
> + return;
> +
> + cf->can_id = le32_to_cpu(frame->id);
> + cf->len = can_cc_dlc2len(frame->length);
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> + cf->can_id |= CAN_EFF_FLAG;
> +
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
> + cf->can_id |= CAN_RTR_FLAG;
> + else
> + memcpy(cf->data, frame->data, cf->len);
> + }
> +
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> + struct can_berr_counter *bec)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + *bec = priv->bec;
> +
> + return 0;
> +}
> +
> +static void nct6694_can_handle_state_change(struct net_device *ndev,
> + enum can_state new_state)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct can_berr_counter bec;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + skb = alloc_can_err_skb(ndev, &cf);
> +
> + nct6694_can_get_berr_counter(ndev, &bec);
> +
> + switch (new_state) {
> + case CAN_STATE_ERROR_ACTIVE:
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> + if (cf)
Set the CAN_ER_CRTL flag:
if (cf) {
cf->can_id |= CAN_ERR_CRTL;
cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
}
> + cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
> + break;
> + case CAN_STATE_ERROR_WARNING:
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_WARNING;
> + if (cf) {
> + cf->can_id |= CAN_ERR_CRTL;
Set the CAN_ERR_CNT flag when you populate cf->data[6] and cf->data[7]:
cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
> + if (bec.txerr > bec.rxerr)
> + cf->data[1] = CAN_ERR_CRTL_TX_WARNING;
> + else
> + cf->data[1] = CAN_ERR_CRTL_RX_WARNING;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + }
> + break;
> + case CAN_STATE_ERROR_PASSIVE:
> + priv->can.can_stats.error_passive++;
> + priv->can.state = CAN_STATE_ERROR_PASSIVE;
> + if (cf) {
> + cf->can_id |= CAN_ERR_CRTL;
Set the CAN_ERR_CNT flag when you populate cf->data[6] and cf->data[7]:
cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
> + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> + if (bec.txerr >= CAN_ERROR_PASSIVE_THRESHOLD)
> + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + }
> + break;
> + case CAN_STATE_BUS_OFF:
> + priv->can.state = CAN_STATE_BUS_OFF;
> + priv->can.can_stats.bus_off++;
> + if (cf)
> + cf->can_id |= CAN_ERR_BUSOFF;
> + can_free_echo_skb(ndev, 0, NULL);
> + netif_stop_queue(ndev);> + can_bus_off(ndev);
> + break;
> + default:
> + break;
> + }
> +
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_handle_state_errors(struct net_device *ndev, u8 status)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + if (status == NCT6694_CAN_EVT_STS_ERROR_ACTIVE &&
> + priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> + netdev_dbg(ndev, "Error, entered active state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_ACTIVE);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_WARNING &&
> + priv->can.state != CAN_STATE_ERROR_WARNING) {
> + netdev_dbg(ndev, "Error, entered warning state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_WARNING);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_ERROR_PASSIVE &&
> + priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> + netdev_dbg(ndev, "Error, entered passive state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_PASSIVE);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_BUS_OFF &&
> + priv->can.state != CAN_STATE_BUS_OFF) {
> + netdev_dbg(ndev, "Error, entered bus-off state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_BUS_OFF);
> + }
> +}
> +
> +static void nct6694_can_handle_bus_err(struct net_device *ndev, u8 bus_err)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
> + return;
> +
> + priv->can.can_stats.bus_error++;
> +
> + skb = alloc_can_err_skb(ndev, &cf);
> + if (skb)
> + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> + switch (bus_err) {
> + case NCT6694_CAN_EVT_ERR_CRC_ERROR:
> + netdev_dbg(ndev, "CRC error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
> + netdev_dbg(ndev, "Stuff error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_STUFF;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_ACK_ERROR:
> + netdev_dbg(ndev, "Ack error\n");
> + ndev->stats.tx_errors++;
> + if (skb) {
> + cf->can_id |= CAN_ERR_ACK;
> + cf->data[2] |= CAN_ERR_PROT_TX;
> + }
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_FORM_ERROR:
> + netdev_dbg(ndev, "Form error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_FORM;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_BIT_ERROR:
> + netdev_dbg(ndev, "Bit error\n");
> + ndev->stats.tx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
> + break;
> +
> + default:
> + break;
> + }
> +
> + if (skb)
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_tx_irq(struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_irq(int irq, void *data)
> +{
> + struct net_device *ndev = data;
> + struct nct6694_can_priv *priv = netdev_priv(ndev);> + struct nct6694_can_event *event = &priv->event[ndev->dev_port];
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_EVENT,
> + .sel = NCT6694_CAN_EVENT_SEL(ndev->dev_port, NCT6694_CAN_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_CAN_EVT_RX_DATA_IN) {
> + nct6694_can_rx(ndev, event->rx_evt);
> + handled = IRQ_HANDLED;
> + }
> +
> + if (event->rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
> + nct6694_can_handle_lost_msg(ndev);
> + handled = IRQ_HANDLED;
> + }
> +
> + if (event->status) {
> + nct6694_can_handle_state_errors(ndev, event->status);
> + handled = IRQ_HANDLED;
> + }
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
> + nct6694_can_handle_bus_err(ndev, event->err);
> + handled = IRQ_HANDLED;
> + }
> +
> + if (event->tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY) {
> + nct6694_can_tx_irq(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_can_tx_work(struct work_struct *work)
> +{
> + struct nct6694_can_priv *priv = container_of(work,
> + struct nct6694_can_priv,
> + tx_work);
> + struct nct6694_can_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_CAN_MOD,
> + .cmd = NCT6694_CAN_DELIVER,
> + .sel = NCT6694_CAN_DELIVER_SEL(1),
> + .len = cpu_to_le16(sizeof(*frame))
> + };
> + u32 txid;
> + int err;
> +
> + memset(frame, 0, sizeof(*frame));
> +
> + frame->tag = NCT6694_CAN_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_CAN_FRAME_FLAG_BRS;
> +
> + if (cfd->can_id & CAN_EFF_FLAG) {
> + txid = cfd->can_id & CAN_EFF_MASK;
> + frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> + } else {
> + txid = cfd->can_id & CAN_SFF_MASK;
> + }
> + frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
> + frame->id = cpu_to_le32(txid);
> + frame->length = cfd->len;
> +
> + memcpy(frame->data, cfd->data, cfd->len);
> + } 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_CAN_FRAME_FLAG_EFF;
> + } else {
> + txid = cf->can_id & CAN_SFF_MASK;
> + }
> +
> + if (cf->can_id & CAN_RTR_FLAG)
> + frame->flag |= NCT6694_CAN_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) {
> + netdev_err(ndev, "%s: TX FIFO is full!\n", __func__);
> + can_free_echo_skb(ndev, 0, NULL);
> + stats->tx_dropped++;
> + stats->tx_errors++;
> + netif_wake_queue(ndev);
> + }
> +}
> +
> +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_start(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> + const struct can_bittiming *n_bt = &priv->can.bittiming;
> + struct nct6694_can_setting *setting __free(kfree) = NULL;> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_SETTING,
> + .sel = ndev->dev_port,
> + .len = cpu_to_le16(sizeof(*setting))
> + };
> + int ret;
> +
> + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> + if (!setting)
> + return -ENOMEM;
> +
> + setting->nbr = cpu_to_le32(n_bt->bitrate);
> + setting->dbr = cpu_to_le32(d_bt->bitrate);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
> +
> + ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> + if (ret)
> + return ret;
> +
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> + return 0;
> +}
> +
> +static int nct6694_can_stop(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
Hmmm, when Marc asked you to put the device in listen only mode, I think
he meant that you set it on the device side (i.e. flag
NCT6694_CAN_SETTING_CTRL1_MON) and not on the driver side. If you set
CAN_CTRLMODE_LISTENONLY flag, that will be reported in the netlink
interface. So you should not change that flag.
But before that, did you check the datasheet? Don't you have a device
flag to actually turn the device off (e.g. sleep mode)?
> + netif_stop_queue(ndev);
> + free_irq(ndev->irq, ndev);
> + destroy_workqueue(priv->wq);
> + can_rx_offload_disable(&priv->offload);
> + priv->can.state = CAN_STATE_STOPPED;
> + close_candev(ndev);
> +
> + return 0;
> +}
> +
> +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> + int ret;
> +
> + switch (mode) {
> + case CAN_MODE_START:
> + ret = nct6694_can_start(ndev);
> + if (!ret && netif_queue_stopped(ndev))
> + netif_wake_queue(ndev);
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return ret;
> +}
> +
> +static int nct6694_can_open(struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_irq, IRQF_ONESHOT,
> + "nct6694_can", ndev);
> + if (ret) {
> + netdev_err(ndev, "Failed to request IRQ\n");
> + goto close_candev;
> + }
> +
> + 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_can_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);
> +close_candev:
> + can_rx_offload_disable(&priv->offload);
> + close_candev(ndev);
> + return ret;
> +}
> +
> +static const struct net_device_ops nct6694_can_netdev_ops = {
> + .ndo_open = nct6694_can_open,
> + .ndo_stop = nct6694_can_stop,
> + .ndo_start_xmit = nct6694_can_start_xmit,
> + .ndo_change_mtu = can_change_mtu,
> +};
> +
> +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> + .get_ts_info = ethtool_op_get_ts_info,
> +};
> +
> +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> +{
> + struct nct6694_can_information *info __free(kfree) = NULL;
> + static const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_INFORMATION,
> + .sel = NCT6694_CAN_INFORMATION_SEL,
> + .len = cpu_to_le16(sizeof(*info))
> + };
> + int ret, can_clk;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> + if (ret)
> + return ret;
> +
> + can_clk = le32_to_cpu(info->can_clk);
> +
> + return can_clk;
> +}
> +
> +static int nct6694_can_probe(struct platform_device *pdev)
> +{
> + const struct mfd_cell *cell = mfd_get_cell(pdev);
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + struct nct6694_can_priv *priv;
> + struct net_device *ndev;
> + int ret, irq, can_clk;
> +
> + irq = irq_create_mapping(nct6694->domain,
> + NCT6694_IRQ_CAN0 + cell->id);
> + if (!irq)
> + return irq;
> +
> + ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> + if (!ndev)
> + return -ENOMEM;
> +
> + ndev->irq = irq;
> + ndev->flags |= IFF_ECHO;
> + ndev->dev_port = cell->id;
> + ndev->netdev_ops = &nct6694_can_netdev_ops;
> + ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> +
> + priv = netdev_priv(ndev);
> + priv->nct6694 = nct6694;
> + priv->ndev = ndev;
> +
> + can_clk = nct6694_can_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_can_tx_work);
> +
> + priv->can.state = CAN_STATE_STOPPED;
Marc asked you to remove this line during the v7 review.
> + priv->can.clock.freq = can_clk;
> + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> + priv->can.do_set_mode = nct6694_can_set_mode;
> + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> +
> + 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);
> + return ret;
> +}
> +
> +static void nct6694_can_remove(struct platform_device *pdev)
> +{
> + struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> +
> + unregister_candev(priv->ndev);
> + can_rx_offload_del(&priv->offload);
> + free_candev(priv->ndev);
> +}
> +
> +static struct platform_driver nct6694_can_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_can_probe,
> + .remove = nct6694_can_remove,
> +};
> +
> +module_platform_driver(nct6694_can_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-can");
Yours sincerely,
Vincent Mailhol
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-27 2:08 ` Vincent Mailhol
@ 2025-02-27 6:03 ` Ming Yu
2025-02-27 14:17 ` Marc Kleine-Budde
1 sibling, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-27 6:03 UTC (permalink / raw)
To: Vincent Mailhol
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, tmyu0, lee,
linus.walleij, brgl, andi.shyti, mkl, andrew+netdev, davem,
edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni
Dear Vincent,
Thank you for reviewing,
Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2025年2月27日 週四 上午10:09寫道:
>
...
> > +static void nct6694_can_handle_state_change(struct net_device *ndev,
> > + enum can_state new_state)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct can_berr_counter bec;
> > + struct can_frame *cf;
> > + struct sk_buff *skb;
> > +
> > + skb = alloc_can_err_skb(ndev, &cf);
> > +
> > + nct6694_can_get_berr_counter(ndev, &bec);
> > +
> > + switch (new_state) {
> > + case CAN_STATE_ERROR_ACTIVE:
> > + priv->can.can_stats.error_warning++;
> > + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > + if (cf)
>
> Set the CAN_ER_CRTL flag:
>
> if (cf) {
> cf->can_id |= CAN_ERR_CRTL;
> cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
> }
>
Fix it in the v9.
> > + cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
> > + break;
> > + case CAN_STATE_ERROR_WARNING:
> > + priv->can.can_stats.error_warning++;
> > + priv->can.state = CAN_STATE_ERROR_WARNING;
> > + if (cf) {
> > + cf->can_id |= CAN_ERR_CRTL;
>
> Set the CAN_ERR_CNT flag when you populate cf->data[6] and cf->data[7]:
>
> cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
>
Fix it in the v9.
> > + if (bec.txerr > bec.rxerr)
> > + cf->data[1] = CAN_ERR_CRTL_TX_WARNING;
> > + else
> > + cf->data[1] = CAN_ERR_CRTL_RX_WARNING;
> > + cf->data[6] = bec.txerr;
> > + cf->data[7] = bec.rxerr;
> > + }
> > + break;
> > + case CAN_STATE_ERROR_PASSIVE:
> > + priv->can.can_stats.error_passive++;
> > + priv->can.state = CAN_STATE_ERROR_PASSIVE;
> > + if (cf) {
> > + cf->can_id |= CAN_ERR_CRTL;
>
> Set the CAN_ERR_CNT flag when you populate cf->data[6] and cf->data[7]:
>
> cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT;
>
Fix it in the v9.
> > + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> > + if (bec.txerr >= CAN_ERROR_PASSIVE_THRESHOLD)
> > + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> > + cf->data[6] = bec.txerr;
> > + cf->data[7] = bec.rxerr;
> > + }
> > + break;
> > + case CAN_STATE_BUS_OFF:
> > + priv->can.state = CAN_STATE_BUS_OFF;
> > + priv->can.can_stats.bus_off++;
> > + if (cf)
> > + cf->can_id |= CAN_ERR_BUSOFF;
> > + can_free_echo_skb(ndev, 0, NULL);
> > + netif_stop_queue(ndev);> + can_bus_off(ndev);
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
...
> > +static int nct6694_can_stop(struct net_device *ndev)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
>
> Hmmm, when Marc asked you to put the device in listen only mode, I think
> he meant that you set it on the device side (i.e. flag
> NCT6694_CAN_SETTING_CTRL1_MON) and not on the driver side. If you set
> CAN_CTRLMODE_LISTENONLY flag, that will be reported in the netlink
> interface. So you should not change that flag.
>
> But before that, did you check the datasheet? Don't you have a device
> flag to actually turn the device off (e.g. sleep mode)?
>
Our firmware currently does not provide an interface to turn the
device off, I will put the device in listen-only mode as an
alternative.
> > + netif_stop_queue(ndev);
> > + free_irq(ndev->irq, ndev);
> > + destroy_workqueue(priv->wq);
> > + can_rx_offload_disable(&priv->offload);
> > + priv->can.state = CAN_STATE_STOPPED;
> > + close_candev(ndev);
> > +
> > + return 0;
> > +}
> > +
...
> > +static int nct6694_can_probe(struct platform_device *pdev)
> > +{
> > + const struct mfd_cell *cell = mfd_get_cell(pdev);
> > + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > + struct nct6694_can_priv *priv;
> > + struct net_device *ndev;
> > + int ret, irq, can_clk;
> > +
> > + irq = irq_create_mapping(nct6694->domain,
> > + NCT6694_IRQ_CAN0 + cell->id);
> > + if (!irq)
> > + return irq;
> > +
> > + ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> > + if (!ndev)
> > + return -ENOMEM;
> > +
> > + ndev->irq = irq;
> > + ndev->flags |= IFF_ECHO;
> > + ndev->dev_port = cell->id;
> > + ndev->netdev_ops = &nct6694_can_netdev_ops;
> > + ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> > +
> > + priv = netdev_priv(ndev);
> > + priv->nct6694 = nct6694;
> > + priv->ndev = ndev;
> > +
> > + can_clk = nct6694_can_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_can_tx_work);
> > +
> > + priv->can.state = CAN_STATE_STOPPED;
>
> Marc asked you to remove this line during the v7 review.
>
Sorry, drop it in the v9.
> > + priv->can.clock.freq = can_clk;
> > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > + priv->can.do_set_mode = nct6694_can_set_mode;
> > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > +
> > + 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);
> > + return ret;
> > +}
> > +
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-27 2:08 ` Vincent Mailhol
2025-02-27 6:03 ` Ming Yu
@ 2025-02-27 14:17 ` Marc Kleine-Budde
2025-03-17 2:08 ` Ming Yu
1 sibling, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-02-27 14:17 UTC (permalink / raw)
To: Vincent Mailhol
Cc: Ming Yu, linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, tmyu0, lee,
linus.walleij, brgl, andi.shyti, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, linux, jdelvare, alexandre.belloni
[-- Attachment #1: Type: text/plain, Size: 1175 bytes --]
On 27.02.2025 11:08:50, Vincent Mailhol wrote:
> > +static int nct6694_can_stop(struct net_device *ndev)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
>
> Hmmm, when Marc asked you to put the device in listen only mode, I think
> he meant that you set it on the device side (i.e. flag
> NCT6694_CAN_SETTING_CTRL1_MON) and not on the driver side. If you set
> CAN_CTRLMODE_LISTENONLY flag, that will be reported in the netlink
> interface. So you should not change that flag.
ACK
> But before that, did you check the datasheet? Don't you have a device
> flag to actually turn the device off (e.g. sleep mode)?
Please test that the ifup -> ifdown -> ifup sequence works properly,
even on a busy bus and on a bus without with a 2nd CAN station that is
sending and you are the only receiver.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-27 14:17 ` Marc Kleine-Budde
@ 2025-03-17 2:08 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-03-17 2:08 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: Vincent Mailhol, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-rtc, linux-usb, tmyu0,
lee, linus.walleij, brgl, andi.shyti, andrew+netdev, davem,
edumazet, kuba, pabeni, wim, linux, jdelvare, alexandre.belloni
Dear Marc,
Thank you for reviewing,
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年2月27日 週四 下午10:17寫道:
>
> On 27.02.2025 11:08:50, Vincent Mailhol wrote:
> > > +static int nct6694_can_stop(struct net_device *ndev)
> > > +{
> > > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > +
> > > + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
> >
> > Hmmm, when Marc asked you to put the device in listen only mode, I think
> > he meant that you set it on the device side (i.e. flag
> > NCT6694_CAN_SETTING_CTRL1_MON) and not on the driver side. If you set
> > CAN_CTRLMODE_LISTENONLY flag, that will be reported in the netlink
> > interface. So you should not change that flag.
>
> ACK
>
> > But before that, did you check the datasheet? Don't you have a device
> > flag to actually turn the device off (e.g. sleep mode)?
>
> Please test that the ifup -> ifdown -> ifup sequence works properly,
> even on a busy bus and on a bus without with a 2nd CAN station that is
> sending and you are the only receiver.
>
Understood.
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
2025-02-27 2:08 ` Vincent Mailhol
@ 2025-02-28 10:44 ` Marc Kleine-Budde
2025-03-17 2:24 ` Ming Yu
2025-03-17 10:41 ` Marc Kleine-Budde
2025-03-26 21:56 ` Christophe JAILLET
3 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-02-28 10:44 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 27873 bytes --]
On 25.02.2025 16:16:41, Ming Yu wrote:
> This driver supports Socket CANFD functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/net/can/usb/Kconfig | 11 +
> drivers/net/can/usb/Makefile | 1 +
> drivers/net/can/usb/nct6694_canfd.c | 799 ++++++++++++++++++++++++++++
> 4 files changed, 812 insertions(+)
> create mode 100644 drivers/net/can/usb/nct6694_canfd.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1327e7a6e507..8aa611504172 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16925,6 +16925,7 @@ S: Supported
> F: drivers/gpio/gpio-nct6694.c
> F: drivers/i2c/busses/i2c-nct6694.c
> F: drivers/mfd/nct6694.c
> +F: drivers/net/can/usb/nct6694_canfd.c
> F: include/linux/mfd/nct6694.h
>
> NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
> index 9dae0c71a2e1..759e724a67cf 100644
> --- a/drivers/net/can/usb/Kconfig
> +++ b/drivers/net/can/usb/Kconfig
> @@ -133,6 +133,17 @@ config CAN_MCBA_USB
> This driver supports the CAN BUS Analyzer interface
> from Microchip (http://www.microchip.com/development-tools/).
>
> +config CAN_NCT6694
> + tristate "Nuvoton NCT6694 Socket CANfd support"
> + depends on MFD_NCT6694
> + select CAN_RX_OFFLOAD
> + help
> + If you say yes to this option, support will be included for Nuvoton
> + NCT6694, a USB device to socket CANfd controller.
> +
> + This driver can also be built as a module. If so, the module will
> + be called nct6694_canfd.
> +
> config CAN_PEAK_USB
> tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
> help
> diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> index 8b11088e9a59..fcafb1ac262e 100644
> --- a/drivers/net/can/usb/Makefile
> +++ b/drivers/net/can/usb/Makefile
> @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
> obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
> obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
> obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
> obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
> obj-$(CONFIG_CAN_UCAN) += ucan.o
> diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> new file mode 100644
> index 000000000000..d97fce5cdf32
> --- /dev/null
> +++ b/drivers/net/can/usb/nct6694_canfd.c
> @@ -0,0 +1,799 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/can/dev.h>
> +#include <linux/can/rx-offload.h>
> +#include <linux/ethtool.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +
> +#define DRVNAME "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_CAN_MOD 0x05
> +
> +/* Command 00h - CAN Setting and Initialization */
> +#define NCT6694_CAN_SETTING 0x00
> +#define NCT6694_CAN_SETTING_CTRL1_MON BIT(0)
> +#define NCT6694_CAN_SETTING_CTRL1_NISO BIT(1)
> +#define NCT6694_CAN_SETTING_CTRL1_LBCK BIT(2)
> +
> +/* Command 01h - CAN Information */
> +#define NCT6694_CAN_INFORMATION 0x01
> +#define NCT6694_CAN_INFORMATION_SEL 0x00
> +
> +/* Command 02h - CAN Event */
> +#define NCT6694_CAN_EVENT 0x02
> +#define NCT6694_CAN_EVENT_SEL(idx, mask) \
> + ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
> +
> +#define NCT6694_CAN_EVENT_MASK GENMASK(5, 0)
> +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_LOST BIT(5) /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_IN BIT(7) /* Read-clear*/
> +
> +/* Command 10h - CAN Deliver */
> +#define NCT6694_CAN_DELIVER 0x10
> +#define NCT6694_CAN_DELIVER_SEL(buf_cnt) \
> + ((buf_cnt) & 0xFF)
> +
> +/* Command 11h - CAN Receive */
> +#define NCT6694_CAN_RECEIVE 0x11
> +#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt) \
> + ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
> +
> +#define NCT6694_CAN_FRAME_TAG(idx) (0xC0 | (idx))
> +#define NCT6694_CAN_FRAME_FLAG_EFF BIT(0)
> +#define NCT6694_CAN_FRAME_FLAG_RTR BIT(1)
> +#define NCT6694_CAN_FRAME_FLAG_FD BIT(2)
> +#define NCT6694_CAN_FRAME_FLAG_BRS BIT(3)
> +#define NCT6694_CAN_FRAME_FLAG_ERR BIT(4)
> +
> +#define NCT6694_NAPI_WEIGHT 32
> +
> +enum nct6694_event_err {
> + NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
> + NCT6694_CAN_EVT_ERR_CRC_ERROR,
> + NCT6694_CAN_EVT_ERR_STUFF_ERROR,
> + NCT6694_CAN_EVT_ERR_ACK_ERROR,
> + NCT6694_CAN_EVT_ERR_FORM_ERROR,
> + NCT6694_CAN_EVT_ERR_BIT_ERROR,
> + NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
> + NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> + NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
> + NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
> + NCT6694_CAN_EVT_STS_BUS_OFF,
> + NCT6694_CAN_EVT_STS_WARNING,
> +};
> +
> +struct __packed nct6694_can_setting {
> + __le32 nbr;
> + __le32 dbr;
> + u8 active;
> + u8 reserved[3];
> + __le16 ctrl1;
> + __le16 ctrl2;
> + __le32 nbtp;
> + __le32 dbtp;
> +};
> +
> +struct __packed nct6694_can_information {
> + u8 tx_fifo_cnt;
> + u8 rx_fifo_cnt;
> + u8 reserved[2];
> + __le32 can_clk;
> +};
> +
> +struct __packed nct6694_can_event {
> + u8 err;
> + u8 status;
> + u8 tx_evt;
> + u8 rx_evt;
> + u8 rec;
> + u8 tec;
> + u8 reserved[2];
> +};
> +
> +struct __packed nct6694_can_frame {
> + u8 tag;
> + u8 flag;
> + u8 reserved;
> + u8 length;
> + __le32 id;
> + u8 data[CANFD_MAX_DLEN];
> +};
> +
> +struct nct6694_can_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_can_frame tx;
> + struct nct6694_can_frame rx;
> + struct nct6694_can_event event[2];
> + struct can_berr_counter bec;
> +};
> +
> +static inline struct nct6694_can_priv *rx_offload_to_priv(struct can_rx_offload *offload)
> +{
> + return container_of(offload, struct nct6694_can_priv, offload);
> +}
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> + .name = DRVNAME,
> + .tseg1_min = 2,
> + .tseg1_max = 256,
> + .tseg2_min = 2,
> + .tseg2_max = 128,
> + .sjw_max = 128,
> + .brp_min = 1,
> + .brp_max = 511,
> + .brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> + .name = DRVNAME,
> + .tseg1_min = 1,
> + .tseg1_max = 32,
> + .tseg2_min = 1,
> + .tseg2_max = 16,
> + .sjw_max = 16,
> + .brp_min = 1,
> + .brp_max = 31,
> + .brp_inc = 1,
> +};
> +
> +static void nct6694_can_rx_offload(struct can_rx_offload *offload,
> + struct sk_buff *skb)
> +{
> + struct nct6694_can_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_can_handle_lost_msg(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
Please make it a netdev_dbg()
> +
> + 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_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_frame *frame = &priv->rx;
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_RECEIVE,
> + .sel = NCT6694_CAN_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_CAN_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_CAN_FRAME_FLAG_EFF)
> + cfd->can_id |= CAN_EFF_FLAG;
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
> + cfd->flags |= CANFD_BRS;
> + if (frame->flag & NCT6694_CAN_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_CAN_FRAME_FLAG_EFF)
> + cf->can_id |= CAN_EFF_FLAG;
> +
> + if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
> + cf->can_id |= CAN_RTR_FLAG;
> + else
> + memcpy(cf->data, frame->data, cf->len);
> + }
> +
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> + struct can_berr_counter *bec)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + *bec = priv->bec;
> +
> + return 0;
> +}
> +
> +static void nct6694_can_handle_state_change(struct net_device *ndev,
> + enum can_state new_state)
> +{
I just replied to one of your questions of the last series:
| https://lore.kernel.org/20250228-magic-seahorse-of-abracadabra-f2a402-mkl@pengutronix.de
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct can_berr_counter bec;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + skb = alloc_can_err_skb(ndev, &cf);
> +
> + nct6694_can_get_berr_counter(ndev, &bec);
> +
> + switch (new_state) {
> + case CAN_STATE_ERROR_ACTIVE:
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> + if (cf)
> + cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
> + break;
> + case CAN_STATE_ERROR_WARNING:
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_WARNING;
> + if (cf) {
> + cf->can_id |= CAN_ERR_CRTL;
> + if (bec.txerr > bec.rxerr)
> + cf->data[1] = CAN_ERR_CRTL_TX_WARNING;
> + else
> + cf->data[1] = CAN_ERR_CRTL_RX_WARNING;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + }
> + break;
> + case CAN_STATE_ERROR_PASSIVE:
> + priv->can.can_stats.error_passive++;
> + priv->can.state = CAN_STATE_ERROR_PASSIVE;
> + if (cf) {
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> + if (bec.txerr >= CAN_ERROR_PASSIVE_THRESHOLD)
> + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + }
> + break;
> + case CAN_STATE_BUS_OFF:
> + priv->can.state = CAN_STATE_BUS_OFF;
> + priv->can.can_stats.bus_off++;
> + if (cf)
> + cf->can_id |= CAN_ERR_BUSOFF;
no need for can_free_echo_skb() and netif_stop_queue()
> + can_free_echo_skb(ndev, 0, NULL);
> + netif_stop_queue(ndev);
> + can_bus_off(ndev);
> + break;
> + default:
> + break;
> + }
> +
if (skb)
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_handle_state_errors(struct net_device *ndev, u8 status)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + if (status == NCT6694_CAN_EVT_STS_ERROR_ACTIVE &&
> + priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> + netdev_dbg(ndev, "Error, entered active state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_ACTIVE);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_WARNING &&
> + priv->can.state != CAN_STATE_ERROR_WARNING) {
> + netdev_dbg(ndev, "Error, entered warning state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_WARNING);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_ERROR_PASSIVE &&
> + priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> + netdev_dbg(ndev, "Error, entered passive state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_PASSIVE);
> + }
> +
> + if (status == NCT6694_CAN_EVT_STS_BUS_OFF &&
> + priv->can.state != CAN_STATE_BUS_OFF) {
> + netdev_dbg(ndev, "Error, entered bus-off state\n");
> + nct6694_can_handle_state_change(ndev, CAN_STATE_BUS_OFF);
> + }
> +}
> +
> +static void nct6694_can_handle_bus_err(struct net_device *ndev, u8 bus_err)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
> + return;
> +
> + priv->can.can_stats.bus_error++;
> +
> + skb = alloc_can_err_skb(ndev, &cf);
make it: "if (cf)" instead of "if (skb)" then interacting with the "cf".
> + if (skb)
> + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> + switch (bus_err) {
> + case NCT6694_CAN_EVT_ERR_CRC_ERROR:
> + netdev_dbg(ndev, "CRC error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
> + netdev_dbg(ndev, "Stuff error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_STUFF;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_ACK_ERROR:
> + netdev_dbg(ndev, "Ack error\n");
> + ndev->stats.tx_errors++;
> + if (skb) {
> + cf->can_id |= CAN_ERR_ACK;
> + cf->data[2] |= CAN_ERR_PROT_TX;
> + }
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_FORM_ERROR:
> + netdev_dbg(ndev, "Form error\n");
> + ndev->stats.rx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_FORM;
> + break;
> +
> + case NCT6694_CAN_EVT_ERR_BIT_ERROR:
> + netdev_dbg(ndev, "Bit error\n");
> + ndev->stats.tx_errors++;
> + if (skb)
> + cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
> + break;
> +
> + default:
> + break;
> + }
> +
> + if (skb)
> + nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_tx_irq(struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_irq(int irq, void *data)
> +{
> + struct net_device *ndev = data;
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_event *event = &priv->event[ndev->dev_port];
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_EVENT,
> + .sel = NCT6694_CAN_EVENT_SEL(ndev->dev_port, NCT6694_CAN_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_CAN_EVT_RX_DATA_IN) {
> + nct6694_can_rx(ndev, event->rx_evt);
just for symmetry name rename nct6694_can_rx() to nct6694_can_handle_rx()
> + handled = IRQ_HANDLED;
> + }
> +
> + if (event->rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
> + nct6694_can_handle_lost_msg(ndev);
> + handled = IRQ_HANDLED;
> + }
> +
> + if (event->status) {
> + nct6694_can_handle_state_errors(ndev, event->status);
I think you should call nct6694_can_handle_state_change() directly and
get rid of the code on nct6694_can_handle_state_errors().
> + handled = IRQ_HANDLED;
> + }
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
> + nct6694_can_handle_bus_err(ndev, event->err);
> + handled = IRQ_HANDLED;
> + }
The logic needs to be refined. If there was an IRQ you must say you've
handled it, event if user space doesn't care for bus errors.
The better solution is to disabled event->err related IRQs in the
hardware, if CAN_CTRLMODE_BERR_REPORTING is disabled, so you don't get
an IRQ in the first place.
if (event->err != NCT6694_CAN_EVT_ERR_NO_ERROR) {
if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
nct6694_can_handle_bus_err(ndev, event->err);
handled = IRQ_HANDLED;
}
> +
> + if (event->tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY) {
> + nct6694_can_tx_irq(ndev);
just for symmetry name rename nct6694_can_tx() to nct6694_can_handle_tx()
> + 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_can_tx_work(struct work_struct *work)
> +{
> + struct nct6694_can_priv *priv = container_of(work,
> + struct nct6694_can_priv,
> + tx_work);
> + struct nct6694_can_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_CAN_MOD,
> + .cmd = NCT6694_CAN_DELIVER,
> + .sel = NCT6694_CAN_DELIVER_SEL(1),
> + .len = cpu_to_le16(sizeof(*frame))
> + };
> + u32 txid;
> + int err;
> +
> + memset(frame, 0, sizeof(*frame));
> +
> + frame->tag = NCT6694_CAN_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_CAN_FRAME_FLAG_BRS;
> +
> + if (cfd->can_id & CAN_EFF_FLAG) {
> + txid = cfd->can_id & CAN_EFF_MASK;
> + frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> + } else {
> + txid = cfd->can_id & CAN_SFF_MASK;
> + }
> + frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
> + frame->id = cpu_to_le32(txid);
> + frame->length = cfd->len;
frame->length = canfd_sanitize_len(cfd->len);
> +
> + memcpy(frame->data, cfd->data, 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_CAN_FRAME_FLAG_EFF;
> + } else {
> + txid = cf->can_id & CAN_SFF_MASK;
> + }
> +
> + if (cf->can_id & CAN_RTR_FLAG)
> + frame->flag |= NCT6694_CAN_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) {
> + netdev_err(ndev, "%s: TX FIFO is full!\n", __func__);
The FIFO should not be full at this time. There also could be an USB
error due to unlug or EMI. I think you should remove this error message.
> + can_free_echo_skb(ndev, 0, NULL);
> + stats->tx_dropped++;
> + stats->tx_errors++;
> + netif_wake_queue(ndev);
> + }
> +}
> +
> +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_start(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> + const struct can_bittiming *n_bt = &priv->can.bittiming;
> + struct nct6694_can_setting *setting __free(kfree) = NULL;
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_SETTING,
> + .sel = ndev->dev_port,
> + .len = cpu_to_le16(sizeof(*setting))
> + };
> + int ret;
> +
> + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> + if (!setting)
> + return -ENOMEM;
> +
> + setting->nbr = cpu_to_le32(n_bt->bitrate);
> + setting->dbr = cpu_to_le32(d_bt->bitrate);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
> +
> + ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> + if (ret)
> + return ret;
> +
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> + return 0;
> +}
> +
> +static int nct6694_can_stop(struct net_device *ndev)
static void
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
As Vincent already pointed out, bring you device into listen only mode.
Please add a comment why you do it, because it cannot be stopped.
> + netif_stop_queue(ndev);
> + free_irq(ndev->irq, ndev);
> + destroy_workqueue(priv->wq);
> + can_rx_offload_disable(&priv->offload);
> + priv->can.state = CAN_STATE_STOPPED;
> + close_candev(ndev);
> +
> + return 0;
> +}
> +
> +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> + int ret;
> +
> + switch (mode) {
> + case CAN_MODE_START:
> + ret = nct6694_can_start(ndev);
if (ret)
return ret;
netif_wake_queue(ndev);
> + if (!ret && netif_queue_stopped(ndev))
> + netif_wake_queue(ndev);
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return ret;
> +}
> +
> +static int nct6694_can_open(struct net_device *ndev)
> +{
> + struct nct6694_can_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_can_irq, IRQF_ONESHOT,
> + "nct6694_can", ndev);
> + if (ret) {
> + netdev_err(ndev, "Failed to request IRQ\n");
> + goto close_candev;
> + }
> +
> + 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_can_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);
> +close_candev:
> + can_rx_offload_disable(&priv->offload);
> + close_candev(ndev);
> + return ret;
> +}
> +
> +static const struct net_device_ops nct6694_can_netdev_ops = {
> + .ndo_open = nct6694_can_open,
> + .ndo_stop = nct6694_can_stop,
> + .ndo_start_xmit = nct6694_can_start_xmit,
> + .ndo_change_mtu = can_change_mtu,
> +};
> +
> +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> + .get_ts_info = ethtool_op_get_ts_info,
> +};
> +
> +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> +{
> + struct nct6694_can_information *info __free(kfree) = NULL;
> + static const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_INFORMATION,
> + .sel = NCT6694_CAN_INFORMATION_SEL,
> + .len = cpu_to_le16(sizeof(*info))
> + };
> + int ret, can_clk;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> + if (ret)
> + return ret;
> +
> + can_clk = le32_to_cpu(info->can_clk);
> +
> + return can_clk;
> +}
> +
> +static int nct6694_can_probe(struct platform_device *pdev)
> +{
> + const struct mfd_cell *cell = mfd_get_cell(pdev);
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + struct nct6694_can_priv *priv;
> + struct net_device *ndev;
> + int ret, irq, can_clk;
> +
> + irq = irq_create_mapping(nct6694->domain,
> + NCT6694_IRQ_CAN0 + cell->id);
> + if (!irq)
> + return irq;
> +
> + ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> + if (!ndev)
> + return -ENOMEM;
> +
> + ndev->irq = irq;
> + ndev->flags |= IFF_ECHO;
> + ndev->dev_port = cell->id;
> + ndev->netdev_ops = &nct6694_can_netdev_ops;
> + ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> +
> + priv = netdev_priv(ndev);
> + priv->nct6694 = nct6694;
> + priv->ndev = ndev;
> +
> + can_clk = nct6694_can_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_can_tx_work);
> +
> + priv->can.state = CAN_STATE_STOPPED;
please remove CAN_STATE_STOPPED, it's not needed
> + priv->can.clock.freq = can_clk;
> + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> + priv->can.do_set_mode = nct6694_can_set_mode;
> + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
Does your device run in CAN-FD mode all the time? If so, please use
can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> +
> + 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);
> + return ret;
> +}
> +
> +static void nct6694_can_remove(struct platform_device *pdev)
> +{
> + struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> +
> + unregister_candev(priv->ndev);
> + can_rx_offload_del(&priv->offload);
> + free_candev(priv->ndev);
> +}
> +
> +static struct platform_driver nct6694_can_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_can_probe,
> + .remove = nct6694_can_remove,
> +};
> +
> +module_platform_driver(nct6694_can_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-can");
There should be no need for the module alias.
Marc
> --
> 2.34.1
>
>
>
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-28 10:44 ` Marc Kleine-Budde
@ 2025-03-17 2:24 ` Ming Yu
2025-03-17 9:13 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-17 2:24 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年2月28日 週五 下午6:44寫道:
>
> > +static void nct6694_can_handle_lost_msg(struct net_device *ndev)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct net_device_stats *stats = &ndev->stats;
> > + struct can_frame *cf;
> > + struct sk_buff *skb;
> > +
> > + netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
>
> Please make it a netdev_dbg()
>
Fix it in v9.
> > +
> > + 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_can_rx_offload(&priv->offload, skb);
> > +}
> > +
> > +static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct nct6694_can_frame *frame = &priv->rx;
> > + const struct nct6694_cmd_header cmd_hd = {
> > + .mod = NCT6694_CAN_MOD,
> > + .cmd = NCT6694_CAN_RECEIVE,
> > + .sel = NCT6694_CAN_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_CAN_FRAME_FLAG_FD) {
> > + struct canfd_frame *cfd;
> > +
> > + skb = alloc_canfd_skb(priv->ndev, &cfd);
> > + if (!skb)
>
> stats->rx_dropped++;
>
Add it in v9.
> > + return;
> > +
> > + cfd->can_id = le32_to_cpu(frame->id);
> > + cfd->len = canfd_sanitize_len(frame->length);
> > + if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> > + cfd->can_id |= CAN_EFF_FLAG;
> > + if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
> > + cfd->flags |= CANFD_BRS;
> > + if (frame->flag & NCT6694_CAN_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++;
>
Add it in v9.
> > + return;
> > +
> > + cf->can_id = le32_to_cpu(frame->id);
> > + cf->len = can_cc_dlc2len(frame->length);
> > + if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> > + cf->can_id |= CAN_EFF_FLAG;
> > +
> > + if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
> > + cf->can_id |= CAN_RTR_FLAG;
> > + else
> > + memcpy(cf->data, frame->data, cf->len);
> > + }
> > +
> > + nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
> > +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> > + struct can_berr_counter *bec)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > + *bec = priv->bec;
> > +
> > + return 0;
> > +}
> > +
> > +static void nct6694_can_handle_state_change(struct net_device *ndev,
> > + enum can_state new_state)
> > +{
>
> I just replied to one of your questions of the last series:
>
> | https://lore.kernel.org/20250228-magic-seahorse-of-abracadabra-f2a402-mkl@pengutronix.de
>
Understood, I will make the modifications in the next patch.
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct can_berr_counter bec;
> > + struct can_frame *cf;
> > + struct sk_buff *skb;
> > +
> > + skb = alloc_can_err_skb(ndev, &cf);
> > +
> > + nct6694_can_get_berr_counter(ndev, &bec);
> > +
> > + switch (new_state) {
> > + case CAN_STATE_ERROR_ACTIVE:
> > + priv->can.can_stats.error_warning++;
> > + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > + if (cf)
> > + cf->data[1] |= CAN_ERR_CRTL_ACTIVE;
> > + break;
> > + case CAN_STATE_ERROR_WARNING:
> > + priv->can.can_stats.error_warning++;
> > + priv->can.state = CAN_STATE_ERROR_WARNING;
> > + if (cf) {
> > + cf->can_id |= CAN_ERR_CRTL;
> > + if (bec.txerr > bec.rxerr)
> > + cf->data[1] = CAN_ERR_CRTL_TX_WARNING;
> > + else
> > + cf->data[1] = CAN_ERR_CRTL_RX_WARNING;
> > + cf->data[6] = bec.txerr;
> > + cf->data[7] = bec.rxerr;
> > + }
> > + break;
> > + case CAN_STATE_ERROR_PASSIVE:
> > + priv->can.can_stats.error_passive++;
> > + priv->can.state = CAN_STATE_ERROR_PASSIVE;
> > + if (cf) {
> > + cf->can_id |= CAN_ERR_CRTL;
> > + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> > + if (bec.txerr >= CAN_ERROR_PASSIVE_THRESHOLD)
> > + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> > + cf->data[6] = bec.txerr;
> > + cf->data[7] = bec.rxerr;
> > + }
> > + break;
> > + case CAN_STATE_BUS_OFF:
> > + priv->can.state = CAN_STATE_BUS_OFF;
> > + priv->can.can_stats.bus_off++;
> > + if (cf)
> > + cf->can_id |= CAN_ERR_BUSOFF;
>
> no need for can_free_echo_skb() and netif_stop_queue()
>
Fix these in v9.
> > + can_free_echo_skb(ndev, 0, NULL);
> > + netif_stop_queue(ndev);
>
> > + can_bus_off(ndev);
> > + break;
> > + default:
> > + break;
> > + }
> > +
>
> if (skb)
>
Add it in v9.
> > + nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
> > +static void nct6694_can_handle_state_errors(struct net_device *ndev, u8 status)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > + if (status == NCT6694_CAN_EVT_STS_ERROR_ACTIVE &&
> > + priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> > + netdev_dbg(ndev, "Error, entered active state\n");
> > + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_ACTIVE);
> > + }
> > +
> > + if (status == NCT6694_CAN_EVT_STS_WARNING &&
> > + priv->can.state != CAN_STATE_ERROR_WARNING) {
> > + netdev_dbg(ndev, "Error, entered warning state\n");
> > + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_WARNING);
> > + }
> > +
> > + if (status == NCT6694_CAN_EVT_STS_ERROR_PASSIVE &&
> > + priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> > + netdev_dbg(ndev, "Error, entered passive state\n");
> > + nct6694_can_handle_state_change(ndev, CAN_STATE_ERROR_PASSIVE);
> > + }
> > +
> > + if (status == NCT6694_CAN_EVT_STS_BUS_OFF &&
> > + priv->can.state != CAN_STATE_BUS_OFF) {
> > + netdev_dbg(ndev, "Error, entered bus-off state\n");
> > + nct6694_can_handle_state_change(ndev, CAN_STATE_BUS_OFF);
> > + }
> > +}
> > +
> > +static void nct6694_can_handle_bus_err(struct net_device *ndev, u8 bus_err)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct can_frame *cf;
> > + struct sk_buff *skb;
> > +
> > + if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
> > + return;
> > +
> > + priv->can.can_stats.bus_error++;
> > +
> > + skb = alloc_can_err_skb(ndev, &cf);
>
> make it: "if (cf)" instead of "if (skb)" then interacting with the "cf".
>
Fix it in v9.
> > + if (skb)
> > + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> > +
> > + switch (bus_err) {
> > + case NCT6694_CAN_EVT_ERR_CRC_ERROR:
> > + netdev_dbg(ndev, "CRC error\n");
> > + ndev->stats.rx_errors++;
> > + if (skb)
> > + cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
> > + break;
> > +
> > + case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
> > + netdev_dbg(ndev, "Stuff error\n");
> > + ndev->stats.rx_errors++;
> > + if (skb)
> > + cf->data[2] |= CAN_ERR_PROT_STUFF;
> > + break;
> > +
> > + case NCT6694_CAN_EVT_ERR_ACK_ERROR:
> > + netdev_dbg(ndev, "Ack error\n");
> > + ndev->stats.tx_errors++;
> > + if (skb) {
> > + cf->can_id |= CAN_ERR_ACK;
> > + cf->data[2] |= CAN_ERR_PROT_TX;
> > + }
> > + break;
> > +
> > + case NCT6694_CAN_EVT_ERR_FORM_ERROR:
> > + netdev_dbg(ndev, "Form error\n");
> > + ndev->stats.rx_errors++;
> > + if (skb)
> > + cf->data[2] |= CAN_ERR_PROT_FORM;
> > + break;
> > +
> > + case NCT6694_CAN_EVT_ERR_BIT_ERROR:
> > + netdev_dbg(ndev, "Bit error\n");
> > + ndev->stats.tx_errors++;
> > + if (skb)
> > + cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
> > + break;
> > +
> > + default:
> > + break;
> > + }
> > +
> > + if (skb)
> > + nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
> > +static void nct6694_can_tx_irq(struct net_device *ndev)
> > +{
> > + struct nct6694_can_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_can_irq(int irq, void *data)
> > +{
> > + struct net_device *ndev = data;
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + struct nct6694_can_event *event = &priv->event[ndev->dev_port];
> > + const struct nct6694_cmd_header cmd_hd = {
> > + .mod = NCT6694_CAN_MOD,
> > + .cmd = NCT6694_CAN_EVENT,
> > + .sel = NCT6694_CAN_EVENT_SEL(ndev->dev_port, NCT6694_CAN_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_CAN_EVT_RX_DATA_IN) {
> > + nct6694_can_rx(ndev, event->rx_evt);
>
> just for symmetry name rename nct6694_can_rx() to nct6694_can_handle_rx()
>
Fix it in v9.
> > + handled = IRQ_HANDLED;
> > + }
> > +
> > + if (event->rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
> > + nct6694_can_handle_lost_msg(ndev);
> > + handled = IRQ_HANDLED;
> > + }
> > +
> > + if (event->status) {
> > + nct6694_can_handle_state_errors(ndev, event->status);
>
> I think you should call nct6694_can_handle_state_change() directly and
> get rid of the code on nct6694_can_handle_state_errors().
>
Fix it in v9.
> > + handled = IRQ_HANDLED;
> > + }
> > +
> > + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
> > + nct6694_can_handle_bus_err(ndev, event->err);
> > + handled = IRQ_HANDLED;
> > + }
>
> The logic needs to be refined. If there was an IRQ you must say you've
> handled it, event if user space doesn't care for bus errors.
>
> The better solution is to disabled event->err related IRQs in the
> hardware, if CAN_CTRLMODE_BERR_REPORTING is disabled, so you don't get
> an IRQ in the first place.
>
> if (event->err != NCT6694_CAN_EVT_ERR_NO_ERROR) {
> if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> nct6694_can_handle_bus_err(ndev, event->err);
> handled = IRQ_HANDLED;
> }
>
Fix it in v9.
> > +
> > + if (event->tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY) {
> > + nct6694_can_tx_irq(ndev);
>
> just for symmetry name rename nct6694_can_tx() to nct6694_can_handle_tx()
>
Fix it in v9.
> > + 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_can_tx_work(struct work_struct *work)
> > +{
> > + struct nct6694_can_priv *priv = container_of(work,
> > + struct nct6694_can_priv,
> > + tx_work);
> > + struct nct6694_can_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_CAN_MOD,
> > + .cmd = NCT6694_CAN_DELIVER,
> > + .sel = NCT6694_CAN_DELIVER_SEL(1),
> > + .len = cpu_to_le16(sizeof(*frame))
> > + };
> > + u32 txid;
> > + int err;
> > +
> > + memset(frame, 0, sizeof(*frame));
> > +
> > + frame->tag = NCT6694_CAN_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_CAN_FRAME_FLAG_BRS;
> > +
> > + if (cfd->can_id & CAN_EFF_FLAG) {
> > + txid = cfd->can_id & CAN_EFF_MASK;
> > + frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> > + } else {
> > + txid = cfd->can_id & CAN_SFF_MASK;
> > + }
> > + frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
> > + frame->id = cpu_to_le32(txid);
> > + frame->length = cfd->len;
>
> frame->length = canfd_sanitize_len(cfd->len);
>
Fix it in v9.
> > +
> > + memcpy(frame->data, cfd->data, cfd->len);
>
> memcpy(frame->data, cfd->data, frame->length);
>
Fix it in v9.
> > + } 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_CAN_FRAME_FLAG_EFF;
> > + } else {
> > + txid = cf->can_id & CAN_SFF_MASK;
> > + }
> > +
> > + if (cf->can_id & CAN_RTR_FLAG)
> > + frame->flag |= NCT6694_CAN_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) {
> > + netdev_err(ndev, "%s: TX FIFO is full!\n", __func__);
>
> The FIFO should not be full at this time. There also could be an USB
> error due to unlug or EMI. I think you should remove this error message.
>
Fix it in v9.
> > + can_free_echo_skb(ndev, 0, NULL);
> > + stats->tx_dropped++;
> > + stats->tx_errors++;
> > + netif_wake_queue(ndev);
> > + }
> > +}
> > +
> > +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> > + struct net_device *ndev)
> > +{
> > + struct nct6694_can_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_can_start(struct net_device *ndev)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > + struct nct6694_can_setting *setting __free(kfree) = NULL;
> > + const struct nct6694_cmd_header cmd_hd = {
> > + .mod = NCT6694_CAN_MOD,
> > + .cmd = NCT6694_CAN_SETTING,
> > + .sel = ndev->dev_port,
> > + .len = cpu_to_le16(sizeof(*setting))
> > + };
> > + int ret;
> > +
> > + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > + if (!setting)
> > + return -ENOMEM;
> > +
> > + setting->nbr = cpu_to_le32(n_bt->bitrate);
> > + setting->dbr = cpu_to_le32(d_bt->bitrate);
> > +
> > + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> > + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
> > +
> > + if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> > + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
> > +
> > + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> > + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
> > +
> > + ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> > + if (ret)
> > + return ret;
> > +
> > + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_can_stop(struct net_device *ndev)
>
> static void
>
Fix it in v9.
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > + priv->can.ctrlmode = CAN_CTRLMODE_LISTENONLY;
>
> As Vincent already pointed out, bring you device into listen only mode.
> Please add a comment why you do it, because it cannot be stopped.
>
Okay! I will add the comment in the next patch.
> > + netif_stop_queue(ndev);
> > + free_irq(ndev->irq, ndev);
> > + destroy_workqueue(priv->wq);
> > + can_rx_offload_disable(&priv->offload);
> > + priv->can.state = CAN_STATE_STOPPED;
> > + close_candev(ndev);
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> > +{
> > + int ret;
> > +
> > + switch (mode) {
> > + case CAN_MODE_START:
> > + ret = nct6694_can_start(ndev);
>
> if (ret)
> return ret;
>
> netif_wake_queue(ndev);
>
Fix it in v9.
> > + if (!ret && netif_queue_stopped(ndev))
> > + netif_wake_queue(ndev);
> > + break;
> > +
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_can_open(struct net_device *ndev)
> > +{
> > + struct nct6694_can_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_can_irq, IRQF_ONESHOT,
> > + "nct6694_can", ndev);
> > + if (ret) {
> > + netdev_err(ndev, "Failed to request IRQ\n");
> > + goto close_candev;
> > + }
> > +
> > + 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_can_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);
> > +close_candev:
> > + can_rx_offload_disable(&priv->offload);
> > + close_candev(ndev);
> > + return ret;
> > +}
> > +
> > +static const struct net_device_ops nct6694_can_netdev_ops = {
> > + .ndo_open = nct6694_can_open,
> > + .ndo_stop = nct6694_can_stop,
> > + .ndo_start_xmit = nct6694_can_start_xmit,
> > + .ndo_change_mtu = can_change_mtu,
> > +};
> > +
> > +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> > + .get_ts_info = ethtool_op_get_ts_info,
> > +};
> > +
> > +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> > +{
> > + struct nct6694_can_information *info __free(kfree) = NULL;
> > + static const struct nct6694_cmd_header cmd_hd = {
> > + .mod = NCT6694_CAN_MOD,
> > + .cmd = NCT6694_CAN_INFORMATION,
> > + .sel = NCT6694_CAN_INFORMATION_SEL,
> > + .len = cpu_to_le16(sizeof(*info))
> > + };
> > + int ret, can_clk;
> > +
> > + info = kzalloc(sizeof(*info), GFP_KERNEL);
> > + if (!info)
> > + return -ENOMEM;
> > +
> > + ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> > + if (ret)
> > + return ret;
> > +
> > + can_clk = le32_to_cpu(info->can_clk);
> > +
> > + return can_clk;
> > +}
> > +
> > +static int nct6694_can_probe(struct platform_device *pdev)
> > +{
> > + const struct mfd_cell *cell = mfd_get_cell(pdev);
> > + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > + struct nct6694_can_priv *priv;
> > + struct net_device *ndev;
> > + int ret, irq, can_clk;
> > +
> > + irq = irq_create_mapping(nct6694->domain,
> > + NCT6694_IRQ_CAN0 + cell->id);
> > + if (!irq)
> > + return irq;
> > +
> > + ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> > + if (!ndev)
> > + return -ENOMEM;
> > +
> > + ndev->irq = irq;
> > + ndev->flags |= IFF_ECHO;
> > + ndev->dev_port = cell->id;
> > + ndev->netdev_ops = &nct6694_can_netdev_ops;
> > + ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> > +
> > + priv = netdev_priv(ndev);
> > + priv->nct6694 = nct6694;
> > + priv->ndev = ndev;
> > +
> > + can_clk = nct6694_can_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_can_tx_work);
> > +
> > + priv->can.state = CAN_STATE_STOPPED;
>
> please remove CAN_STATE_STOPPED, it's not needed
>
Fix it in v9.
> > + priv->can.clock.freq = can_clk;
> > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > + priv->can.do_set_mode = nct6694_can_set_mode;
> > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
>
> Does your device run in CAN-FD mode all the time? If so, please use
> can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> and remove CAN_CTRLMODE_FD from ctrlmode_supported.
>
Our device is designed to allow users to dynamically switch between
Classical CAN and CAN-FD mode via ip link set ... fd on/off.
Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
can_set_static_ctrlmode() is not suitable in this case.
Please let me know if you have any concerns about this approach.
> > +
> > + 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);
> > + return ret;
> > +}
> > +
> > +static void nct6694_can_remove(struct platform_device *pdev)
> > +{
> > + struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> > +
> > + unregister_candev(priv->ndev);
> > + can_rx_offload_del(&priv->offload);
> > + free_candev(priv->ndev);
> > +}
> > +
> > +static struct platform_driver nct6694_can_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_can_probe,
> > + .remove = nct6694_can_remove,
> > +};
> > +
> > +module_platform_driver(nct6694_can_driver);
> > +
> > +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:nct6694-can");
>
> There should be no need for the module alias.
>
Remove it in v9.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-17 2:24 ` Ming Yu
@ 2025-03-17 9:13 ` Marc Kleine-Budde
2025-03-26 2:27 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-17 9:13 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 1512 bytes --]
On 17.03.2025 10:24:11, Ming Yu wrote:
[...]
> > > + priv->can.clock.freq = can_clk;
> > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> >
> > Does your device run in CAN-FD mode all the time? If so, please use
> > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> >
>
> Our device is designed to allow users to dynamically switch between
> Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> can_set_static_ctrlmode() is not suitable in this case.
> Please let me know if you have any concerns about this approach.
Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-17 9:13 ` Marc Kleine-Budde
@ 2025-03-26 2:27 ` Ming Yu
2025-03-26 17:41 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-26 2:27 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月17日 週一 下午5:21寫道:
>
> > > > + priv->can.clock.freq = can_clk;
> > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > >
> > > Does your device run in CAN-FD mode all the time? If so, please use
> > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > >
> >
> > Our device is designed to allow users to dynamically switch between
> > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > can_set_static_ctrlmode() is not suitable in this case.
> > Please let me know if you have any concerns about this approach.
>
> Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
>
Sorry, I was previously confused about our device's control mode. I
will use can_set_static_ctrlmode() to set CAN_FD mode in the next
patch.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-26 2:27 ` Ming Yu
@ 2025-03-26 17:41 ` Marc Kleine-Budde
2025-03-27 5:38 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-26 17:41 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 1958 bytes --]
On 26.03.2025 10:27:03, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月17日 週一 下午5:21寫道:
> >
> > > > > + priv->can.clock.freq = can_clk;
> > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > >
> > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > >
> > >
> > > Our device is designed to allow users to dynamically switch between
> > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > can_set_static_ctrlmode() is not suitable in this case.
> > > Please let me know if you have any concerns about this approach.
> >
> > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> >
>
> Sorry, I was previously confused about our device's control mode. I
> will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> patch.
Does your device support CAN-CC only mode? Does your device support to
switch between CAN-CC only and CAN-FD mode?
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-26 17:41 ` Marc Kleine-Budde
@ 2025-03-27 5:38 ` Ming Yu
2025-03-27 7:06 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-27 5:38 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 上午1:41寫道:
>
> > > > > > + priv->can.clock.freq = can_clk;
> > > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > > >
> > > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > > >
> > > >
> > > > Our device is designed to allow users to dynamically switch between
> > > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > > can_set_static_ctrlmode() is not suitable in this case.
> > > > Please let me know if you have any concerns about this approach.
> > >
> > > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> > >
> >
> > Sorry, I was previously confused about our device's control mode. I
> > will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> > patch.
>
> Does your device support CAN-CC only mode? Does your device support to
> switch between CAN-CC only and CAN-FD mode?
>
Our device supports both CAN-CC and CAN-FD mode.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-27 5:38 ` Ming Yu
@ 2025-03-27 7:06 ` Marc Kleine-Budde
2025-03-28 2:37 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-27 7:06 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 2207 bytes --]
On 27.03.2025 13:38:22, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 上午1:41寫道:
> >
> > > > > > > + priv->can.clock.freq = can_clk;
> > > > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > > > >
> > > > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > > > >
> > > > >
> > > > > Our device is designed to allow users to dynamically switch between
> > > > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > > > can_set_static_ctrlmode() is not suitable in this case.
> > > > > Please let me know if you have any concerns about this approach.
> > > >
> > > > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> > > >
> > >
> > > Sorry, I was previously confused about our device's control mode. I
> > > will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> > > patch.
> >
> > Does your device support CAN-CC only mode? Does your device support to
> > switch between CAN-CC only and CAN-FD mode?
> >
>
> Our device supports both CAN-CC and CAN-FD mode.
This doesn't answer my question:
Does your device support CAN-CC only mode?
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-27 7:06 ` Marc Kleine-Budde
@ 2025-03-28 2:37 ` Ming Yu
2025-03-28 7:22 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-28 2:37 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 下午3:25寫道:
>
> On 27.03.2025 13:38:22, Ming Yu wrote:
> > Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 上午1:41寫道:
> > >
> > > > > > > > + priv->can.clock.freq = can_clk;
> > > > > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > > > > >
> > > > > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > > > > >
> > > > > >
> > > > > > Our device is designed to allow users to dynamically switch between
> > > > > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > > > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > > > > can_set_static_ctrlmode() is not suitable in this case.
> > > > > > Please let me know if you have any concerns about this approach.
> > > > >
> > > > > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> > > > >
> > > >
> > > > Sorry, I was previously confused about our device's control mode. I
> > > > will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> > > > patch.
> > >
> > > Does your device support CAN-CC only mode? Does your device support to
> > > switch between CAN-CC only and CAN-FD mode?
> > >
> >
> > Our device supports both CAN-CC and CAN-FD mode.
>
> This doesn't answer my question:
>
> Does your device support CAN-CC only mode?
>
It can dynamically switch between CAN-CC and CAN-FD mode when
trasmitting or receiving, depending on whether the nct6694_can_frame
passs the flag with NCT6694_CAN_FRAME_FLAG_FD.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-28 2:37 ` Ming Yu
@ 2025-03-28 7:22 ` Marc Kleine-Budde
2025-03-28 8:57 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-28 7:22 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 2834 bytes --]
On 28.03.2025 10:37:33, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 下午3:25寫道:
> >
> > On 27.03.2025 13:38:22, Ming Yu wrote:
> > > Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 上午1:41寫道:
> > > >
> > > > > > > > > + priv->can.clock.freq = can_clk;
> > > > > > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > > > > > >
> > > > > > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > > > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > > > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > > > > > >
> > > > > > >
> > > > > > > Our device is designed to allow users to dynamically switch between
> > > > > > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > > > > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > > > > > can_set_static_ctrlmode() is not suitable in this case.
> > > > > > > Please let me know if you have any concerns about this approach.
> > > > > >
> > > > > > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> > > > > >
> > > > >
> > > > > Sorry, I was previously confused about our device's control mode. I
> > > > > will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> > > > > patch.
> > > >
> > > > Does your device support CAN-CC only mode? Does your device support to
> > > > switch between CAN-CC only and CAN-FD mode?
> > > >
> > >
> > > Our device supports both CAN-CC and CAN-FD mode.
> >
> > This doesn't answer my question:
> >
> > Does your device support CAN-CC only mode?
>
> It can dynamically switch between CAN-CC and CAN-FD mode when
> trasmitting or receiving, depending on whether the nct6694_can_frame
> passs the flag with NCT6694_CAN_FRAME_FLAG_FD.
Ok, but what about the receive path? Does the device support CAN-CC only
mode? Will it throw an error, if it receives a CAN-FD frame?
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-28 7:22 ` Marc Kleine-Budde
@ 2025-03-28 8:57 ` Ming Yu
2025-03-28 9:11 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-28 8:57 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月28日 週五 下午3:22寫道:
>
> > > > > > > > > > + priv->can.clock.freq = can_clk;
> > > > > > > > > > + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> > > > > > > > > > + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> > > > > > > > > > + priv->can.do_set_mode = nct6694_can_set_mode;
> > > > > > > > > > + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> > > > > > > > > > + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> > > > > > > > > > + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> > > > > > > > > > + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> > > > > > > > >
> > > > > > > > > Does your device run in CAN-FD mode all the time? If so, please use
> > > > > > > > > can_set_static_ctrlmode() to set it after priv->can.ctrlmode_supported
> > > > > > > > > and remove CAN_CTRLMODE_FD from ctrlmode_supported.
> > > > > > > > >
> > > > > > > >
> > > > > > > > Our device is designed to allow users to dynamically switch between
> > > > > > > > Classical CAN and CAN-FD mode via ip link set ... fd on/off.
> > > > > > > > Therefore, CAN_CTRLMODE_FD needs to remain in ctrlmode_supported, and
> > > > > > > > can_set_static_ctrlmode() is not suitable in this case.
> > > > > > > > Please let me know if you have any concerns about this approach.
> > > > > > >
> > > > > > > Where do you evaluate if the user has configured CAN_CTRLMODE_FD or not?
> > > > > > >
> > > > > >
> > > > > > Sorry, I was previously confused about our device's control mode. I
> > > > > > will use can_set_static_ctrlmode() to set CAN_FD mode in the next
> > > > > > patch.
> > > > >
> > > > > Does your device support CAN-CC only mode? Does your device support to
> > > > > switch between CAN-CC only and CAN-FD mode?
> > > > >
> > > >
> > > > Our device supports both CAN-CC and CAN-FD mode.
> > >
> > > This doesn't answer my question:
> > >
> > > Does your device support CAN-CC only mode?
> >
> > It can dynamically switch between CAN-CC and CAN-FD mode when
> > trasmitting or receiving, depending on whether the nct6694_can_frame
> > passs the flag with NCT6694_CAN_FRAME_FLAG_FD.
>
> Ok, but what about the receive path? Does the device support CAN-CC only
> mode? Will it throw an error, if it receives a CAN-FD frame?
>
No, it can receive both CAN-CC and CAN-FD frames, if the hardware
receives a CAN-FD frame, the firmware will set the
NCT6694_CAN_FRAME_FLAG_FD flag.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-28 8:57 ` Ming Yu
@ 2025-03-28 9:11 ` Marc Kleine-Budde
0 siblings, 0 replies; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-28 9:11 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 940 bytes --]
On 28.03.2025 16:57:46, Ming Yu wrote:
> > > > Does your device support CAN-CC only mode?
> > >
> > > It can dynamically switch between CAN-CC and CAN-FD mode when
> > > trasmitting or receiving, depending on whether the nct6694_can_frame
> > > passs the flag with NCT6694_CAN_FRAME_FLAG_FD.
> >
> > Ok, but what about the receive path? Does the device support CAN-CC only
> > mode? Will it throw an error, if it receives a CAN-FD frame?
>
> No, it can receive both CAN-CC and CAN-FD frames, if the hardware
> receives a CAN-FD frame, the firmware will set the
> NCT6694_CAN_FRAME_FLAG_FD flag.
Ok, then set the CAN-FD ctrl mode static.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
2025-02-27 2:08 ` Vincent Mailhol
2025-02-28 10:44 ` Marc Kleine-Budde
@ 2025-03-17 10:41 ` Marc Kleine-Budde
2025-03-26 2:37 ` Ming Yu
2025-03-26 21:56 ` Christophe JAILLET
3 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-17 10:41 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 2920 bytes --]
On 25.02.2025 16:16:41, Ming Yu wrote:
[...]
> diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> new file mode 100644
> index 000000000000..d97fce5cdf32
> --- /dev/null
> +++ b/drivers/net/can/usb/nct6694_canfd.c
[...]
> +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> + .name = DRVNAME,
> + .tseg1_min = 2,
> + .tseg1_max = 256,
> + .tseg2_min = 2,
> + .tseg2_max = 128,
> + .sjw_max = 128,
> + .brp_min = 1,
> + .brp_max = 511,
> + .brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> + .name = DRVNAME,
> + .tseg1_min = 1,
> + .tseg1_max = 32,
> + .tseg2_min = 1,
> + .tseg2_max = 16,
> + .sjw_max = 16,
> + .brp_min = 1,
> + .brp_max = 31,
> + .brp_inc = 1,
> +};
[...]
> +static int nct6694_can_start(struct net_device *ndev)
> +{
> + struct nct6694_can_priv *priv = netdev_priv(ndev);
> + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> + const struct can_bittiming *n_bt = &priv->can.bittiming;
> + struct nct6694_can_setting *setting __free(kfree) = NULL;
> + const struct nct6694_cmd_header cmd_hd = {
> + .mod = NCT6694_CAN_MOD,
> + .cmd = NCT6694_CAN_SETTING,
> + .sel = ndev->dev_port,
> + .len = cpu_to_le16(sizeof(*setting))
> + };
> + int ret;
> +
> + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> + if (!setting)
> + return -ENOMEM;
> +
> + setting->nbr = cpu_to_le32(n_bt->bitrate);
> + setting->dbr = cpu_to_le32(d_bt->bitrate);
I just noticed one thing that needs clarification/documentation.
You have nct6694_can_bittiming_nominal_const and
nct6694_can_bittiming_data_const, but only pass the bit rates to your
device.
Do the bit timing const really reflect the HW limitations of your
device?
Are you sure your device uses the same algorithm as the kernel and
calculates the same bit timing parameters as the kernel, so that the
values given to the user space reflects the bit timing parameter chosen
by your device?
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> + setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
> +
> + ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> + if (ret)
> + return ret;
> +
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> + return 0;
> +}
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-17 10:41 ` Marc Kleine-Budde
@ 2025-03-26 2:37 ` Ming Yu
2025-03-26 17:35 ` Marc Kleine-Budde
0 siblings, 1 reply; 41+ messages in thread
From: Ming Yu @ 2025-03-26 2:37 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月17日 週一 下午8:01寫道:
>
...
> > +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> > + .name = DRVNAME,
> > + .tseg1_min = 2,
> > + .tseg1_max = 256,
> > + .tseg2_min = 2,
> > + .tseg2_max = 128,
> > + .sjw_max = 128,
> > + .brp_min = 1,
> > + .brp_max = 511,
> > + .brp_inc = 1,
> > +};
> > +
> > +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> > + .name = DRVNAME,
> > + .tseg1_min = 1,
> > + .tseg1_max = 32,
> > + .tseg2_min = 1,
> > + .tseg2_max = 16,
> > + .sjw_max = 16,
> > + .brp_min = 1,
> > + .brp_max = 31,
> > + .brp_inc = 1,
> > +};
>
> [...]
>
> > +static int nct6694_can_start(struct net_device *ndev)
> > +{
> > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > + struct nct6694_can_setting *setting __free(kfree) = NULL;
> > + const struct nct6694_cmd_header cmd_hd = {
> > + .mod = NCT6694_CAN_MOD,
> > + .cmd = NCT6694_CAN_SETTING,
> > + .sel = ndev->dev_port,
> > + .len = cpu_to_le16(sizeof(*setting))
> > + };
> > + int ret;
> > +
> > + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > + if (!setting)
> > + return -ENOMEM;
> > +
> > + setting->nbr = cpu_to_le32(n_bt->bitrate);
> > + setting->dbr = cpu_to_le32(d_bt->bitrate);
>
> I just noticed one thing that needs clarification/documentation.
>
> You have nct6694_can_bittiming_nominal_const and
> nct6694_can_bittiming_data_const, but only pass the bit rates to your
> device.
>
> Do the bit timing const really reflect the HW limitations of your
> device?
>
> Are you sure your device uses the same algorithm as the kernel and
> calculates the same bit timing parameters as the kernel, so that the
> values given to the user space reflects the bit timing parameter chosen
> by your device?
>
Originally, I only intended to provide NBR and DBR for user
configuration. In the next patch, I will add code to configure
NBTP(Nominal Bit Timing Prescaler) and DBTP(Data Bit Timing Prescaler)
based on the setting of nct6694_can_bittiming_nominal_const and
nct6694_can_bittiming_data_const.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-26 2:37 ` Ming Yu
@ 2025-03-26 17:35 ` Marc Kleine-Budde
2025-03-27 5:30 ` Ming Yu
0 siblings, 1 reply; 41+ messages in thread
From: Marc Kleine-Budde @ 2025-03-26 17:35 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
[-- Attachment #1: Type: text/plain, Size: 2692 bytes --]
On 26.03.2025 10:37:11, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月17日 週一 下午8:01寫道:
> > > +static int nct6694_can_start(struct net_device *ndev)
> > > +{
> > > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > > + struct nct6694_can_setting *setting __free(kfree) = NULL;
> > > + const struct nct6694_cmd_header cmd_hd = {
> > > + .mod = NCT6694_CAN_MOD,
> > > + .cmd = NCT6694_CAN_SETTING,
> > > + .sel = ndev->dev_port,
> > > + .len = cpu_to_le16(sizeof(*setting))
> > > + };
> > > + int ret;
> > > +
> > > + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > > + if (!setting)
> > > + return -ENOMEM;
> > > +
> > > + setting->nbr = cpu_to_le32(n_bt->bitrate);
> > > + setting->dbr = cpu_to_le32(d_bt->bitrate);
> >
> > I just noticed one thing that needs clarification/documentation.
> >
> > You have nct6694_can_bittiming_nominal_const and
> > nct6694_can_bittiming_data_const, but only pass the bit rates to your
> > device.
> >
> > Do the bit timing const really reflect the HW limitations of your
> > device?
> >
> > Are you sure your device uses the same algorithm as the kernel and
> > calculates the same bit timing parameters as the kernel, so that the
> > values given to the user space reflects the bit timing parameter chosen
> > by your device?
> >
>
> Originally, I only intended to provide NBR and DBR for user
> configuration. In the next patch, I will add code to configure
> NBTP(Nominal Bit Timing Prescaler) and DBTP(Data Bit Timing Prescaler)
> based on the setting of nct6694_can_bittiming_nominal_const and
> nct6694_can_bittiming_data_const.
Sounds good, but this doesn't answer my questions:
You have nct6694_can_bittiming_nominal_const and
nct6694_can_bittiming_data_const, but only pass the bit rates and the
prescaler to your device.
Do the bit timing const really reflect the HW limitations of your
device?
Are you sure your device uses the same algorithm as the kernel and
calculates the same bit timing parameters as the kernel, so that the
values given to the user space reflects the bit timing parameter chosen
by your device?
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-26 17:35 ` Marc Kleine-Budde
@ 2025-03-27 5:30 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-03-27 5:30 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Marc Kleine-Budde <mkl@pengutronix.de> 於 2025年3月27日 週四 上午1:35寫道:
>
> > > > +static int nct6694_can_start(struct net_device *ndev)
> > > > +{
> > > > + struct nct6694_can_priv *priv = netdev_priv(ndev);
> > > > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > > > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > > > + struct nct6694_can_setting *setting __free(kfree) = NULL;
> > > > + const struct nct6694_cmd_header cmd_hd = {
> > > > + .mod = NCT6694_CAN_MOD,
> > > > + .cmd = NCT6694_CAN_SETTING,
> > > > + .sel = ndev->dev_port,
> > > > + .len = cpu_to_le16(sizeof(*setting))
> > > > + };
> > > > + int ret;
> > > > +
> > > > + setting = kzalloc(sizeof(*setting), GFP_KERNEL);
> > > > + if (!setting)
> > > > + return -ENOMEM;
> > > > +
> > > > + setting->nbr = cpu_to_le32(n_bt->bitrate);
> > > > + setting->dbr = cpu_to_le32(d_bt->bitrate);
> > >
> > > I just noticed one thing that needs clarification/documentation.
> > >
> > > You have nct6694_can_bittiming_nominal_const and
> > > nct6694_can_bittiming_data_const, but only pass the bit rates to your
> > > device.
> > >
> > > Do the bit timing const really reflect the HW limitations of your
> > > device?
> > >
> > > Are you sure your device uses the same algorithm as the kernel and
> > > calculates the same bit timing parameters as the kernel, so that the
> > > values given to the user space reflects the bit timing parameter chosen
> > > by your device?
> > >
> >
> > Originally, I only intended to provide NBR and DBR for user
> > configuration. In the next patch, I will add code to configure
> > NBTP(Nominal Bit Timing Prescaler) and DBTP(Data Bit Timing Prescaler)
> > based on the setting of nct6694_can_bittiming_nominal_const and
> > nct6694_can_bittiming_data_const.
>
> Sounds good, but this doesn't answer my questions:
>
> You have nct6694_can_bittiming_nominal_const and
> nct6694_can_bittiming_data_const, but only pass the bit rates and the
> prescaler to your device.
>
I understand.
The prescaler field is used to calculate sjw, brp, prop_seg,
phase_seg1 and phase_seg2. I will update the code in the next patch.
Thanks,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
` (2 preceding siblings ...)
2025-03-17 10:41 ` Marc Kleine-Budde
@ 2025-03-26 21:56 ` Christophe JAILLET
2025-03-27 5:41 ` Ming Yu
3 siblings, 1 reply; 41+ messages in thread
From: Christophe JAILLET @ 2025-03-26 21:56 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, linux, jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb
Le 25/02/2025 à 09:16, Ming Yu a écrit :
> This driver supports Socket CANFD functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
...
> +static int nct6694_can_probe(struct platform_device *pdev)
> +{
> + const struct mfd_cell *cell = mfd_get_cell(pdev);
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + struct nct6694_can_priv *priv;
> + struct net_device *ndev;
> + int ret, irq, can_clk;
> +
> + irq = irq_create_mapping(nct6694->domain,
> + NCT6694_IRQ_CAN0 + cell->id);
> + if (!irq)
> + return irq;
Should irq_dispose_mapping() be caled in the error handling path and in
the remove function?
> +
> + ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> + if (!ndev)
> + return -ENOMEM;
> +
> + ndev->irq = irq;
> + ndev->flags |= IFF_ECHO;
> + ndev->dev_port = cell->id;
> + ndev->netdev_ops = &nct6694_can_netdev_ops;
> + ndev->ethtool_ops = &nct6694_can_ethtool_ops;
> +
> + priv = netdev_priv(ndev);
> + priv->nct6694 = nct6694;
> + priv->ndev = ndev;
> +
> + can_clk = nct6694_can_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_can_tx_work);
> +
> + priv->can.state = CAN_STATE_STOPPED;
> + priv->can.clock.freq = can_clk;
> + priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> + priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> + priv->can.do_set_mode = nct6694_can_set_mode;
> + priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING |
> + CAN_CTRLMODE_FD | CAN_CTRLMODE_FD_NON_ISO;
> +
> + 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);
> + return ret;
> +}
> +
> +static void nct6694_can_remove(struct platform_device *pdev)
> +{
> + struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> +
> + unregister_candev(priv->ndev);
> + can_rx_offload_del(&priv->offload);
> + free_candev(priv->ndev);
> +}
...
CJ
^ permalink raw reply [flat|nested] 41+ messages in thread* Re: [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support
2025-03-26 21:56 ` Christophe JAILLET
@ 2025-03-27 5:41 ` Ming Yu
0 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-03-27 5:41 UTC (permalink / raw)
To: Christophe JAILLET
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
linux-usb
Dear Christophe,
Thank you for reviewing,
Christophe JAILLET <christophe.jaillet@wanadoo.fr> 於 2025年3月27日 週四 上午5:56寫道:
>
> > +static int nct6694_can_probe(struct platform_device *pdev)
> > +{
> > + const struct mfd_cell *cell = mfd_get_cell(pdev);
> > + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > + struct nct6694_can_priv *priv;
> > + struct net_device *ndev;
> > + int ret, irq, can_clk;
> > +
> > + irq = irq_create_mapping(nct6694->domain,
> > + NCT6694_IRQ_CAN0 + cell->id);
> > + if (!irq)
> > + return irq;
>
> Should irq_dispose_mapping() be caled in the error handling path and in
> the remove function?
>
I think you're right. I'll add this function in the next patch.
Best regards,
Ming
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v8 5/7] watchdog: Add Nuvoton NCT6694 WDT support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (3 preceding siblings ...)
2025-02-25 8:16 ` [PATCH v8 4/7] can: Add Nuvoton NCT6694 CANFD support Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-02-25 8:16 ` [PATCH v8 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2025-02-25 8:16 ` [PATCH v8 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
6 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
This driver supports Watchdog timer functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/watchdog/Kconfig | 11 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/nct6694_wdt.c | 298 +++++++++++++++++++++++++++++++++
4 files changed, 311 insertions(+)
create mode 100644 drivers/watchdog/nct6694_wdt.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8aa611504172..4889b618abef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16926,6 +16926,7 @@ F: drivers/gpio/gpio-nct6694.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/usb/nct6694_canfd.c
+F: drivers/watchdog/nct6694_wdt.c
F: include/linux/mfd/nct6694.h
NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f81705f8539a..4c4f826368c4 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -748,6 +748,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 8411626fa162..de2a04ff8a92 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -232,6 +232,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
+obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o
obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
new file mode 100644
index 000000000000..a99d0429e637
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define DRVNAME "nct6694_wdt"
+
+#define NCT6694_DEFAULT_TIMEOUT 10
+#define NCT6694_DEFAULT_PRETIMEOUT 0
+
+/*
+ * 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_DEFAULT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int pretimeout = NCT6694_DEFAULT_PRETIMEOUT;
+module_param(pretimeout, int, 0);
+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;
+ struct mutex lock;
+ union nct6694_wdt_msg *msg;
+ unsigned int 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;
+
+ guard(mutex)(&data->lock);
+
+ 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))
+ };
+
+ guard(mutex)(&data->lock);
+
+ 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))
+ };
+
+ guard(mutex)(&data->lock);
+ 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;
+
+ guard(mutex)(&data->lock);
+
+ 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 = DRVNAME,
+};
+
+static const struct watchdog_ops nct6694_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = nct6694_wdt_start,
+ .stop = nct6694_wdt_stop,
+ .set_timeout = nct6694_wdt_set_timeout,
+ .set_pretimeout = nct6694_wdt_set_pretimeout,
+ .get_timeleft = nct6694_wdt_get_time,
+ .ping = nct6694_wdt_ping,
+};
+
+static int nct6694_wdt_probe(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct device *dev = &pdev->dev;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_wdt_data *data;
+ struct watchdog_device *wdev;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->nct6694 = nct6694;
+ data->wdev_idx = cell->id;
+
+ wdev = &data->wdev;
+ wdev->info = &nct6694_wdt_info;
+ wdev->ops = &nct6694_wdt_ops;
+ wdev->timeout = timeout;
+ wdev->pretimeout = pretimeout;
+ if (timeout < pretimeout) {
+ dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+ wdev->pretimeout = 0;
+ }
+
+ wdev->min_timeout = 1;
+ wdev->max_timeout = 255;
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ 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 = DRVNAME,
+ },
+ .probe = nct6694_wdt_probe,
+};
+
+module_platform_driver(nct6694_wdt_driver);
+
+MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-wdt");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* [PATCH v8 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (4 preceding siblings ...)
2025-02-25 8:16 ` [PATCH v8 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
2025-02-25 8:16 ` [PATCH v8 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
6 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
This driver supports Hardware monitor functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/nct6694-hwmon.c | 949 ++++++++++++++++++++++++++++++++++
4 files changed, 961 insertions(+)
create mode 100644 drivers/hwmon/nct6694-hwmon.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4889b618abef..a9eda4530b07 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16923,6 +16923,7 @@ M: Ming Yu <tmyu0@nuvoton.com>
L: linux-kernel@vger.kernel.org
S: Supported
F: drivers/gpio/gpio-nct6694.c
+F: drivers/hwmon/nct6694-hwmon.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/usb/nct6694_canfd.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4cbaba15d86e..8a7de798a30a 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1638,6 +1638,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 b7ef0f0562d3..c73898704421 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -168,6 +168,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o
+obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o
obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
nct6775-objs := nct6775-platform.o
obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
new file mode 100644
index 000000000000..4793f1d53f96
--- /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) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 report channel
+ * This defines the module type used for communication with the NCT6694
+ * report channel over the USB interface.
+ */
+#define NCT6694_RPT_MOD 0xFF
+
+/* Report channel */
+/*
+ * The report channel is used to report the status of the hardware monitor
+ * devices, such as voltage, temperature, fan speed, and PWM.
+ */
+#define NCT6694_VIN_IDX(x) (0x00 + (x))
+#define NCT6694_TIN_IDX(x) \
+ ({ typeof(x) (_x) = (x); \
+ ((_x) < 10) ? (0x10 + ((_x) * 2)) : \
+ (0x30 + (((_x) - 10) * 2)); })
+#define NCT6694_FIN_IDX(x) (0x50 + ((x) * 2))
+#define NCT6694_PWM_IDX(x) (0x70 + (x))
+#define NCT6694_VIN_STS(x) (0x68 + (x))
+#define NCT6694_TIN_STS(x) (0x6A + (x))
+#define NCT6694_FIN_STS(x) (0x6E + (x))
+
+/*
+ * USB command module type for NCT6694 HWMON controller.
+ * This defines the module type used for communication with the NCT6694
+ * HWMON controller over the USB interface.
+ */
+#define NCT6694_HWMON_MOD 0x00
+
+/* Command 00h - Hardware Monitor Control */
+#define NCT6694_HWMON_CONTROL 0x00
+#define NCT6694_HWMON_CONTROL_SEL 0x00
+
+/* Command 02h - Alarm Control */
+#define NCT6694_HWMON_ALARM 0x02
+#define NCT6694_HWMON_ALARM_SEL 0x00
+
+/*
+ * USB command module type for NCT6694 PWM controller.
+ * This defines the module type used for communication with the NCT6694
+ * PWM controller over the USB interface.
+ */
+#define NCT6694_PWM_MOD 0x01
+
+/* PWM Command - Manual Control */
+#define NCT6694_PWM_CONTROL 0x01
+#define NCT6694_PWM_CONTROL_SEL 0x00
+
+#define NCT6694_FREQ_FROM_REG(reg) ((reg) * 25000 / 255)
+#define NCT6694_FREQ_TO_REG(val) \
+ (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
+
+#define NCT6694_LSB_REG_MASK GENMASK(7, 5)
+#define NCT6694_TIN_HYST_MASK GENMASK(7, 5)
+
+enum nct6694_hwmon_temp_mode {
+ NCT6694_HWMON_TWOTIME_IRQ = 0,
+ NCT6694_HWMON_ONETIME_IRQ,
+ NCT6694_HWMON_REALTIME_IRQ,
+ NCT6694_HWMON_COMPARE_IRQ,
+};
+
+struct __packed nct6694_hwmon_control {
+ u8 vin_en[2];
+ u8 tin_en[2];
+ u8 fin_en[2];
+ u8 pwm_en[2];
+ u8 reserved1[40];
+ u8 pwm_freq[10];
+ u8 reserved2[6];
+};
+
+struct __packed nct6694_hwmon_alarm {
+ u8 smi_ctrl;
+ u8 reserved1[15];
+ struct {
+ u8 hl;
+ u8 ll;
+ } vin_limit[16];
+ struct {
+ u8 hyst;
+ s8 hl;
+ } tin_cfg[32];
+ __be16 fin_ll[10];
+ u8 reserved2[4];
+};
+
+struct __packed nct6694_pwm_control {
+ u8 mal_en[2];
+ u8 mal_val[10];
+ u8 reserved[12];
+};
+
+union __packed nct6694_hwmon_rpt {
+ u8 vin;
+ struct {
+ u8 msb;
+ u8 lsb;
+ } tin;
+ __be16 fin;
+ u8 pwm;
+ u8 status;
+};
+
+union __packed nct6694_hwmon_msg {
+ struct nct6694_hwmon_alarm hwmon_alarm;
+ struct nct6694_pwm_control pwm_ctrl;
+};
+
+struct nct6694_hwmon_data {
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ struct nct6694_hwmon_control hwmon_en;
+ union nct6694_hwmon_rpt *rpt;
+ union nct6694_hwmon_msg *msg;
+};
+
+static inline long in_from_reg(u8 reg)
+{
+ return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 16);
+}
+
+static inline long temp_from_reg(s8 reg)
+{
+ return reg * 1000;
+}
+
+static inline s8 temp_to_reg(long val)
+{
+ return DIV_ROUND_CLOSEST(val, 1000);
+}
+
+#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE | \
+ HWMON_I_MAX | HWMON_I_MIN | \
+ HWMON_I_ALARM)
+#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \
+ HWMON_T_MAX | HWMON_T_MAX_HYST | \
+ HWMON_T_MAX_ALARM)
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \
+ HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \
+ HWMON_PWM_FREQ)
+static const struct hwmon_channel_info *nct6694_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ NCT6694_HWMON_IN_CONFIG, /* VIN0 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN1 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN2 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN3 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN5 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN6 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN7 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN14 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN15 */
+ NCT6694_HWMON_IN_CONFIG, /* VIN16 */
+ NCT6694_HWMON_IN_CONFIG, /* VBAT */
+ NCT6694_HWMON_IN_CONFIG, /* VSB */
+ NCT6694_HWMON_IN_CONFIG, /* AVSB */
+ NCT6694_HWMON_IN_CONFIG, /* VCC */
+ NCT6694_HWMON_IN_CONFIG, /* VHIF */
+ NCT6694_HWMON_IN_CONFIG), /* VTT */
+
+ HWMON_CHANNEL_INFO(temp,
+ NCT6694_HWMON_TEMP_CONFIG, /* THR1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR14 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR15 */
+ NCT6694_HWMON_TEMP_CONFIG, /* THR16 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */
+ NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */
+ NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */
+
+ HWMON_CHANNEL_INFO(fan,
+ NCT6694_HWMON_FAN_CONFIG, /* FIN0 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN1 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN2 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN3 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN4 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN5 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN6 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN7 */
+ NCT6694_HWMON_FAN_CONFIG, /* FIN8 */
+ NCT6694_HWMON_FAN_CONFIG), /* FIN9 */
+
+ HWMON_CHANNEL_INFO(pwm,
+ NCT6694_HWMON_PWM_CONFIG, /* PWM0 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM1 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM2 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM3 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM4 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM5 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM6 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM7 */
+ NCT6694_HWMON_PWM_CONFIG, /* PWM8 */
+ NCT6694_HWMON_PWM_CONFIG), /* PWM9 */
+ NULL
+};
+
+static int nct6694_in_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char vin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ vin_en = data->hwmon_en.vin_en[(channel / 8)];
+ *val = !!(vin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_in_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->vin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->vin);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->rpt->vin);
+
+ return 0;
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl);
+
+ return 0;
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll);
+
+ return 0;
+ case hwmon_in_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_en, temp_hyst;
+ signed char temp_max;
+ int ret, temp_raw;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ temp_en = data->hwmon_en.tin_en[channel / 8];
+ *val = !!(temp_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_temp_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->tin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->tin);
+ if (ret)
+ return ret;
+
+ temp_raw = data->rpt->tin.msb << 3;
+ temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb);
+
+ /* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */
+ *val = sign_extend32(temp_raw, 10) * 125;
+
+ return 0;
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl);
+
+ return 0;
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst);
+ *val = temp_from_reg(temp_max - temp_hyst);
+
+ return 0;
+ case hwmon_temp_max_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char fanin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ fanin_en = data->hwmon_en.fin_en[channel / 8];
+ *val = !!(fanin_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_fan_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->fin))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->fin);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->rpt->fin);
+
+ return 0;
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ *val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]);
+
+ return 0;
+ case hwmon_fan_min_alarm:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)),
+ .len = cpu_to_le16(sizeof(data->rpt->status))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->status);
+ if (ret)
+ return ret;
+
+ *val = !!(data->rpt->status & BIT(channel % 8));
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char pwm_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ pwm_en = data->hwmon_en.pwm_en[channel / 8];
+ *val = !!(pwm_en & BIT(channel % 8));
+
+ return 0;
+ case hwmon_pwm_input:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_RPT_MOD,
+ .offset = cpu_to_le16(NCT6694_PWM_IDX(channel)),
+ .len = cpu_to_le16(sizeof(data->rpt->pwm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->rpt->pwm);
+ if (ret)
+ return ret;
+
+ *val = data->rpt->pwm;
+
+ return 0;
+ case hwmon_pwm_freq:
+ *val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]);
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ if (val == 0)
+ data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_in_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_in_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ unsigned char temp_hyst;
+ signed char temp_max;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ if (val == 0)
+ data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_temp_max:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, -127000, 127000);
+ data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ case hwmon_temp_max_hyst:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+
+ val = clamp_val(val, -127000, 127000);
+ temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+ temp_hyst = temp_max - temp_to_reg(val);
+ temp_hyst = clamp_val(temp_hyst, 0, 7);
+ data->msg->hwmon_alarm.tin_cfg[channel].hyst =
+ (data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) |
+ FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ if (val == 0)
+ data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_fan_min:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 1, 65535);
+ data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ struct nct6694_cmd_header cmd_hd;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ if (val == 0)
+ data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_PWM_MOD,
+ .cmd = NCT6694_PWM_CONTROL,
+ .sel = NCT6694_PWM_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->pwm_ctrl))
+ };
+
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ if (ret)
+ return ret;
+
+ data->msg->pwm_ctrl.mal_val[channel] = val;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->pwm_ctrl);
+ case hwmon_pwm_freq:
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+
+ data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val);
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_in:
+ /* in mV */
+ return nct6694_in_read(dev, attr, channel, val);
+ case hwmon_temp:
+ /* in mC */
+ return nct6694_temp_read(dev, attr, channel, val);
+ case hwmon_fan:
+ /* in RPM */
+ return nct6694_fan_read(dev, attr, channel, val);
+ case hwmon_pwm:
+ /* in value 0~255 */
+ return nct6694_pwm_read(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_in:
+ return nct6694_in_write(dev, attr, channel, val);
+ case hwmon_temp:
+ return nct6694_temp_write(dev, attr, channel, val);
+ case hwmon_fan:
+ return nct6694_fan_write(dev, attr, channel, val);
+ case hwmon_pwm:
+ return nct6694_pwm_write(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t nct6694_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_enable:
+ case hwmon_in_max:
+ case hwmon_in_min:
+ return 0644;
+ case hwmon_in_alarm:
+ case hwmon_in_input:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_enable:
+ case hwmon_temp_max:
+ case hwmon_temp_max_hyst:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_max_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_enable:
+ case hwmon_fan_min:
+ return 0644;
+ case hwmon_fan_input:
+ case hwmon_fan_min_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ case hwmon_pwm_freq:
+ case hwmon_pwm_input:
+ return 0644;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops nct6694_hwmon_ops = {
+ .is_visible = nct6694_is_visible,
+ .read = nct6694_read,
+ .write = nct6694_write,
+};
+
+static const struct hwmon_chip_info nct6694_chip_info = {
+ .ops = &nct6694_hwmon_ops,
+ .info = nct6694_info,
+};
+
+static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
+{
+ struct nct6694_cmd_header cmd_hd = {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_CONTROL,
+ .sel = NCT6694_HWMON_CONTROL_SEL,
+ .len = cpu_to_le16(sizeof(data->hwmon_en))
+ };
+ int ret;
+
+ /*
+ * Record each Hardware Monitor Channel enable status
+ * and PWM frequency register
+ */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->hwmon_en);
+ if (ret)
+ return ret;
+
+ cmd_hd = (struct nct6694_cmd_header) {
+ .mod = NCT6694_HWMON_MOD,
+ .cmd = NCT6694_HWMON_ALARM,
+ .sel = NCT6694_HWMON_ALARM_SEL,
+ .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+ };
+
+ /* Select hwmon device alarm mode */
+ ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+ if (ret)
+ return ret;
+
+ data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ;
+
+ return nct6694_write_msg(data->nct6694, &cmd_hd,
+ &data->msg->hwmon_alarm);
+}
+
+static int nct6694_hwmon_probe(struct platform_device *pdev)
+{
+ struct nct6694_hwmon_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct device *hwmon_dev;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt),
+ GFP_KERNEL);
+ if (!data->rpt)
+ return -ENOMEM;
+
+ data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg),
+ GFP_KERNEL);
+ if (!data->msg)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ ret = devm_mutex_init(&pdev->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = nct6694_hwmon_init(data);
+ if (ret)
+ return ret;
+
+ /* Register hwmon device to HWMON framework */
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "nct6694", data,
+ &nct6694_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+ .driver = {
+ .name = "nct6694-hwmon",
+ },
+ .probe = nct6694_hwmon_probe,
+};
+
+module_platform_driver(nct6694_hwmon_driver);
+
+MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-hwmon");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread* [PATCH v8 7/7] rtc: Add Nuvoton NCT6694 RTC support
2025-02-25 8:16 [PATCH v8 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (5 preceding siblings ...)
2025-02-25 8:16 ` [PATCH v8 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2025-02-25 8:16 ` Ming Yu
6 siblings, 0 replies; 41+ messages in thread
From: Ming Yu @ 2025-02-25 8:16 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
This driver supports RTC functionality for NCT6694 MFD device
based on USB interface.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-nct6694.c | 286 ++++++++++++++++++++++++++++++++++++++
4 files changed, 298 insertions(+)
create mode 100644 drivers/rtc/rtc-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a9eda4530b07..7eba4ffdc877 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16927,6 +16927,7 @@ F: drivers/hwmon/nct6694-hwmon.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/usb/nct6694_canfd.c
+F: drivers/rtc/rtc-nct6694.c
F: drivers/watchdog/nct6694_wdt.c
F: include/linux/mfd/nct6694.h
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 0bbbf778ecfa..248425bf26f3 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 489b4ab07068..d0d6f4a4972e 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -118,6 +118,7 @@ obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o
obj-$(CONFIG_RTC_DRV_GAMECUBE) += rtc-gamecube.o
obj-$(CONFIG_RTC_DRV_NCT3018Y) += rtc-nct3018y.o
+obj-$(CONFIG_RTC_DRV_NCT6694) += rtc-nct6694.o
obj-$(CONFIG_RTC_DRV_NTXEC) += rtc-ntxec.o
obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_OPAL) += rtc-opal.o
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
new file mode 100644
index 000000000000..892674d453d1
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bcd.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/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;
+};
+
+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 int nct6694_rtc_probe(struct platform_device *pdev)
+{
+ struct nct6694_rtc_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ int ret, irq;
+
+ irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
+ if (!irq)
+ return -EINVAL;
+
+ 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->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, irq, NULL,
+ nct6694_irq, IRQF_ONESHOT,
+ "rtc-nct6694", data);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+ ret = devm_rtc_register_device(data->rtc);
+ if (ret)
+ return ret;
+
+ device_init_wakeup(&pdev->dev, true);
+ return 0;
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+ .driver = {
+ .name = "rtc-nct6694",
+ },
+ .probe = nct6694_rtc_probe,
+};
+
+module_platform_driver(nct6694_rtc_driver);
+
+MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-rtc");
--
2.34.1
^ permalink raw reply related [flat|nested] 41+ messages in thread