* [PATCH v2 1/7] mfd: Add core driver for Nuvoton NCT6694
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 6:40 ` [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
` (5 subsequent siblings)
6 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
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 <tmyu0@nuvoton.com>
---
MAINTAINERS | 7 +
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 2 +
drivers/mfd/nct6694.c | 382 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/nct6694.h | 139 +++++++++++++
5 files changed, 540 insertions(+)
create mode 100644 drivers/mfd/nct6694.c
create mode 100644 include/linux/mfd/nct6694.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 67d2159406c2..9c9a94ff8f98 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16541,6 +16541,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 03c1e4e3eea4..07ccc32f9985 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -558,6 +558,16 @@ config MFD_MX25_TSADC
i.MX25 processors. They consist of a conversion queue for general
purpose ADC and a queue for Touchscreens.
+config MFD_NCT6694
+ tristate "Nuvoton NCT6694 support"
+ select MFD_CORE
+ depends on USB
+ help
+ This adds support for Nuvoton USB device NCT6694 sharing peripherals
+ This includes the USB devcie driver and core APIs.
+ Additional drivers must be enabled in order to use the functionality
+ of the device.
+
config MFD_HI6421_PMIC
tristate "HiSilicon Hi6421 PMU/Codec IC"
depends on OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e057d6d6faef..9d0365ba6a26 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -117,6 +117,8 @@ obj-$(CONFIG_TWL6040_CORE) += twl6040.o
obj-$(CONFIG_MFD_MX25_TSADC) += fsl-imx25-tsadc.o
+obj-$(CONFIG_MFD_NCT6694) += nct6694.o
+
obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
new file mode 100644
index 000000000000..071333d13eae
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 MFD driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#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>
+
+#define MFD_DEV_SIMPLE(_name) \
+{ \
+ .name = NCT6694_DEV_##_name, \
+} \
+
+#define MFD_DEV_WITH_ID(_name, _id) \
+{ \
+ .name = NCT6694_DEV_##_name, \
+ .id = _id, \
+}
+
+/* MFD device resources */
+static const struct mfd_cell nct6694_dev[] = {
+ MFD_DEV_WITH_ID(GPIO, 0x0),
+ MFD_DEV_WITH_ID(GPIO, 0x1),
+ MFD_DEV_WITH_ID(GPIO, 0x2),
+ MFD_DEV_WITH_ID(GPIO, 0x3),
+ MFD_DEV_WITH_ID(GPIO, 0x4),
+ MFD_DEV_WITH_ID(GPIO, 0x5),
+ MFD_DEV_WITH_ID(GPIO, 0x6),
+ MFD_DEV_WITH_ID(GPIO, 0x7),
+ MFD_DEV_WITH_ID(GPIO, 0x8),
+ MFD_DEV_WITH_ID(GPIO, 0x9),
+ MFD_DEV_WITH_ID(GPIO, 0xA),
+ MFD_DEV_WITH_ID(GPIO, 0xB),
+ MFD_DEV_WITH_ID(GPIO, 0xC),
+ MFD_DEV_WITH_ID(GPIO, 0xD),
+ MFD_DEV_WITH_ID(GPIO, 0xE),
+ MFD_DEV_WITH_ID(GPIO, 0xF),
+
+ MFD_DEV_WITH_ID(I2C, 0x0),
+ MFD_DEV_WITH_ID(I2C, 0x1),
+ MFD_DEV_WITH_ID(I2C, 0x2),
+ MFD_DEV_WITH_ID(I2C, 0x3),
+ MFD_DEV_WITH_ID(I2C, 0x4),
+ MFD_DEV_WITH_ID(I2C, 0x5),
+
+ MFD_DEV_WITH_ID(CAN, 0x0),
+ MFD_DEV_WITH_ID(CAN, 0x1),
+
+ MFD_DEV_WITH_ID(WDT, 0x0),
+ MFD_DEV_WITH_ID(WDT, 0x1),
+
+ MFD_DEV_SIMPLE(HWMON),
+ MFD_DEV_SIMPLE(RTC),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694,
+ unsigned char err_status)
+{
+ struct device *dev = &nct6694->udev->dev;
+
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return err_status;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_dbg(dev, "%s: Command is not support!\n", __func__);
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_dbg(dev, "%s: Command is no response!\n", __func__);
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_dbg(dev, "%s: Command is timeout!\n", __func__);
+ break;
+ case NCT6694_PENDING:
+ dev_dbg(dev, "%s: Command is pending!\n", __func__);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+ u16 length, void *buf)
+{
+ struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
+ struct nct6694_response_header *response_header = nct6694->response_header;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ cmd_header->mod = mod;
+ cmd_header->cmd = offset & 0xFF;
+ cmd_header->sel = (offset >> 8) & 0xFF;
+ cmd_header->hctrl = NCT6694_HCTRL_GET;
+ cmd_header->len = length;
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+ cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+ response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+ buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
+ if (ret)
+ return ret;
+
+ return nct6694_response_err_handling(nct6694, response_header->sts);
+}
+EXPORT_SYMBOL(nct6694_read_msg);
+
+int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+ u16 length, void *buf)
+{
+ struct nct6694_cmd_header *cmd_header = nct6694->cmd_header;
+ struct nct6694_response_header *response_header = nct6694->response_header;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ cmd_header->mod = mod;
+ cmd_header->cmd = offset & 0xFF;
+ cmd_header->sel = (offset >> 8) & 0xFF;
+ cmd_header->hctrl = NCT6694_HCTRL_SET;
+ cmd_header->len = length;
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+ cmd_header, NCT6694_CMD_PACKET_SZ, &tx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP),
+ buf, length, &tx_len, nct6694->timeout);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+ response_header, NCT6694_CMD_PACKET_SZ, &rx_len,
+ nct6694->timeout);
+ if (ret)
+ return ret;
+
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP),
+ buf, NCT6694_MAX_PACKET_SZ, &rx_len, nct6694->timeout);
+ if (ret)
+ return ret;
+
+ return nct6694_response_err_handling(nct6694, response_header->sts);
+}
+EXPORT_SYMBOL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+ struct nct6694 *nct6694 = urb->context;
+ struct device *dev = &nct6694->udev->dev;
+ 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);
+
+ if (*int_status & (1 << irq))
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+
+ *int_status &= ~(1 << irq);
+ }
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ dev_dbg(dev, "%s: Failed to resubmit urb, status %d",
+ __func__, 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);
+ unsigned int irq = data->hwirq;
+
+ nct6694->irq_enable |= (1 << irq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ unsigned int irq = data->hwirq;
+
+ nct6694->irq_enable &= ~(1 << irq);
+}
+
+static 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 device *dev = &udev->dev;
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *int_endpoint;
+ struct nct6694 *nct6694;
+ struct nct6694_cmd_header *cmd_header;
+ struct nct6694_response_header *response_header;
+ int pipe, maxp;
+ int ret;
+
+ interface = iface->cur_altsetting;
+
+ int_endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(int_endpoint))
+ return -ENODEV;
+
+ 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);
+
+ cmd_header = devm_kzalloc(dev, sizeof(*cmd_header),
+ GFP_KERNEL);
+ if (!cmd_header)
+ return -ENOMEM;
+
+ response_header = devm_kzalloc(dev, sizeof(*response_header),
+ GFP_KERNEL);
+ if (!response_header)
+ return -ENOMEM;
+
+ nct6694->int_buffer = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), 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)
+ return -ENODEV;
+
+ nct6694->udev = udev;
+ nct6694->timeout = NCT6694_URB_TIMEOUT; /* Wait until urb complete */
+ nct6694->cmd_header = cmd_header;
+ nct6694->response_header = response_header;
+
+ mutex_init(&nct6694->access_lock);
+ mutex_init(&nct6694->irq_lock);
+
+ 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;
+
+ dev_set_drvdata(dev, nct6694);
+ usb_set_intfdata(iface, nct6694);
+
+ ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
+ if (ret)
+ goto err_mfd;
+
+ dev_info(dev, "Probed device: (%04X:%04X)\n", id->idVendor, id->idProduct);
+ return 0;
+
+err_mfd:
+ usb_kill_urb(nct6694->int_in_urb);
+err_urb:
+ usb_free_urb(nct6694->int_in_urb);
+ return dev_err_probe(dev, ret, "Probe failed\n");
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+ mfd_remove_devices(&udev->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-MFD 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..9733d47f5a01
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,139 @@
+/* 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_DEV_GPIO "nct6694-gpio"
+#define NCT6694_DEV_I2C "nct6694-i2c"
+#define NCT6694_DEV_CAN "nct6694-can"
+#define NCT6694_DEV_WDT "nct6694-wdt"
+#define NCT6694_DEV_HWMON "nct6694-hwmon"
+#define NCT6694_DEV_RTC "nct6694-rtc"
+
+#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_MAX_PACKET_SZ 0x200
+
+#define NCT6694_CMD_PACKET_SZ 0x8
+#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_CAN1,
+ NCT6694_IRQ_CAN2,
+ 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 nct6694 {
+ struct usb_device *udev;
+ struct urb *int_in_urb;
+ struct irq_domain *domain;
+ struct nct6694_cmd_header *cmd_header;
+ struct nct6694_response_header *response_header;
+ struct mutex access_lock;
+ struct mutex irq_lock;
+ unsigned char *int_buffer;
+ unsigned int irq_enable;
+ /* time in msec to wait for the urb to the complete */
+ long timeout;
+};
+
+struct nct6694_cmd_header {
+ unsigned char rsv1;
+ unsigned char mod;
+ unsigned char cmd;
+ unsigned char sel;
+ unsigned char hctrl;
+ unsigned char rsv2;
+ unsigned short len;
+} __packed;
+
+struct nct6694_response_header {
+ unsigned char sequence_id;
+ unsigned char sts;
+ unsigned int rsv;
+ unsigned short len;
+} __packed;
+
+/*
+ * nct6694_read_msg - Receive data from NCT6694 USB device
+ *
+ * @nct6694 - Nuvoton NCT6694 structure
+ * @mod - Module byte
+ * @offset - Offset byte or (Select byte | Command byte)
+ * @length - Length byte
+ * @buf - Read data from rx buffer
+ *
+ * USB Transaction format:
+ *
+ * OUT |RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
+ * OUT |SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
+ * IN |-------D------A------D------A-------|
+ * IN ......
+ * IN |-------D------A------D------A-------|
+ */
+int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+ u16 length, void *buf);
+
+/*
+ * nct6694_read_msg - Transmit data to NCT6694 USB device
+ *
+ * @nct6694 - Nuvoton NCT6694 structure
+ * @mod - Module byte
+ * @offset - Offset byte or (Select byte | Command byte)
+ * @length - Length byte
+ * @buf - Write data to tx buffer
+ *
+ * USB Transaction format:
+ *
+ * OUT |RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H|
+ * OUT |-------D------A------D------A-------|
+ * OUT ......
+ * OUT |-------D------A------D------A-------|
+ * IN |SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H|
+ * IN |-------D------A------D------A-------|
+ * IN ......
+ * IN |-------D------A------D------A-------|
+ */
+int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+ u16 length, void *buf);
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
2024-11-21 6:40 ` [PATCH v2 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 8:28 ` Bartosz Golaszewski
2024-11-21 6:40 ` [PATCH v2 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
` (4 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
This driver supports GPIO and IRQ functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/gpio/Kconfig | 12 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-nct6694.c | 441 ++++++++++++++++++++++++++++++++++++
4 files changed, 455 insertions(+)
create mode 100644 drivers/gpio/gpio-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9c9a94ff8f98..6688c5c470b7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16545,6 +16545,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 56fee58e281e..7773b5326e60 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1460,6 +1460,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..ad80a078b27b 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o
obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o
+obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 000000000000..1776200b3a04
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#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>
+
+/* Host interface */
+#define NCT6694_GPIO_MOD 0xFF
+#define NCT6694_GPIO_LEN 0x01
+
+/* Report Channel */
+#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 xmit_buf;
+ unsigned char irq_trig_falling;
+ unsigned char irq_trig_rising;
+
+ /* Current gpio group */
+ unsigned char group;
+
+ /* GPIO line names */
+ char **names;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ return !(BIT(offset) & data->xmit_buf);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ data->xmit_buf &= ~(1 << offset);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+ unsigned int offset, int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ /* Set direction to output */
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ data->xmit_buf |= (1 << offset);
+ ret = nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ /* Then set output level */
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ data->xmit_buf |= (1 << offset);
+ else
+ data->xmit_buf &= ~(1 << offset);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DIR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ if (BIT(offset) & data->xmit_buf) {
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->xmit_buf);
+ }
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & data->xmit_buf);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+ int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+ guard(mutex)(&data->lock);
+
+ nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+
+ if (val)
+ data->xmit_buf |= (1 << offset);
+ else
+ data->xmit_buf &= ~(1 << offset);
+
+ nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_DATA + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+ unsigned long config)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_TYPE + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ data->xmit_buf |= (1 << offset);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ data->xmit_buf &= ~(1 << offset);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPO_TYPE + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+}
+
+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);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPIO_VALID + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ if (ret < 0)
+ return ret;
+
+ *valid_mask = data->xmit_buf;
+
+ return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+ struct nct6694_gpio_data *data = priv;
+ unsigned char status;
+
+ guard(mutex)(&data->lock);
+
+ nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_STS + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+
+ status = data->xmit_buf;
+
+ while (status) {
+ int bit = __ffs(status);
+
+ data->xmit_buf = BIT(bit);
+ handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+ status &= ~(1 << bit);
+ nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_CLR + data->group,
+ NCT6694_GPIO_LEN, &data->xmit_buf);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_FALLING + data->group,
+ NCT6694_GPIO_LEN, &data->irq_trig_falling);
+ if (ret)
+ return ret;
+
+ return nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_RISING + data->group,
+ NCT6694_GPIO_LEN, &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);
+
+ scoped_guard(mutex, &data->lock) {
+ nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_FALLING + data->group,
+ NCT6694_GPIO_LEN, &data->irq_trig_falling);
+
+ nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
+ NCT6694_GPI_RISING + data->group,
+ NCT6694_GPIO_LEN, &data->irq_trig_rising);
+ }
+
+ mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-gpio",
+ .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;
+
+ 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;
+
+ data->names = devm_kzalloc(dev, sizeof(char *) * NCT6694_NR_GPIO,
+ GFP_KERNEL);
+ if (!data->names)
+ return -ENOMEM;
+
+ for (i = 0; i < NCT6694_NR_GPIO; i++) {
+ data->names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
+ cell->id, i);
+ if (!data->names[i])
+ return -ENOMEM;
+ }
+
+ data->nct6694 = nct6694;
+ data->group = cell->id;
+
+ data->gpio.names = (const char * const*)data->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;
+
+ mutex_init(&data->irq_lock);
+
+ platform_set_drvdata(pdev, data);
+
+ ret = nct6694_get_irq_trig(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+
+ /* Register gpio chip to GPIO framework */
+ 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,
+ "nct6694-gpio", 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 = "nct6694-gpio",
+ },
+ .probe = nct6694_gpio_probe,
+};
+
+module_platform_driver(nct6694_gpio_driver);
+
+MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support
2024-11-21 6:40 ` [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-11-21 8:28 ` Bartosz Golaszewski
2024-11-22 7:20 ` Ming Yu
0 siblings, 1 reply; 20+ messages in thread
From: Bartosz Golaszewski @ 2024-11-21 8:28 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, 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
On Thu, Nov 21, 2024 at 7:41 AM Ming Yu <a0282524688@gmail.com> wrote:
>
> This driver supports GPIO and IRQ functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> MAINTAINERS | 1 +
> drivers/gpio/Kconfig | 12 +
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-nct6694.c | 441 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 455 insertions(+)
> create mode 100644 drivers/gpio/gpio-nct6694.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9c9a94ff8f98..6688c5c470b7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16545,6 +16545,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 56fee58e281e..7773b5326e60 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1460,6 +1460,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..ad80a078b27b 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -123,6 +123,7 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
> obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
> obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o
> obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o
> +obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o
> obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
> obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
> obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o
> diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
> new file mode 100644
> index 000000000000..1776200b3a04
> --- /dev/null
> +++ b/drivers/gpio/gpio-nct6694.c
> @@ -0,0 +1,441 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 GPIO controller driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#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>
> +
> +/* Host interface */
> +#define NCT6694_GPIO_MOD 0xFF
> +#define NCT6694_GPIO_LEN 0x01
> +
> +/* Report Channel */
> +#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 xmit_buf;
> + unsigned char irq_trig_falling;
> + unsigned char irq_trig_rising;
> +
> + /* Current gpio group */
> + unsigned char group;
> +
> + /* GPIO line names */
> + char **names;
You only use this in probe() and after assigning it to gc->names, you
never reference it again. You don't need this field here, it can be a
local variable in probe().
> +};
> +
> +static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + return !(BIT(offset) & data->xmit_buf);
> +}
> +
> +static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + data->xmit_buf &= ~(1 << offset);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +}
> +
> +static int nct6694_direction_output(struct gpio_chip *gpio,
> + unsigned int offset, int val)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + /* Set direction to output */
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + data->xmit_buf |= (1 << offset);
> + ret = nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + /* Then set output level */
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + if (val)
> + data->xmit_buf |= (1 << offset);
> + else
> + data->xmit_buf &= ~(1 << offset);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +}
> +
> +static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DIR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + if (BIT(offset) & data->xmit_buf) {
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + return !!(BIT(offset) & data->xmit_buf);
> + }
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + return !!(BIT(offset) & data->xmit_buf);
> +}
> +
> +static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
> + int val)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> +
> + guard(mutex)(&data->lock);
> +
> + nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +
> + if (val)
> + data->xmit_buf |= (1 << offset);
> + else
> + data->xmit_buf &= ~(1 << offset);
> +
> + nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_DATA + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +}
> +
> +static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
> + unsigned long config)
> +{
> + struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_TYPE + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + switch (pinconf_to_config_param(config)) {
> + case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> + data->xmit_buf |= (1 << offset);
> + break;
> + case PIN_CONFIG_DRIVE_PUSH_PULL:
> + data->xmit_buf &= ~(1 << offset);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPO_TYPE + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +}
> +
> +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);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPIO_VALID + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + if (ret < 0)
> + return ret;
> +
> + *valid_mask = data->xmit_buf;
> +
> + return ret;
> +}
> +
> +static irqreturn_t nct6694_irq_handler(int irq, void *priv)
> +{
> + struct nct6694_gpio_data *data = priv;
> + unsigned char status;
> +
> + guard(mutex)(&data->lock);
> +
> + nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_STS + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> +
> + status = data->xmit_buf;
> +
> + while (status) {
> + int bit = __ffs(status);
> +
> + data->xmit_buf = BIT(bit);
> + handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
> + status &= ~(1 << bit);
> + nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_CLR + data->group,
> + NCT6694_GPIO_LEN, &data->xmit_buf);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
> +{
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_FALLING + data->group,
> + NCT6694_GPIO_LEN, &data->irq_trig_falling);
> + if (ret)
> + return ret;
> +
> + return nct6694_read_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_RISING + data->group,
> + NCT6694_GPIO_LEN, &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);
> +
> + scoped_guard(mutex, &data->lock) {
> + nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_FALLING + data->group,
> + NCT6694_GPIO_LEN, &data->irq_trig_falling);
> +
> + nct6694_write_msg(data->nct6694, NCT6694_GPIO_MOD,
> + NCT6694_GPI_RISING + data->group,
> + NCT6694_GPIO_LEN, &data->irq_trig_rising);
> + }
> +
> + mutex_unlock(&data->irq_lock);
> +}
> +
> +static const struct irq_chip nct6694_irq_chip = {
> + .name = "nct6694-gpio",
> + .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;
> +
> + 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;
> +
> + data->names = devm_kzalloc(dev, sizeof(char *) * NCT6694_NR_GPIO,
devm_kcalloc()?
> + GFP_KERNEL);
> + if (!data->names)
> + return -ENOMEM;
> +
> + for (i = 0; i < NCT6694_NR_GPIO; i++) {
> + data->names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
> + cell->id, i);
> + if (!data->names[i])
> + return -ENOMEM;
> + }
> +
> + data->nct6694 = nct6694;
> + data->group = cell->id;
> +
> + data->gpio.names = (const char * const*)data->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;
> +
> + mutex_init(&data->irq_lock);
> +
> + platform_set_drvdata(pdev, data);
There is no corresponding platform_get_drvdata() so you don't need this.
> +
> + ret = nct6694_get_irq_trig(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
> +
> + /* Register gpio chip to GPIO framework */
> + 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,
> + "nct6694-gpio", 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 = "nct6694-gpio",
> + },
> + .probe = nct6694_gpio_probe,
> +};
> +
> +module_platform_driver(nct6694_gpio_driver);
> +
> +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
It's an MFD device, don't you need a MODULE_ALIAS() for this module to load?
Bart
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support
2024-11-21 8:28 ` Bartosz Golaszewski
@ 2024-11-22 7:20 ` Ming Yu
0 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-22 7:20 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: tmyu0, lee, linus.walleij, 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
Dear Bartosz,
Thank you for your comments,
Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年11月21日 週四 下午4:28寫道:
>
> > +struct nct6694_gpio_data {
> > + struct nct6694 *nct6694;
> > + struct gpio_chip gpio;
> > + struct mutex lock;
> > + /* Protect irq operation */
> > + struct mutex irq_lock;
> > +
> > + unsigned char xmit_buf;
> > + unsigned char irq_trig_falling;
> > + unsigned char irq_trig_rising;
> > +
> > + /* Current gpio group */
> > + unsigned char group;
> > +
> > + /* GPIO line names */
> > + char **names;
>
> You only use this in probe() and after assigning it to gc->names, you
> never reference it again. You don't need this field here, it can be a
> local variable in probe().
>
Understood. I will modify it in the next patch.
> > +};
...
> > +
> > + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->names = devm_kzalloc(dev, sizeof(char *) * NCT6694_NR_GPIO,
>
> devm_kcalloc()?
>
Okay, fix it in v3.
> > + GFP_KERNEL);
...
> > + mutex_init(&data->irq_lock);
> > +
> > + platform_set_drvdata(pdev, data);
>
> There is no corresponding platform_get_drvdata() so you don't need this.
>
Okay, I'll drop it.
> > +
...
> > +module_platform_driver(nct6694_gpio_driver);
> > +
> > +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
>
> It's an MFD device, don't you need a MODULE_ALIAS() for this module to load?
>
I will add MODULE_ALIAS() for each child driver.
Best regards,
Ming
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 3/7] i2c: Add Nuvoton NCT6694 I2C support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
2024-11-21 6:40 ` [PATCH v2 1/7] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-11-21 6:40 ` [PATCH v2 2/7] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 6:40 ` [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
` (3 subsequent siblings)
6 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
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 <tmyu0@nuvoton.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 6688c5c470b7..a190f2b08fa3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16546,6 +16546,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 4977abcd7c46..1962cf1e71f9 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1325,6 +1325,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 a6bcbf2febcf..6d2fd8e56569 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o
+obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 000000000000..2a5e0629f860
--- /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>
+
+/* Host interface */
+#define NCT6694_I2C_MOD 0x03
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_I2C_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define NCT6694_I2C_CMD0_LEN 0x90
+
+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_cmd0 {
+ 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;
+ unsigned char *xmit_buf;
+ 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_cmd0 *cmd = (struct nct6694_i2c_cmd0 *)data->xmit_buf;
+ int ret, i;
+
+ for (i = 0; i < num ; i++) {
+ struct i2c_msg *msg_temp = &msgs[i];
+
+ memset(data->xmit_buf, 0, sizeof(struct nct6694_i2c_cmd0));
+
+ if (msg_temp->len > 64)
+ return -EPROTO;
+ cmd->port = data->port;
+ cmd->br = data->br;
+ cmd->addr = i2c_8bit_addr_from_msg(msg_temp);
+ if (msg_temp->flags & I2C_M_RD) {
+ cmd->r_cnt = msg_temp->len;
+ ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
+ NCT6694_I2C_CMD0_OFFSET,
+ NCT6694_I2C_CMD0_LEN,
+ cmd);
+ if (ret < 0)
+ return 0;
+
+ memcpy(msg_temp->buf, cmd->read_data, msg_temp->len);
+ } else {
+ cmd->w_cnt = msg_temp->len;
+ memcpy(cmd->write_data, msg_temp->buf, msg_temp->len);
+ ret = nct6694_write_msg(data->nct6694, NCT6694_I2C_MOD,
+ NCT6694_I2C_CMD0_OFFSET,
+ NCT6694_I2C_CMD0_LEN,
+ cmd);
+ if (ret < 0)
+ return 0;
+ }
+ }
+
+ 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->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!data->xmit_buf)
+ 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 = "nct6694-i2c",
+ },
+ .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");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (2 preceding siblings ...)
2024-11-21 6:40 ` [PATCH v2 3/7] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 7:47 ` Vincent Mailhol
2024-11-21 6:40 ` [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
` (2 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
This driver supports Socket CANfd functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/net/can/Kconfig | 10 +
drivers/net/can/Makefile | 1 +
drivers/net/can/nct6694_canfd.c | 926 ++++++++++++++++++++++++++++++++
4 files changed, 938 insertions(+)
create mode 100644 drivers/net/can/nct6694_canfd.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a190f2b08fa3..eb5d46825e71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16548,6 +16548,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/nct6694_canfd.c
F: include/linux/mfd/nct6694.h
NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index cf989bea9aa3..130e98ec28a5 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -200,6 +200,16 @@ config CAN_SUN4I
To compile this driver as a module, choose M here: the module will
be called sun4i_can.
+config CAN_NCT6694
+ tristate "Nuvoton NCT6694 Socket CANfd support"
+ depends on MFD_NCT6694
+ 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_TI_HECC
depends on ARM
tristate "TI High End CAN Controller"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index a71db2cfe990..4a6b5b9d6c2b 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd.o
obj-$(CONFIG_CAN_MSCAN) += mscan/
obj-$(CONFIG_CAN_M_CAN) += m_can/
+obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
obj-$(CONFIG_CAN_SJA1000) += sja1000/
obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c
new file mode 100644
index 000000000000..d488414981ec
--- /dev/null
+++ b/drivers/net/can/nct6694_canfd.c
@@ -0,0 +1,926 @@
+// 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/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-can"
+
+/* Host interface */
+#define NCT6694_CAN_MOD 0x05
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_CAN_CMD0_LEN 0x18
+#define NCT6694_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000)
+#define NCT6694_CAN_CTRL1_MON BIT(0)
+#define NCT6694_CAN_CTRL1_NISO BIT(1)
+#define NCT6694_CAN_CTRL1_LBCK BIT(2)
+
+/* Command 01h */
+#define NCT6694_CAN_CMD1_LEN 0x08
+#define NCT6694_CAN_CMD1_OFFSET 0x0001
+
+/* Command 02h */
+#define NCT6694_CAN_CMD2_LEN 0x10
+#define NCT6694_CAN_CMD2_OFFSET(idx, mask) \
+ ({ typeof(mask) mask_ = (mask); \
+ idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) : \
+ ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
+
+#define NCT6694_CAN_EVENT_ERR BIT(0)
+#define NCT6694_CAN_EVENT_STATUS BIT(1)
+#define NCT6694_CAN_EVENT_TX_EVT BIT(2)
+#define NCT6694_CAN_EVENT_RX_EVT BIT(3)
+#define NCT6694_CAN_EVENT_REC BIT(4)
+#define NCT6694_CAN_EVENT_TEC BIT(5)
+#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_HALF_FULL BIT(6) /* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_IN BIT(7)
+
+/* Command 10h */
+#define NCT6694_CAN_CMD10_LEN 0x90
+#define NCT6694_CAN_CMD10_OFFSET(buf_cnt) \
+ (((buf_cnt) & 0xFF) << 8 | 0x10)
+#define NCT6694_CAN_TAG_CAN0 0xC0
+#define NCT6694_CAN_TAG_CAN1 0xC1
+#define NCT6694_CAN_FLAG_EFF BIT(0)
+#define NCT6694_CAN_FLAG_RTR BIT(1)
+#define NCT6694_CAN_FLAG_FD BIT(2)
+#define NCT6694_CAN_FLAG_BRS BIT(3)
+#define NCT6694_CAN_FLAG_ERR BIT(4)
+
+/* Command 11h */
+#define NCT6694_CAN_CMD11_LEN 0x90
+#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt) \
+ ({ typeof(buf_cnt) buf_cnt_ = (buf_cnt); \
+ idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) : \
+ ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })
+
+#define NCT6694_CAN_RX_QUOTA 64
+
+enum nct6694_event_err {
+ NCT6694_CAN_EVT_NO_ERROR,
+ NCT6694_CAN_EVT_CRC_ERROR,
+ NCT6694_CAN_EVT_STUFF_ERROR,
+ NCT6694_CAN_EVT_ACK_ERROR,
+ NCT6694_CAN_EVT_FORM_ERROR,
+ NCT6694_CAN_EVT_BIT_ERROR,
+ NCT6694_CAN_EVT_TIMEOUT_ERROR,
+ NCT6694_CAN_EVT_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+ NCT6694_CAN_EVT_ERROR_ACTIVE,
+ NCT6694_CAN_EVT_ERROR_PASSIVE,
+ NCT6694_CAN_EVT_BUS_OFF,
+ NCT6694_CAN_EVT_WARNING,
+};
+
+struct __packed nct6694_can_cmd0 {
+ u32 nbr;
+ u32 dbr;
+ u32 active:8;
+ u32 reserved:24;
+ u16 ctrl1;
+ u16 ctrl2;
+ u32 nbtp;
+ u32 dbtp;
+};
+
+struct __packed nct6694_can_cmd1 {
+ u8 tx_fifo_cnt;
+ u8 rx_fifo_cnt;
+ u16 reserved;
+ __le32 can_clk;
+};
+
+struct __packed nct6694_can_cmd2 {
+ u8 err1;
+ u8 status1;
+ u8 tx_evt1;
+ u8 rx_evt1;
+ u8 rec1;
+ u8 tec1;
+ u8 reserved1[2];
+ u8 err2;
+ u8 status2;
+ u8 tx_evt2;
+ u8 rx_evt2;
+ u8 rec2;
+ u8 tec2;
+ u8 reserved2[2];
+};
+
+struct __packed nct6694_can_cmd10_11 {
+ u8 tag;
+ u8 flag;
+ u8 reserved;
+ u8 dlc;
+ __le32 id;
+ u8 data[64];
+ u8 msg_buf[72];
+};
+
+struct nct6694_canfd_priv {
+ struct can_priv can; /* must be the first member */
+ struct net_device *ndev;
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ struct sk_buff *tx_skb;
+ struct workqueue_struct *wq;
+ struct work_struct tx_work;
+ unsigned char *tx_buf;
+ unsigned char *rx_buf;
+ unsigned char can_idx;
+ bool tx_busy;
+};
+
+static const struct can_bittiming_const nct6694_canfd_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_canfd_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_canfd_clean(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ if (priv->tx_skb || priv->tx_busy)
+ ndev->stats.tx_errors++;
+ dev_kfree_skb(priv->tx_skb);
+ if (priv->tx_busy)
+ can_free_echo_skb(priv->ndev, 0, NULL);
+ priv->tx_skb = NULL;
+ priv->tx_busy = false;
+}
+
+static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
+ u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+
+ ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD2_OFFSET(priv->can_idx, mask),
+ NCT6694_CAN_CMD2_LEN,
+ buf);
+ if (ret < 0)
+ return ret;
+
+ bec->rxerr = priv->can_idx ? buf->rec2 : buf->rec1;
+ bec->txerr = priv->can_idx ? buf->tec2 : buf->tec1;
+
+ return 0;
+}
+
+static int nuv_canfd_handle_lost_msg(struct net_device *ndev)
+{
+ struct net_device_stats *stats = &ndev->stats;
+ struct sk_buff *skb;
+ struct can_frame *frame;
+
+ netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+ stats->rx_errors++;
+ stats->rx_over_errors++;
+
+ skb = alloc_can_err_skb(ndev, &frame);
+ if (unlikely(!skb))
+ return 0;
+
+ frame->can_id |= CAN_ERR_CRTL;
+ frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+static void nuv_canfd_read_fifo(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->rx_buf;
+ struct net_device_stats *stats = &ndev->stats;
+ struct canfd_frame *cf;
+ struct sk_buff *skb;
+ int can_idx = priv->can_idx;
+ u32 id;
+ int ret;
+ u8 fd_format = 0;
+ int i;
+
+ guard(mutex)(&priv->lock);
+
+ ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
+ NCT6694_CAN_CMD11_LEN, buf);
+ if (ret < 0)
+ return;
+
+ /* Check type of frame and create skb */
+ fd_format = buf->flag & NCT6694_CAN_FLAG_FD;
+ if (fd_format)
+ skb = alloc_canfd_skb(ndev, &cf);
+ else
+ skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
+
+ if (!skb) {
+ stats->rx_dropped++;
+ return;
+ }
+
+ cf->len = buf->dlc;
+
+ /* Get ID and set flag by its type(Standard ID format or Ext ID format) */
+ id = le32_to_cpu(buf->id);
+ if (buf->flag & NCT6694_CAN_FLAG_EFF) {
+ /*
+ * In case the Extended ID frame is received, the standard
+ * and extended part of the ID are swapped in the register,
+ * so swap them back to obtain the correct ID.
+ */
+ id |= CAN_EFF_FLAG;
+ }
+
+ cf->can_id = id;
+
+ /* Set ESI flag */
+ if (buf->flag & NCT6694_CAN_FLAG_ERR) {
+ cf->flags |= CANFD_ESI;
+ netdev_dbg(ndev, "ESI Error\n");
+ }
+
+ /* Set RTR and BRS */
+ if (!fd_format && (buf->flag & NCT6694_CAN_FLAG_RTR)) {
+ cf->can_id |= CAN_RTR_FLAG;
+ } else {
+ if (buf->flag & NCT6694_CAN_FLAG_BRS)
+ cf->flags |= CANFD_BRS;
+
+ for (i = 0; i < cf->len; i++)
+ cf->data[i] = buf->data[i];
+ }
+
+ /* Remove the packet from FIFO */
+ stats->rx_packets++;
+ stats->rx_bytes += cf->len;
+ netif_receive_skb(skb);
+}
+
+static int nct6694_canfd_do_rx_poll(struct net_device *ndev, int quota)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
+ int can_idx = priv->can_idx;
+ u32 pkts = 0;
+ u8 mask_rx = NCT6694_CAN_EVENT_RX_EVT;
+ u8 rx_evt;
+
+ for (;;) {
+ scoped_guard(mutex, &priv->lock) {
+ nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD2_OFFSET(can_idx, mask_rx),
+ NCT6694_CAN_CMD2_LEN, buf);
+
+ rx_evt = can_idx ? buf->rx_evt2 : buf->rx_evt1;
+ }
+
+ if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST)
+ nuv_canfd_handle_lost_msg(ndev);
+
+ /* No data */
+ if ((rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) == 0)
+ break;
+
+ if (quota <= 0)
+ break;
+
+ nuv_canfd_read_fifo(ndev);
+ quota--;
+ pkts++;
+ }
+
+ return pkts;
+}
+
+static int nct6694_canfd_handle_lec_err(struct net_device *ndev, u8 bus_err)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
+ return 0;
+
+ priv->can.can_stats.bus_error++;
+ stats->rx_errors++;
+
+ /* Propagate the error condition to the CAN stack. */
+ skb = alloc_can_err_skb(ndev, &cf);
+
+ if (unlikely(!skb))
+ return 0;
+
+ /* Read the error counter register and check for new errors. */
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+ switch (bus_err) {
+ case NCT6694_CAN_EVT_CRC_ERROR:
+ cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
+ break;
+
+ case NCT6694_CAN_EVT_STUFF_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+
+ case NCT6694_CAN_EVT_ACK_ERROR:
+ cf->data[3] = CAN_ERR_PROT_LOC_ACK;
+ break;
+
+ case NCT6694_CAN_EVT_FORM_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+
+ case NCT6694_CAN_EVT_BIT_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_BIT |
+ CAN_ERR_PROT_BIT0 |
+ CAN_ERR_PROT_BIT1;
+ break;
+
+ case NCT6694_CAN_EVT_TIMEOUT_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ break;
+
+ case NCT6694_CAN_EVT_UNKNOWN_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ /*
+ * It means 'unspecified'(the value is '0').
+ * But it is not sure if it's ok to send an error package
+ * without specific error bit.
+ */
+ break;
+
+ default:
+ break;
+ }
+
+ /* Reset the error counter, ack the IRQ and re-enable the counter. */
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+static int nct6694_canfd_handle_state_change(struct net_device *ndev,
+ enum can_state new_state)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ struct can_berr_counter bec;
+
+ switch (new_state) {
+ case CAN_STATE_ERROR_ACTIVE:
+ /* error active state */
+ priv->can.can_stats.error_warning++;
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+ break;
+ case CAN_STATE_ERROR_WARNING:
+ /* error warning state */
+ priv->can.can_stats.error_warning++;
+ priv->can.state = CAN_STATE_ERROR_WARNING;
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ /* error passive state */
+ priv->can.can_stats.error_passive++;
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ break;
+ case CAN_STATE_BUS_OFF:
+ /* bus-off state */
+ priv->can.state = CAN_STATE_BUS_OFF;
+ priv->can.can_stats.bus_off++;
+ can_bus_off(ndev);
+ break;
+ default:
+ break;
+ }
+
+ /* propagate the error condition to the CAN stack */
+ skb = alloc_can_err_skb(ndev, &cf);
+ if (unlikely(!skb))
+ return 0;
+
+ nct6694_canfd_get_berr_counter(ndev, &bec);
+
+ switch (new_state) {
+ case CAN_STATE_ERROR_WARNING:
+ /* error warning state */
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
+ CAN_ERR_CRTL_RX_WARNING;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ /* error passive state */
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+ if (bec.txerr > 127)
+ cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+ cf->data[6] = bec.txerr;
+ cf->data[7] = bec.rxerr;
+ break;
+ case CAN_STATE_BUS_OFF:
+ /* bus-off state */
+ cf->can_id |= CAN_ERR_BUSOFF;
+ break;
+ default:
+ break;
+ }
+
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_receive_skb(skb);
+
+ return 1;
+}
+
+static int nct6694_canfd_handle_state_errors(struct net_device *ndev,
+ unsigned char can_status)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ int work_done = 0;
+
+ if (can_status == NCT6694_CAN_EVT_ERROR_ACTIVE &&
+ priv->can.state != CAN_STATE_ERROR_ACTIVE) {
+ netdev_dbg(ndev, "Error, entered active state\n");
+ work_done += nct6694_canfd_handle_state_change(ndev,
+ CAN_STATE_ERROR_ACTIVE);
+ }
+
+ if (can_status == NCT6694_CAN_EVT_WARNING &&
+ priv->can.state != CAN_STATE_ERROR_WARNING) {
+ netdev_dbg(ndev, "Error, entered warning state\n");
+ work_done += nct6694_canfd_handle_state_change(ndev,
+ CAN_STATE_ERROR_WARNING);
+ }
+
+ if (can_status == NCT6694_CAN_EVT_ERROR_PASSIVE &&
+ priv->can.state != CAN_STATE_ERROR_PASSIVE) {
+ netdev_dbg(ndev, "Error, entered passive state\n");
+ work_done += nct6694_canfd_handle_state_change(ndev,
+ CAN_STATE_ERROR_PASSIVE);
+ }
+
+ if (can_status == NCT6694_CAN_EVT_BUS_OFF &&
+ priv->can.state != CAN_STATE_BUS_OFF) {
+ netdev_dbg(ndev, "Error, entered bus-off state\n");
+ work_done += nct6694_canfd_handle_state_change(ndev,
+ CAN_STATE_BUS_OFF);
+ }
+
+ return work_done;
+}
+
+static int nct6694_canfd_poll(struct net_device *ndev, int quota)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
+ int can_idx = priv->can_idx;
+ int work_done = 0, ret;
+ u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
+ u8 bus_err, can_status;
+
+ scoped_guard(mutex, &priv->lock) {
+ ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
+ NCT6694_CAN_CMD2_LEN, buf);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ if (can_idx) {
+ bus_err = buf->err2;
+ can_status = buf->status2;
+ } else {
+ bus_err = buf->err1;
+ can_status = buf->status1;
+ }
+ }
+
+ /* Handle bus state changes */
+ work_done += nct6694_canfd_handle_state_errors(ndev, can_status);
+
+ /* Handle lec errors on the bus */
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+ work_done += nct6694_canfd_handle_lec_err(ndev, bus_err);
+
+ /* Handle RX events */
+ work_done += nct6694_canfd_do_rx_poll(ndev, quota - work_done);
+ return work_done;
+}
+
+static void nct6694_canfd_tx_irq(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+
+ guard(mutex)(&priv->lock);
+ stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
+ stats->tx_packets++;
+ priv->tx_busy = false;
+ netif_wake_queue(ndev);
+}
+
+static irqreturn_t nct6694_canfd_irq(int irq, void *data)
+{
+ struct net_device *ndev = data;
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
+ int can_idx = priv->can_idx;
+ int ret;
+ u8 mask_sts = NCT6694_CAN_EVENT_TX_EVT;
+ u8 tx_evt;
+
+ scoped_guard(mutex, &priv->lock) {
+ ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD2_OFFSET(can_idx, mask_sts),
+ NCT6694_CAN_CMD2_LEN, buf);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ tx_evt = can_idx ? buf->tx_evt2 : buf->tx_evt1;
+ }
+
+ if (tx_evt) {
+ nct6694_canfd_tx_irq(ndev);
+ } else {
+ ret = nct6694_canfd_poll(ndev, NCT6694_CAN_RX_QUOTA);
+ if (!ret)
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int nct6694_canfd_start(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd0 *buf = (struct nct6694_can_cmd0 *)priv->tx_buf;
+ const struct can_bittiming *n_bt = &priv->can.bittiming;
+ const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+ int ret;
+
+ guard(mutex)(&priv->lock);
+
+ memset(priv->tx_buf, 0, NCT6694_CAN_CMD0_LEN);
+ buf->nbr = n_bt->bitrate;
+ buf->dbr = d_bt->bitrate;
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ buf->ctrl1 |= NCT6694_CAN_CTRL1_MON;
+
+ if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+ priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+ buf->ctrl1 |= NCT6694_CAN_CTRL1_NISO;
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ buf->ctrl1 |= NCT6694_CAN_CTRL1_LBCK;
+
+ ret = nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD0_OFFSET(priv->can_idx),
+ NCT6694_CAN_CMD0_LEN, buf);
+ if (ret < 0)
+ return ret;
+
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ return 0;
+}
+
+static int nct6694_canfd_stop(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ free_irq(ndev->irq, ndev);
+ destroy_workqueue(priv->wq);
+ priv->wq = NULL;
+ nct6694_canfd_clean(ndev);
+ priv->can.state = CAN_STATE_STOPPED;
+ close_candev(ndev);
+
+ return 0;
+}
+
+static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ switch (mode) {
+ case CAN_MODE_START:
+ nct6694_canfd_clean(ndev);
+ nct6694_canfd_start(ndev);
+ netif_wake_queue(ndev);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int nct6694_canfd_open(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = open_candev(ndev);
+ if (ret)
+ return ret;
+
+ ret = request_threaded_irq(ndev->irq, NULL,
+ nct6694_canfd_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;
+ }
+
+ priv->tx_skb = NULL;
+ priv->tx_busy = false;
+
+ ret = nct6694_canfd_start(ndev);
+ if (ret)
+ goto destroy_wq;
+
+ netif_start_queue(ndev);
+
+ return 0;
+
+destroy_wq:
+ destroy_workqueue(priv->wq);
+free_irq:
+ free_irq(ndev->irq, ndev);
+close_candev:
+ close_candev(ndev);
+ return ret;
+}
+
+static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ if (priv->tx_skb || priv->tx_busy) {
+ netdev_err(ndev, "hard_xmit called while tx busy\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ if (can_dev_dropped_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ netif_stop_queue(ndev);
+ priv->tx_skb = skb;
+ queue_work(priv->wq, &priv->tx_work);
+
+ return NETDEV_TX_OK;
+}
+
+static void nct6694_canfd_tx(struct net_device *ndev, struct canfd_frame *cf)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->tx_buf;
+ u32 txid = 0;
+ int i;
+
+ memset(buf, 0, NCT6694_CAN_CMD10_LEN);
+
+ if (priv->can_idx == 0)
+ buf->tag = NCT6694_CAN_TAG_CAN0;
+ else
+ buf->tag = NCT6694_CAN_TAG_CAN1;
+
+ if (cf->can_id & CAN_EFF_FLAG) {
+ txid = cf->can_id & CAN_EFF_MASK;
+ /*
+ * In case the Extended ID frame is transmitted, the
+ * standard and extended part of the ID are swapped
+ * in the register, so swap them back to send the
+ * correct ID.
+ */
+ buf->flag |= NCT6694_CAN_FLAG_EFF;
+ } else {
+ txid = cf->can_id & CAN_SFF_MASK;
+ }
+
+ buf->id = cpu_to_le32(txid);
+ buf->dlc = cf->len;
+
+ if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+ can_is_canfd_skb(priv->tx_skb)) {
+ buf->flag |= NCT6694_CAN_FLAG_FD;
+ if (cf->flags & CANFD_BRS)
+ buf->flag |= NCT6694_CAN_FLAG_BRS;
+ }
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ buf->flag |= NCT6694_CAN_FLAG_RTR;
+
+ /* set data to buf */
+ for (i = 0; i < cf->len; i++)
+ buf->data[i] = cf->data[i];
+
+ nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD10_OFFSET(1),
+ NCT6694_CAN_CMD10_LEN,
+ buf);
+}
+
+static void nct6694_canfd_tx_work(struct work_struct *work)
+{
+ struct nct6694_canfd_priv *priv = container_of(work,
+ struct nct6694_canfd_priv,
+ tx_work);
+ struct net_device *ndev = priv->ndev;
+ struct canfd_frame *cf;
+
+ guard(mutex)(&priv->lock);
+
+ if (priv->tx_skb) {
+ if (priv->can.state == CAN_STATE_BUS_OFF) {
+ nct6694_canfd_clean(ndev);
+ } else {
+ cf = (struct canfd_frame *)priv->tx_skb->data;
+ nct6694_canfd_tx(ndev, cf);
+ priv->tx_busy = true;
+ can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
+ priv->tx_skb = NULL;
+ }
+ }
+}
+
+static const struct net_device_ops nct6694_canfd_netdev_ops = {
+ .ndo_open = nct6694_canfd_open,
+ .ndo_stop = nct6694_canfd_stop,
+ .ndo_start_xmit = nct6694_canfd_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
+{
+ struct nct6694_can_cmd1 *buf = (struct nct6694_can_cmd1 *)priv->rx_buf;
+ int ret;
+
+ ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
+ NCT6694_CAN_CMD1_OFFSET,
+ NCT6694_CAN_CMD1_LEN,
+ buf);
+ if (ret)
+ return ret;
+
+ return le32_to_cpu(buf->can_clk);
+}
+
+static int nct6694_canfd_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_canfd_priv *priv;
+ struct net_device *ndev;
+ int ret, irq, can_clk;
+
+ irq = irq_create_mapping(nct6694->domain,
+ NCT6694_IRQ_CAN1 + cell->id);
+ if (!irq)
+ return -EINVAL;
+
+ ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
+ if (!ndev)
+ return -ENOMEM;
+
+ ndev->irq = irq;
+ ndev->flags |= IFF_ECHO;
+ ndev->netdev_ops = &nct6694_canfd_netdev_ops;
+
+ priv = netdev_priv(ndev);
+ priv->nct6694 = nct6694;
+ priv->ndev = ndev;
+
+ priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!priv->tx_buf) {
+ ret = -ENOMEM;
+ goto free_candev;
+ }
+
+ priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!priv->rx_buf) {
+ ret = -ENOMEM;
+ goto free_candev;
+ }
+
+ can_clk = nct6694_canfd_get_clock(priv);
+ if (can_clk < 0) {
+ ret = -EIO;
+ goto free_candev;
+ }
+
+ mutex_init(&priv->lock);
+ INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
+
+ priv->can_idx = cell->id;
+ priv->can.state = CAN_STATE_STOPPED;
+ priv->can.clock.freq = can_clk;
+ priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
+ priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
+ priv->can.do_set_mode = nct6694_canfd_set_mode;
+ priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
+
+ priv->can.ctrlmode = CAN_CTRLMODE_FD;
+
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+ CAN_CTRLMODE_LISTENONLY |
+ CAN_CTRLMODE_FD |
+ CAN_CTRLMODE_FD_NON_ISO |
+ CAN_CTRLMODE_BERR_REPORTING;
+
+ platform_set_drvdata(pdev, priv);
+ SET_NETDEV_DEV(priv->ndev, &pdev->dev);
+
+ ret = register_candev(priv->ndev);
+ if (ret)
+ goto free_candev;
+
+ return 0;
+
+free_candev:
+ free_candev(ndev);
+ return dev_err_probe(&pdev->dev, ret, "Probe failed\n");
+}
+
+static void nct6694_canfd_remove(struct platform_device *pdev)
+{
+ struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&priv->tx_work);
+ unregister_candev(priv->ndev);
+ free_candev(priv->ndev);
+}
+
+static struct platform_driver nct6694_canfd_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_canfd_probe,
+ .remove = nct6694_canfd_remove,
+};
+
+module_platform_driver(nct6694_canfd_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support
2024-11-21 6:40 ` [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-11-21 7:47 ` Vincent Mailhol
2024-11-22 8:03 ` Ming Yu
0 siblings, 1 reply; 20+ messages in thread
From: Vincent Mailhol @ 2024-11-21 7:47 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
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
Hi Ming,
I did a quick review.
On 21/11/2024 at 15:40, Ming Yu wrote:
> This driver supports Socket CANfd functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> MAINTAINERS | 1 +
> drivers/net/can/Kconfig | 10 +
> drivers/net/can/Makefile | 1 +
> drivers/net/can/nct6694_canfd.c | 926 ++++++++++++++++++++++++++++++++
> 4 files changed, 938 insertions(+)
> create mode 100644 drivers/net/can/nct6694_canfd.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a190f2b08fa3..eb5d46825e71 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16548,6 +16548,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/nct6694_canfd.c
> F: include/linux/mfd/nct6694.h
>
> NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
> index cf989bea9aa3..130e98ec28a5 100644
> --- a/drivers/net/can/Kconfig
> +++ b/drivers/net/can/Kconfig
> @@ -200,6 +200,16 @@ config CAN_SUN4I
> To compile this driver as a module, choose M here: the module will
> be called sun4i_can.
>
> +config CAN_NCT6694
> + tristate "Nuvoton NCT6694 Socket CANfd support"
> + depends on MFD_NCT6694
> + 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_TI_HECC
> depends on ARM
> tristate "TI High End CAN Controller"
> diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> index a71db2cfe990..4a6b5b9d6c2b 100644
> --- a/drivers/net/can/Makefile
> +++ b/drivers/net/can/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
> obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd.o
> obj-$(CONFIG_CAN_MSCAN) += mscan/
> obj-$(CONFIG_CAN_M_CAN) += m_can/
> +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
> obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
> obj-$(CONFIG_CAN_SJA1000) += sja1000/
> obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o
> diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c
> new file mode 100644
> index 000000000000..d488414981ec
> --- /dev/null
> +++ b/drivers/net/can/nct6694_canfd.c
> @@ -0,0 +1,926 @@
> +// 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/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-can"
> +
> +/* Host interface */
> +#define NCT6694_CAN_MOD 0x05
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define NCT6694_CAN_CMD0_LEN 0x18> +#define NCT6694_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000)
> +#define NCT6694_CAN_CTRL1_MON BIT(0)
> +#define NCT6694_CAN_CTRL1_NISO BIT(1)
> +#define NCT6694_CAN_CTRL1_LBCK BIT(2)
> +
> +/* Command 01h */
> +#define NCT6694_CAN_CMD1_LEN 0x08
> +#define NCT6694_CAN_CMD1_OFFSET 0x0001
> +
> +/* Command 02h */
> +#define NCT6694_CAN_CMD2_LEN 0x10
> +#define NCT6694_CAN_CMD2_OFFSET(idx, mask) \
> + ({ typeof(mask) mask_ = (mask); \
> + idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) : \
> + ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
> +
> +#define NCT6694_CAN_EVENT_ERR BIT(0)
> +#define NCT6694_CAN_EVENT_STATUS BIT(1)
> +#define NCT6694_CAN_EVENT_TX_EVT BIT(2)
> +#define NCT6694_CAN_EVENT_RX_EVT BIT(3)
> +#define NCT6694_CAN_EVENT_REC BIT(4)
> +#define NCT6694_CAN_EVENT_TEC BIT(5)
> +#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_HALF_FULL BIT(6) /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_IN BIT(7)
> +
> +/* Command 10h */
> +#define NCT6694_CAN_CMD10_LEN 0x90
> +#define NCT6694_CAN_CMD10_OFFSET(buf_cnt) \
> + (((buf_cnt) & 0xFF) << 8 | 0x10)
> +#define NCT6694_CAN_TAG_CAN0 0xC0
> +#define NCT6694_CAN_TAG_CAN1 0xC1
> +#define NCT6694_CAN_FLAG_EFF BIT(0)
> +#define NCT6694_CAN_FLAG_RTR BIT(1)
> +#define NCT6694_CAN_FLAG_FD BIT(2)
> +#define NCT6694_CAN_FLAG_BRS BIT(3)
> +#define NCT6694_CAN_FLAG_ERR BIT(4)
> +
> +/* Command 11h */
> +#define NCT6694_CAN_CMD11_LEN 0x90
> +#define NCT6694_CAN_CMD11_OFFSET(idx, buf_cnt) \
> + ({ typeof(buf_cnt) buf_cnt_ = (buf_cnt); \
> + idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) : \
> + ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); })
> +
> +#define NCT6694_CAN_RX_QUOTA 64
> +
> +enum nct6694_event_err {
> + NCT6694_CAN_EVT_NO_ERROR,
> + NCT6694_CAN_EVT_CRC_ERROR,
> + NCT6694_CAN_EVT_STUFF_ERROR,
> + NCT6694_CAN_EVT_ACK_ERROR,
> + NCT6694_CAN_EVT_FORM_ERROR,
> + NCT6694_CAN_EVT_BIT_ERROR,
> + NCT6694_CAN_EVT_TIMEOUT_ERROR,
> + NCT6694_CAN_EVT_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> + NCT6694_CAN_EVT_ERROR_ACTIVE,
> + NCT6694_CAN_EVT_ERROR_PASSIVE,
> + NCT6694_CAN_EVT_BUS_OFF,
> + NCT6694_CAN_EVT_WARNING,
> +};
> +
> +struct __packed nct6694_can_cmd0 {
Give more meaningfull names to your structures. For example:
/* cmd1 */
struct __packed nct6694_can_bittiming
> + u32 nbr;
> + u32 dbr;
> + u32 active:8;
> + u32 reserved:24;
> + u16 ctrl1;
> + u16 ctrl2;
> + u32 nbtp;
> + u32 dbtp;
> +};
Always use the specific endianess types in the structures that you are
sending to the device. e.g. replace u32 by __le32 (assuming little endian).
> +struct __packed nct6694_can_cmd1 {
> + u8 tx_fifo_cnt;
> + u8 rx_fifo_cnt;
> + u16 reserved;
> + __le32 can_clk;
> +};
> +
> +struct __packed nct6694_can_cmd2 {
> + u8 err1;
> + u8 status1;
> + u8 tx_evt1;
> + u8 rx_evt1;
> + u8 rec1;
> + u8 tec1;
> + u8 reserved1[2];
> + u8 err2;
> + u8 status2;
> + u8 tx_evt2;
> + u8 rx_evt2;
> + u8 rec2;
> + u8 tec2;
> + u8 reserved2[2];
> +};
> +
> +struct __packed nct6694_can_cmd10_11 {
> + u8 tag;
> + u8 flag;
> + u8 reserved;
> + u8 dlc;
> + __le32 id;
> + u8 data[64];
> + u8 msg_buf[72];
> +};
> +
> +struct nct6694_canfd_priv {
Be consistent in your name space. Sometime you prefix your names with
nct6694_can and sometimes with nct6694_canfd for no apparent reasons.
> + struct can_priv can; /* must be the first member */
> + struct net_device *ndev;
> + struct nct6694 *nct6694;
> + struct mutex lock;
> + struct sk_buff *tx_skb;
> + struct workqueue_struct *wq;
> + struct work_struct tx_work;
> + unsigned char *tx_buf;
> + unsigned char *rx_buf;
> + unsigned char can_idx;
> + bool tx_busy;
> +};
> +
> +static const struct can_bittiming_const nct6694_canfd_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_canfd_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_canfd_clean(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +
> + if (priv->tx_skb || priv->tx_busy)
> + ndev->stats.tx_errors++;
> + dev_kfree_skb(priv->tx_skb);
> + if (priv->tx_busy)
> + can_free_echo_skb(priv->ndev, 0, NULL);
> + priv->tx_skb = NULL;
> + priv->tx_busy = false;
> +}
> +
> +static int nct6694_canfd_get_berr_counter(const struct net_device *ndev,
> + struct can_berr_counter *bec)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
> + u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
> + int ret;
> +
> + guard(mutex)(&priv->lock);
> +
> + ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD2_OFFSET(priv->can_idx, mask),
> + NCT6694_CAN_CMD2_LEN,
> + buf);
> + if (ret < 0)
> + return ret;
> +
> + bec->rxerr = priv->can_idx ? buf->rec2 : buf->rec1;
> + bec->txerr = priv->can_idx ? buf->tec2 : buf->tec1;
> +
> + return 0;
> +}
> +
> +static int nuv_canfd_handle_lost_msg(struct net_device *ndev)
> +{
> + struct net_device_stats *stats = &ndev->stats;
> + struct sk_buff *skb;
> + struct can_frame *frame;
> +
> + netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
> +
> + stats->rx_errors++;
> + stats->rx_over_errors++;
> +
> + skb = alloc_can_err_skb(ndev, &frame);
> + if (unlikely(!skb))
> + return 0;
> +
> + frame->can_id |= CAN_ERR_CRTL;
> + frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> +
> + netif_receive_skb(skb);
> +
> + return 1;
> +}
> +
> +static void nuv_canfd_read_fifo(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->rx_buf;
> + struct net_device_stats *stats = &ndev->stats;
> + struct canfd_frame *cf;
> + struct sk_buff *skb;
> + int can_idx = priv->can_idx;
> + u32 id;
> + int ret;
> + u8 fd_format = 0;
> + int i;
> +
> + guard(mutex)(&priv->lock);
> +
> + ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD11_OFFSET(can_idx, 1),
> + NCT6694_CAN_CMD11_LEN, buf);
> + if (ret < 0)
> + return;
> +
> + /* Check type of frame and create skb */
> + fd_format = buf->flag & NCT6694_CAN_FLAG_FD;
> + if (fd_format)
> + skb = alloc_canfd_skb(ndev, &cf);
> + else
> + skb = alloc_can_skb(ndev, (struct can_frame **)&cf);
> +
> + if (!skb) {
> + stats->rx_dropped++;
> + return;
> + }
> +
> + cf->len = buf->dlc;
> +
> + /* Get ID and set flag by its type(Standard ID format or Ext ID format) */
> + id = le32_to_cpu(buf->id);
> + if (buf->flag & NCT6694_CAN_FLAG_EFF) {
> + /*
> + * In case the Extended ID frame is received, the standard
> + * and extended part of the ID are swapped in the register,
> + * so swap them back to obtain the correct ID.
> + */
> + id |= CAN_EFF_FLAG;
> + }
> +
> + cf->can_id = id;
> +
> + /* Set ESI flag */
> + if (buf->flag & NCT6694_CAN_FLAG_ERR) {
> + cf->flags |= CANFD_ESI;
> + netdev_dbg(ndev, "ESI Error\n");
> + }
> +
> + /* Set RTR and BRS */
> + if (!fd_format && (buf->flag & NCT6694_CAN_FLAG_RTR)) {
> + cf->can_id |= CAN_RTR_FLAG;
> + } else {
> + if (buf->flag & NCT6694_CAN_FLAG_BRS)
> + cf->flags |= CANFD_BRS;
> +
> + for (i = 0; i < cf->len; i++)
> + cf->data[i] = buf->data[i];
Use memcpy().
> + }
> +
> + /* Remove the packet from FIFO */
> + stats->rx_packets++;
> + stats->rx_bytes += cf->len;
Do not increment the rx_bytes if the frame is RTR.
> + netif_receive_skb(skb);
> +}
> +
> +static int nct6694_canfd_do_rx_poll(struct net_device *ndev, int quota)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
> + int can_idx = priv->can_idx;
> + u32 pkts = 0;
> + u8 mask_rx = NCT6694_CAN_EVENT_RX_EVT;
> + u8 rx_evt;
> +
> + for (;;) {
> + scoped_guard(mutex, &priv->lock) {
> + nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD2_OFFSET(can_idx, mask_rx),
> + NCT6694_CAN_CMD2_LEN, buf);
> +
> + rx_evt = can_idx ? buf->rx_evt2 : buf->rx_evt1;
> + }
> +
> + if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST)
> + nuv_canfd_handle_lost_msg(ndev);
> +
> + /* No data */
> + if ((rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) == 0)
> + break;
> +
> + if (quota <= 0)
> + break;
> +
> + nuv_canfd_read_fifo(ndev);
> + quota--;
> + pkts++;
> + }
> +
> + return pkts;
> +}
> +
> +static int nct6694_canfd_handle_lec_err(struct net_device *ndev, u8 bus_err)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> +
> + if (bus_err == NCT6694_CAN_EVT_NO_ERROR)
> + return 0;
> +
> + priv->can.can_stats.bus_error++;
> + stats->rx_errors++;
> +
> + /* Propagate the error condition to the CAN stack. */
> + skb = alloc_can_err_skb(ndev, &cf);
> +
> + if (unlikely(!skb))
> + return 0;
> +
> + /* Read the error counter register and check for new errors. */
> + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> + switch (bus_err) {
> + case NCT6694_CAN_EVT_CRC_ERROR:
> + cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
> + break;
> +
> + case NCT6694_CAN_EVT_STUFF_ERROR:
> + cf->data[2] |= CAN_ERR_PROT_STUFF;
> + break;
> +
> + case NCT6694_CAN_EVT_ACK_ERROR:
> + cf->data[3] = CAN_ERR_PROT_LOC_ACK;
> + break;
> +
> + case NCT6694_CAN_EVT_FORM_ERROR:
> + cf->data[2] |= CAN_ERR_PROT_FORM;
> + break;
> +
> + case NCT6694_CAN_EVT_BIT_ERROR:
> + cf->data[2] |= CAN_ERR_PROT_BIT |
> + CAN_ERR_PROT_BIT0 |
> + CAN_ERR_PROT_BIT1;
> + break;
> +
> + case NCT6694_CAN_EVT_TIMEOUT_ERROR:
> + cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> + break;
> +
> + case NCT6694_CAN_EVT_UNKNOWN_ERROR:
> + cf->data[2] |= CAN_ERR_PROT_UNSPEC;
> + /*
> + * It means 'unspecified'(the value is '0').
> + * But it is not sure if it's ok to send an error package
> + * without specific error bit.
> + */
> + break;
> +
> + default:
> + break;
> + }
> +
> + /* Reset the error counter, ack the IRQ and re-enable the counter. */
> + stats->rx_packets++;
> + stats->rx_bytes += cf->can_dlc;
> + netif_receive_skb(skb);
> +
> + return 1;
> +}
> +
> +static int nct6694_canfd_handle_state_change(struct net_device *ndev,
> + enum can_state new_state)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + struct can_frame *cf;
> + struct sk_buff *skb;
> + struct can_berr_counter bec;
> +
> + switch (new_state) {
> + case CAN_STATE_ERROR_ACTIVE:
> + /* error active state */
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> + break;
> + case CAN_STATE_ERROR_WARNING:
> + /* error warning state */
> + priv->can.can_stats.error_warning++;
> + priv->can.state = CAN_STATE_ERROR_WARNING;
> + break;
> + case CAN_STATE_ERROR_PASSIVE:
> + /* error passive state */
> + priv->can.can_stats.error_passive++;
> + priv->can.state = CAN_STATE_ERROR_PASSIVE;
> + break;
> + case CAN_STATE_BUS_OFF:
> + /* bus-off state */
> + priv->can.state = CAN_STATE_BUS_OFF;
> + priv->can.can_stats.bus_off++;
> + can_bus_off(ndev);
> + break;
> + default:
> + break;
> + }
> +
> + /* propagate the error condition to the CAN stack */
> + skb = alloc_can_err_skb(ndev, &cf);
> + if (unlikely(!skb))
> + return 0;
> +
> + nct6694_canfd_get_berr_counter(ndev, &bec);
> +
> + switch (new_state) {
> + case CAN_STATE_ERROR_WARNING:
> + /* error warning state */
Such comment can be removed. Here you are just paraphrasing the macro. I
can already see that CAN_STATE_ERROR_WARNING means the "error warning
state". The comments should add information.
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> + CAN_ERR_CRTL_RX_WARNING;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + break;
> + case CAN_STATE_ERROR_PASSIVE:
> + /* error passive state */
> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
> + if (bec.txerr > 127)
> + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
> + cf->data[6] = bec.txerr;
> + cf->data[7] = bec.rxerr;
> + break;
> + case CAN_STATE_BUS_OFF:
> + /* bus-off state */
> + cf->can_id |= CAN_ERR_BUSOFF;
> + break;
> + default:
> + break;
> + }
> +
> + stats->rx_packets++;
> + stats->rx_bytes += cf->can_dlc;
> + netif_receive_skb(skb);
> +
> + return 1;
> +}
> +
> +static int nct6694_canfd_handle_state_errors(struct net_device *ndev,
> + unsigned char can_status)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + int work_done = 0;
> +
> + if (can_status == NCT6694_CAN_EVT_ERROR_ACTIVE &&
> + priv->can.state != CAN_STATE_ERROR_ACTIVE) {
> + netdev_dbg(ndev, "Error, entered active state\n");
> + work_done += nct6694_canfd_handle_state_change(ndev,
> + CAN_STATE_ERROR_ACTIVE);
> + }
> +
> + if (can_status == NCT6694_CAN_EVT_WARNING &&
> + priv->can.state != CAN_STATE_ERROR_WARNING) {
> + netdev_dbg(ndev, "Error, entered warning state\n");
> + work_done += nct6694_canfd_handle_state_change(ndev,
> + CAN_STATE_ERROR_WARNING);
> + }
> +
> + if (can_status == NCT6694_CAN_EVT_ERROR_PASSIVE &&
> + priv->can.state != CAN_STATE_ERROR_PASSIVE) {
> + netdev_dbg(ndev, "Error, entered passive state\n");
> + work_done += nct6694_canfd_handle_state_change(ndev,
> + CAN_STATE_ERROR_PASSIVE);
> + }
> +
> + if (can_status == NCT6694_CAN_EVT_BUS_OFF &&
> + priv->can.state != CAN_STATE_BUS_OFF) {
> + netdev_dbg(ndev, "Error, entered bus-off state\n");
> + work_done += nct6694_canfd_handle_state_change(ndev,
> + CAN_STATE_BUS_OFF);
> + }
> +
> + return work_done;
> +}
> +
> +static int nct6694_canfd_poll(struct net_device *ndev, int quota)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
> + int can_idx = priv->can_idx;
> + int work_done = 0, ret;
> + u8 evt_mask = NCT6694_CAN_EVENT_ERR | NCT6694_CAN_EVENT_STATUS;
> + u8 bus_err, can_status;
> +
> + scoped_guard(mutex, &priv->lock) {
> + ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD2_OFFSET(can_idx, evt_mask),
> + NCT6694_CAN_CMD2_LEN, buf);
> + if (ret < 0)
> + return IRQ_NONE;
> +
> + if (can_idx) {
> + bus_err = buf->err2;
> + can_status = buf->status2;
> + } else {
> + bus_err = buf->err1;
> + can_status = buf->status1;
> + }
> + }
> +
> + /* Handle bus state changes */
> + work_done += nct6694_canfd_handle_state_errors(ndev, can_status);
> +
> + /* Handle lec errors on the bus */
> + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
> + work_done += nct6694_canfd_handle_lec_err(ndev, bus_err);
> +
> + /* Handle RX events */
> + work_done += nct6694_canfd_do_rx_poll(ndev, quota - work_done);
> + return work_done;
> +}
> +
> +static void nct6694_canfd_tx_irq(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> +
> + guard(mutex)(&priv->lock);
> + stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
> + stats->tx_packets++;
> + priv->tx_busy = false;
> + netif_wake_queue(ndev);
> +}
> +
> +static irqreturn_t nct6694_canfd_irq(int irq, void *data)
> +{
> + struct net_device *ndev = data;
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd2 *buf = (struct nct6694_can_cmd2 *)priv->rx_buf;
> + int can_idx = priv->can_idx;
> + int ret;
> + u8 mask_sts = NCT6694_CAN_EVENT_TX_EVT;
> + u8 tx_evt;
> +
> + scoped_guard(mutex, &priv->lock) {
> + ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD2_OFFSET(can_idx, mask_sts),
> + NCT6694_CAN_CMD2_LEN, buf);
> + if (ret < 0)
> + return IRQ_NONE;
> +
> + tx_evt = can_idx ? buf->tx_evt2 : buf->tx_evt1;
> + }
> +
> + if (tx_evt) {
> + nct6694_canfd_tx_irq(ndev);
> + } else {
> + ret = nct6694_canfd_poll(ndev, NCT6694_CAN_RX_QUOTA);
> + if (!ret)
> + return IRQ_NONE;
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int nct6694_canfd_start(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd0 *buf = (struct nct6694_can_cmd0 *)priv->tx_buf;
Give a more memorable name to buf, for example: bittiming_cmd.
> + const struct can_bittiming *n_bt = &priv->can.bittiming;
> + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> + int ret;
> +
> + guard(mutex)(&priv->lock);
> +
> + memset(priv->tx_buf, 0, NCT6694_CAN_CMD0_LEN);
Remove those CMD*_LEN macros, instead, use sizeof() of your structures.
memset(buf, 0, sizeof(*buf));
> + buf->nbr = n_bt->bitrate;
> + buf->dbr = d_bt->bitrate;
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> + buf->ctrl1 |= NCT6694_CAN_CTRL1_MON;
> +
> + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
> + priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> + buf->ctrl1 |= NCT6694_CAN_CTRL1_NISO;
> +
> + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> + buf->ctrl1 |= NCT6694_CAN_CTRL1_LBCK;
> +
> + ret = nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD0_OFFSET(priv->can_idx),
> + NCT6694_CAN_CMD0_LEN, buf);
> + if (ret < 0)
> + return ret;
> +
> + priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> + return 0;
> +}
> +
> +static int nct6694_canfd_stop(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +
> + netif_stop_queue(ndev);
> + free_irq(ndev->irq, ndev);
> + destroy_workqueue(priv->wq);
> + priv->wq = NULL;
> + nct6694_canfd_clean(ndev);
> + priv->can.state = CAN_STATE_STOPPED;
> + close_candev(ndev);
> +
> + return 0;
> +}
> +
> +static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> + switch (mode) {
> + case CAN_MODE_START:
> + nct6694_canfd_clean(ndev);
> + nct6694_canfd_start(ndev);
> + netif_wake_queue(ndev);
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int nct6694_canfd_open(struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + int ret;
> +
> + ret = open_candev(ndev);
> + if (ret)
> + return ret;
> +
> + ret = request_threaded_irq(ndev->irq, NULL,
> + nct6694_canfd_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;
> + }
> +
> + priv->tx_skb = NULL;
> + priv->tx_busy = false;
> +
> + ret = nct6694_canfd_start(ndev);
> + if (ret)
> + goto destroy_wq;
> +
> + netif_start_queue(ndev);
> +
> + return 0;
> +
> +destroy_wq:
> + destroy_workqueue(priv->wq);
> +free_irq:
> + free_irq(ndev->irq, ndev);
> +close_candev:
> + close_candev(ndev);
> + return ret;
> +}
> +
> +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> +
> + if (priv->tx_skb || priv->tx_busy) {
> + netdev_err(ndev, "hard_xmit called while tx busy\n");
> + return NETDEV_TX_BUSY;
> + }
> +
> + if (can_dev_dropped_skb(ndev, skb))
> + return NETDEV_TX_OK;
> +
> + netif_stop_queue(ndev);
Here, you are inconditionally stopping the queue. Does it mean that your
device is only able to manage one CAN frame at the time?
> + priv->tx_skb = skb;
> + queue_work(priv->wq, &priv->tx_work);
> +
> + return NETDEV_TX_OK;
> +}
> +
> +static void nct6694_canfd_tx(struct net_device *ndev, struct canfd_frame *cf)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->tx_buf;
> + u32 txid = 0;
> + int i;
> +
> + memset(buf, 0, NCT6694_CAN_CMD10_LEN);
> +
> + if (priv->can_idx == 0)
> + buf->tag = NCT6694_CAN_TAG_CAN0;
> + else
> + buf->tag = NCT6694_CAN_TAG_CAN1;
> +
> + if (cf->can_id & CAN_EFF_FLAG) {
> + txid = cf->can_id & CAN_EFF_MASK;
> + /*
> + * In case the Extended ID frame is transmitted, the
> + * standard and extended part of the ID are swapped
> + * in the register, so swap them back to send the
> + * correct ID.
> + */
> + buf->flag |= NCT6694_CAN_FLAG_EFF;
> + } else {
> + txid = cf->can_id & CAN_SFF_MASK;
> + }
> +
> + buf->id = cpu_to_le32(txid);
> + buf->dlc = cf->len;
> +
> + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
> + can_is_canfd_skb(priv->tx_skb)) {
> + buf->flag |= NCT6694_CAN_FLAG_FD;
> + if (cf->flags & CANFD_BRS)
> + buf->flag |= NCT6694_CAN_FLAG_BRS;
> + }
> +
> + if (cf->can_id & CAN_RTR_FLAG)
> + buf->flag |= NCT6694_CAN_FLAG_RTR;
> +
> + /* set data to buf */
> + for (i = 0; i < cf->len; i++)
> + buf->data[i] = cf->data[i];
> +
> + nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD10_OFFSET(1),
> + NCT6694_CAN_CMD10_LEN,
> + buf);
> +}
> +
> +static void nct6694_canfd_tx_work(struct work_struct *work)
> +{
> + struct nct6694_canfd_priv *priv = container_of(work,
> + struct nct6694_canfd_priv,
> + tx_work);
> + struct net_device *ndev = priv->ndev;
> + struct canfd_frame *cf;
> +
> + guard(mutex)(&priv->lock);
> +
> + if (priv->tx_skb) {
> + if (priv->can.state == CAN_STATE_BUS_OFF) {
> + nct6694_canfd_clean(ndev);
> + } else {
> + cf = (struct canfd_frame *)priv->tx_skb->data;
> + nct6694_canfd_tx(ndev, cf);
> + priv->tx_busy = true;
> + can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
> + priv->tx_skb = NULL;
> + }
> + }
> +}
> +
> +static const struct net_device_ops nct6694_canfd_netdev_ops = {
> + .ndo_open = nct6694_canfd_open,
> + .ndo_stop = nct6694_canfd_stop,
> + .ndo_start_xmit = nct6694_canfd_start_xmit,
> + .ndo_change_mtu = can_change_mtu,
> +};
Also add a struct ethtool_ops for the default timestamps:
static const struct ethtool_ops nct6694_ethtool_ops = {
.get_ts_info = ethtool_op_get_ts_info,
};
This assumes that your device does not support hardware timestamps. If
you do have hardware timestamping support, please adjust accordingly.
> +static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv)
> +{
> + struct nct6694_can_cmd1 *buf = (struct nct6694_can_cmd1 *)priv->rx_buf;
> + int ret;
> +
> + ret = nct6694_read_msg(priv->nct6694, NCT6694_CAN_MOD,
> + NCT6694_CAN_CMD1_OFFSET,
> + NCT6694_CAN_CMD1_LEN,
> + buf);
> + if (ret)
> + return ret;
> +
> + return le32_to_cpu(buf->can_clk);
> +}
> +
> +static int nct6694_canfd_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_canfd_priv *priv;
> + struct net_device *ndev;
> + int ret, irq, can_clk;
> +
> + irq = irq_create_mapping(nct6694->domain,
> + NCT6694_IRQ_CAN1 + cell->id);
> + if (!irq)
> + return -EINVAL;
> +
> + ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
> + if (!ndev)
> + return -ENOMEM;
> +
> + ndev->irq = irq;
> + ndev->flags |= IFF_ECHO;
> + ndev->netdev_ops = &nct6694_canfd_netdev_ops;
> +
> + priv = netdev_priv(ndev);
> + priv->nct6694 = nct6694;
> + priv->ndev = ndev;
> +
> + priv->tx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!priv->tx_buf) {
> + ret = -ENOMEM;
> + goto free_candev;
> + }
> +
> + priv->rx_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!priv->rx_buf) {
> + ret = -ENOMEM;
> + goto free_candev;
> + }
> +
> + can_clk = nct6694_canfd_get_clock(priv);
> + if (can_clk < 0) {
> + ret = -EIO;
> + goto free_candev;
> + }
> +
> + mutex_init(&priv->lock);
> + INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
> +
> + priv->can_idx = cell->id;
> + priv->can.state = CAN_STATE_STOPPED;
> + priv->can.clock.freq = can_clk;
> + priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const;
> + priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const;
> + priv->can.do_set_mode = nct6694_canfd_set_mode;
> + priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter;
> +
> + priv->can.ctrlmode = CAN_CTRLMODE_FD;
> +
> + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
> + CAN_CTRLMODE_LISTENONLY |
> + CAN_CTRLMODE_FD |
> + CAN_CTRLMODE_FD_NON_ISO |
> + CAN_CTRLMODE_BERR_REPORTING;
> +
> + platform_set_drvdata(pdev, priv);
> + SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> +
> + ret = register_candev(priv->ndev);
> + if (ret)
> + goto free_candev;
> +
> + return 0;
> +
> +free_candev:
> + free_candev(ndev);
> + return dev_err_probe(&pdev->dev, ret, "Probe failed\n");
> +}
> +
> +static void nct6694_canfd_remove(struct platform_device *pdev)
> +{
> + struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
> +
> + cancel_work_sync(&priv->tx_work);
> + unregister_candev(priv->ndev);
> + free_candev(priv->ndev);
> +}
> +
> +static struct platform_driver nct6694_canfd_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_canfd_probe,
> + .remove = nct6694_canfd_remove,
> +};
> +
> +module_platform_driver(nct6694_canfd_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
Yours sincerely,
Vincent Mailhol
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support
2024-11-21 7:47 ` Vincent Mailhol
@ 2024-11-22 8:03 ` Ming Yu
2024-11-22 8:18 ` Vincent Mailhol
0 siblings, 1 reply; 20+ messages in thread
From: Ming Yu @ 2024-11-22 8:03 UTC (permalink / raw)
To: Vincent Mailhol
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, 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
Dear Vincent,
Thank you for your comments,
Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年11月21日 週四 下午3:47寫道:
> > +
> > +struct __packed nct6694_can_cmd0 {
>
> Give more meaningfull names to your structures. For example:
>
> /* cmd1 */
> struct __packed nct6694_can_bittiming
>
Understood. I will make the modifications in v3.
> > + u32 nbr;
> > + u32 dbr;
> > + u32 active:8;
> > + u32 reserved:24;
> > + u16 ctrl1;
> > + u16 ctrl2;
> > + u32 nbtp;
> > + u32 dbtp;
> > +};
> Always use the specific endianess types in the structures that you are
> sending to the device. e.g. replace u32 by __le32 (assuming little endian).
>
Okay, I'll fix it in v3.
> > +struct __packed nct6694_can_cmd1 {
...
> > +
> > +struct nct6694_canfd_priv {
>
> Be consistent in your name space. Sometime you prefix your names with
> nct6694_can and sometimes with nct6694_canfd for no apparent reasons.
>
Understood. I will make the modifications in v3.
> > + struct can_priv can; /* must be the first member */
...
> > + } else {
> > + if (buf->flag & NCT6694_CAN_FLAG_BRS)
> > + cf->flags |= CANFD_BRS;
> > +
> > + for (i = 0; i < cf->len; i++)
> > + cf->data[i] = buf->data[i];
>
> Use memcpy().
>
Okay, I'll fix it in v3.
> > + }
> > +
> > + /* Remove the packet from FIFO */
> > + stats->rx_packets++;
> > + stats->rx_bytes += cf->len;
>
> Do not increment the rx_bytes if the frame is RTR.
>
Okay, I'll fix it in v3.
> > + netif_receive_skb(skb);
...
> > +
> > + switch (new_state) {
> > + case CAN_STATE_ERROR_WARNING:
> > + /* error warning state */
>
> Such comment can be removed. Here you are just paraphrasing the macro. I
> can already see that CAN_STATE_ERROR_WARNING means the "error warning
> state". The comments should add information.
>
Okay, I will drop them in v3.
> > + cf->can_id |= CAN_ERR_CRTL;
> > + cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> > + CAN_ERR_CRTL_RX_WARNING;
...
> > +static int nct6694_canfd_start(struct net_device *ndev)
> > +{
> > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > + struct nct6694_can_cmd0 *buf = (struct nct6694_can_cmd0 *)priv->tx_buf;
>
> Give a more memorable name to buf, for example: bittiming_cmd.
>
Got it. So, every buf that uses a command must be renamed similarly, right?
> > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > + int ret;
> > +
> > + guard(mutex)(&priv->lock);
> > +
> > + memset(priv->tx_buf, 0, NCT6694_CAN_CMD0_LEN);
>
> Remove those CMD*_LEN macros, instead, use sizeof() of your structures.
>
> memset(buf, 0, sizeof(*buf));
>
Understood. I will make the modifications in v3.
> > + buf->nbr = n_bt->bitrate;
> > + buf->dbr = d_bt->bitrate;
...
> > +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> > + struct net_device *ndev)
> > +{
> > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > +
> > + if (priv->tx_skb || priv->tx_busy) {
> > + netdev_err(ndev, "hard_xmit called while tx busy\n");
> > + return NETDEV_TX_BUSY;
> > + }
> > +
> > + if (can_dev_dropped_skb(ndev, skb))
> > + return NETDEV_TX_OK;
> > +
> > + netif_stop_queue(ndev);
>
> Here, you are inconditionally stopping the queue. Does it mean that your
> device is only able to manage one CAN frame at the time?
>
Yes, we designed it to manage a single CAN frame, so we stop the queue
here until a TX event is received to wake queue.
But It seems I lost the error handling code for the tx command in
nct6694_canfd_tx(), I will fix it in the next patch.
> > + priv->tx_skb = skb;
> > + queue_work(priv->wq, &priv->tx_work);
> > +
> > + return NETDEV_TX_OK;
> > +}
> > +
> > +static void nct6694_canfd_tx(struct net_device *ndev, struct canfd_frame *cf)
> > +{
> > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > + struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->tx_buf;
> > + u32 txid = 0;
> > + int i;
...
> > +
> > + /* set data to buf */
> > + for (i = 0; i < cf->len; i++)
> > + buf->data[i] = cf->data[i];
> > +
> > + nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> > + NCT6694_CAN_CMD10_OFFSET(1),
> > + NCT6694_CAN_CMD10_LEN,
> > + buf);
I will add the error handling to wake the queue in the next patch.
> > +}
> > +
...
> > +static const struct net_device_ops nct6694_canfd_netdev_ops = {
> > + .ndo_open = nct6694_canfd_open,
> > + .ndo_stop = nct6694_canfd_stop,
> > + .ndo_start_xmit = nct6694_canfd_start_xmit,
> > + .ndo_change_mtu = can_change_mtu,
> > +};
>
> Also add a struct ethtool_ops for the default timestamps:
>
> static const struct ethtool_ops nct6694_ethtool_ops = {
> .get_ts_info = ethtool_op_get_ts_info,
> };
>
> This assumes that your device does not support hardware timestamps. If
> you do have hardware timestamping support, please adjust accordingly.
>
Understood. I will make the modifications in v3.
Best regards,
Ming
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support
2024-11-22 8:03 ` Ming Yu
@ 2024-11-22 8:18 ` Vincent Mailhol
2024-11-22 9:51 ` Ming Yu
0 siblings, 1 reply; 20+ messages in thread
From: Vincent Mailhol @ 2024-11-22 8:18 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, 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
On Fri. 22 Nov. 2024 at 17:05, Ming Yu <a0282524688@gmail.com> wrote:
> Dear Vincent,
>
> Thank you for your comments,
>
> Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年11月21日 週四 下午3:47寫道:
> > > +
> > > +struct __packed nct6694_can_cmd0 {
> >
> > Give more meaningfull names to your structures. For example:
> >
> > /* cmd1 */
> > struct __packed nct6694_can_bittiming
> >
>
> Understood. I will make the modifications in v3.
>
> > > + u32 nbr;
> > > + u32 dbr;
> > > + u32 active:8;
> > > + u32 reserved:24;
> > > + u16 ctrl1;
> > > + u16 ctrl2;
> > > + u32 nbtp;
> > > + u32 dbtp;
> > > +};
> > Always use the specific endianess types in the structures that you are
> > sending to the device. e.g. replace u32 by __le32 (assuming little endian).
> >
>
> Okay, I'll fix it in v3.
>
> > > +struct __packed nct6694_can_cmd1 {
> ...
> > > +
> > > +struct nct6694_canfd_priv {
> >
> > Be consistent in your name space. Sometime you prefix your names with
> > nct6694_can and sometimes with nct6694_canfd for no apparent reasons.
> >
>
> Understood. I will make the modifications in v3.
>
> > > + struct can_priv can; /* must be the first member */
> ...
> > > + } else {
> > > + if (buf->flag & NCT6694_CAN_FLAG_BRS)
> > > + cf->flags |= CANFD_BRS;
> > > +
> > > + for (i = 0; i < cf->len; i++)
> > > + cf->data[i] = buf->data[i];
> >
> > Use memcpy().
> >
>
> Okay, I'll fix it in v3.
>
> > > + }
> > > +
> > > + /* Remove the packet from FIFO */
> > > + stats->rx_packets++;
> > > + stats->rx_bytes += cf->len;
> >
> > Do not increment the rx_bytes if the frame is RTR.
> >
>
> Okay, I'll fix it in v3.
>
> > > + netif_receive_skb(skb);
> ...
> > > +
> > > + switch (new_state) {
> > > + case CAN_STATE_ERROR_WARNING:
> > > + /* error warning state */
> >
> > Such comment can be removed. Here you are just paraphrasing the macro. I
> > can already see that CAN_STATE_ERROR_WARNING means the "error warning
> > state". The comments should add information.
> >
>
> Okay, I will drop them in v3.
>
> > > + cf->can_id |= CAN_ERR_CRTL;
> > > + cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING :
> > > + CAN_ERR_CRTL_RX_WARNING;
> ...
> > > +static int nct6694_canfd_start(struct net_device *ndev)
> > > +{
> > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > + struct nct6694_can_cmd0 *buf = (struct nct6694_can_cmd0 *)priv->tx_buf;
> >
> > Give a more memorable name to buf, for example: bittiming_cmd.
> >
>
> Got it. So, every buf that uses a command must be renamed similarly, right?
Yes. Note that you can use different names if you have a better idea.
It is just that generic names like "buf" or "cmd1" do not tell me what
this variable actually is. On the contrary, bittiming_cmd tells me
that this variable holds the payload for the command to set the device
bittiming. This way, suddenly, the code becomes easier to read and
understand. As long as your naming conveys this kind of information,
then I am fine with whatever you choose, just avoid the "buf" or
"cmd1" names.
> > > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> > > + int ret;
> > > +
> > > + guard(mutex)(&priv->lock);
> > > +
> > > + memset(priv->tx_buf, 0, NCT6694_CAN_CMD0_LEN);
> >
> > Remove those CMD*_LEN macros, instead, use sizeof() of your structures.
> >
> > memset(buf, 0, sizeof(*buf));
> >
>
> Understood. I will make the modifications in v3.
>
> > > + buf->nbr = n_bt->bitrate;
> > > + buf->dbr = d_bt->bitrate;
> ...
> > > +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> > > + struct net_device *ndev)
> > > +{
> > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > +
> > > + if (priv->tx_skb || priv->tx_busy) {
> > > + netdev_err(ndev, "hard_xmit called while tx busy\n");
> > > + return NETDEV_TX_BUSY;
> > > + }
> > > +
> > > + if (can_dev_dropped_skb(ndev, skb))
> > > + return NETDEV_TX_OK;
> > > +
> > > + netif_stop_queue(ndev);
> >
> > Here, you are inconditionally stopping the queue. Does it mean that your
> > device is only able to manage one CAN frame at the time?
> >
>
> Yes, we designed it to manage a single CAN frame, so we stop the queue
> here until a TX event is received to wake queue.
Do you mean that the device is designed to manage only one single CAN
frame or is the driver designed to only manage one single CAN frame?
If the device is capable of handling several CAN frames, your driver
should take advantage of this. Else the driver will slow down the
communication a lot whenever packets start to accumulate in the TX
queue.
> But It seems I lost the error handling code for the tx command in
> nct6694_canfd_tx(), I will fix it in the next patch.
>
> > > + priv->tx_skb = skb;
> > > + queue_work(priv->wq, &priv->tx_work);
> > > +
> > > + return NETDEV_TX_OK;
> > > +}
> > > +
> > > +static void nct6694_canfd_tx(struct net_device *ndev, struct canfd_frame *cf)
> > > +{
> > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > + struct nct6694_can_cmd10_11 *buf = (struct nct6694_can_cmd10_11 *)priv->tx_buf;
> > > + u32 txid = 0;
> > > + int i;
> ...
> > > +
> > > + /* set data to buf */
> > > + for (i = 0; i < cf->len; i++)
> > > + buf->data[i] = cf->data[i];
> > > +
> > > + nct6694_write_msg(priv->nct6694, NCT6694_CAN_MOD,
> > > + NCT6694_CAN_CMD10_OFFSET(1),
> > > + NCT6694_CAN_CMD10_LEN,
> > > + buf);
>
> I will add the error handling to wake the queue in the next patch.
>
> > > +}
> > > +
> ...
> > > +static const struct net_device_ops nct6694_canfd_netdev_ops = {
> > > + .ndo_open = nct6694_canfd_open,
> > > + .ndo_stop = nct6694_canfd_stop,
> > > + .ndo_start_xmit = nct6694_canfd_start_xmit,
> > > + .ndo_change_mtu = can_change_mtu,
> > > +};
> >
> > Also add a struct ethtool_ops for the default timestamps:
> >
> > static const struct ethtool_ops nct6694_ethtool_ops = {
> > .get_ts_info = ethtool_op_get_ts_info,
> > };
> >
> > This assumes that your device does not support hardware timestamps. If
> > you do have hardware timestamping support, please adjust accordingly.
> >
>
> Understood. I will make the modifications in v3.
>
> Best regards,
> Ming
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support
2024-11-22 8:18 ` Vincent Mailhol
@ 2024-11-22 9:51 ` Ming Yu
0 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-22 9:51 UTC (permalink / raw)
To: Vincent Mailhol
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, 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
Dear Vincent,
Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2024年11月22日 週五 下午4:25寫道:
> > > > +static int nct6694_canfd_start(struct net_device *ndev)
> > > > +{
> > > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > > + struct nct6694_can_cmd0 *buf = (struct nct6694_can_cmd0 *)priv->tx_buf;
> > >
> > > Give a more memorable name to buf, for example: bittiming_cmd.
> > >
> >
> > Got it. So, every buf that uses a command must be renamed similarly, right?
>
> Yes. Note that you can use different names if you have a better idea.
> It is just that generic names like "buf" or "cmd1" do not tell me what
> this variable actually is. On the contrary, bittiming_cmd tells me
> that this variable holds the payload for the command to set the device
> bittiming. This way, suddenly, the code becomes easier to read and
> understand. As long as your naming conveys this kind of information,
> then I am fine with whatever you choose, just avoid the "buf" or
> "cmd1" names.
>
Understood. I will make the modifications in v3.
> > > > + const struct can_bittiming *n_bt = &priv->can.bittiming;
> > > > + const struct can_bittiming *d_bt = &priv->can.data_bittiming;
...
> > > > +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> > > > + struct net_device *ndev)
> > > > +{
> > > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > > +
> > > > + if (priv->tx_skb || priv->tx_busy) {
> > > > + netdev_err(ndev, "hard_xmit called while tx busy\n");
> > > > + return NETDEV_TX_BUSY;
> > > > + }
> > > > +
> > > > + if (can_dev_dropped_skb(ndev, skb))
> > > > + return NETDEV_TX_OK;
> > > > +
> > > > + netif_stop_queue(ndev);
> > >
> > > Here, you are inconditionally stopping the queue. Does it mean that your
> > > device is only able to manage one CAN frame at the time?
> > >
> >
> > Yes, we designed it to manage a single CAN frame, so we stop the queue
> > here until a TX event is received to wake queue.
>
> Do you mean that the device is designed to manage only one single CAN
> frame or is the driver designed to only manage one single CAN frame?
> If the device is capable of handling several CAN frames, your driver
> should take advantage of this. Else the driver will slow down the
> communication a lot whenever packets start to accumulate in the TX
> queue.
>
I understand, but our device currently supports handling only one CAN frame.
Best regards,
Ming
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (3 preceding siblings ...)
2024-11-21 6:40 ` [PATCH v2 4/7] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 14:15 ` Guenter Roeck
2024-11-21 6:40 ` [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-11-21 6:40 ` [PATCH v2 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
6 siblings, 1 reply; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
This driver supports Watchdog timer functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/watchdog/Kconfig | 11 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/nct6694_wdt.c | 277 +++++++++++++++++++++++++++++++++
4 files changed, 290 insertions(+)
create mode 100644 drivers/watchdog/nct6694_wdt.c
diff --git a/MAINTAINERS b/MAINTAINERS
index eb5d46825e71..496fe7d5a23f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16549,6 +16549,7 @@ F: drivers/gpio/gpio-nct6694.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/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 2333476a42c0..851c1f17712d 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -739,6 +739,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
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to watchdog timer.
+
+ 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 386d88d89fe5..8355893b4435 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..7d2c8d5c6fa3
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,277 @@
+// 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
+
+/* Host interface */
+#define NCT6694_WDT_MOD 0x07
+
+/* Message Channel*/
+/* Command 00h */
+#define NCT6694_WDT_CMD0_LEN 0x0F
+#define NCT6694_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */
+
+/* Command 01h */
+#define NCT6694_WDT_CMD1_LEN 0x08
+#define NCT6694_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */
+
+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_cmd0 {
+ __le32 pretimeout;
+ __le32 timeout;
+ u8 owner;
+ u8 scratch;
+ u8 control;
+ u8 status;
+ __le32 countdown;
+};
+
+struct __packed nct6694_wdt_cmd1 {
+ u32 wdt_cmd;
+ u32 reserved;
+};
+
+struct nct6694_wdt_data {
+ struct watchdog_device wdev;
+ struct device *dev;
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ unsigned char *xmit_buf;
+ 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_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
+ struct nct6694 *nct6694 = data->nct6694;
+ 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(buf, 0, NCT6694_WDT_CMD0_LEN);
+ buf->timeout = cpu_to_le32(timeout_fmt);
+ buf->pretimeout = cpu_to_le32(pretimeout_fmt);
+
+ return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+ NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
+ NCT6694_WDT_CMD0_LEN, buf);
+}
+
+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_info(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_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ guard(mutex)(&data->lock);
+
+ memcpy(buf, "WDTC", 4);
+ buf->reserved = 0;
+
+ return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+ NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
+ NCT6694_WDT_CMD1_LEN, buf);
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ guard(mutex)(&data->lock);
+ memcpy(buf, "WDTS", 4);
+ buf->reserved = 0;
+
+ return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
+ NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
+ NCT6694_WDT_CMD1_LEN, buf);
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int timeout)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ int ret;
+
+ if (timeout < wdev->pretimeout) {
+ dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+ wdev->pretimeout = 0;
+ }
+
+ ret = nct6694_wdt_setting(wdev, timeout, NCT6694_ACTION_GPO,
+ wdev->pretimeout, NCT6694_ACTION_GPO);
+ if (ret)
+ return ret;
+
+ wdev->timeout = timeout;
+
+ return ret;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+ unsigned int pretimeout)
+{
+ int ret;
+
+ ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+ pretimeout, NCT6694_ACTION_GPO);
+ if (ret)
+ return ret;
+
+ wdev->pretimeout = pretimeout;
+
+ return ret;
+}
+
+static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694_wdt_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned int timeleft_ms;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(nct6694, NCT6694_WDT_MOD,
+ NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
+ NCT6694_WDT_CMD0_LEN, buf);
+ if (ret)
+ return ret;
+
+ timeleft_ms = le32_to_cpu(buf->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;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->xmit_buf = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!data->xmit_buf)
+ 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;
+ wdev->min_timeout = 1;
+ wdev->max_timeout = 255;
+
+ mutex_init(&data->lock);
+
+ platform_set_drvdata(pdev, data);
+
+ /* Register watchdog timer device to WDT framework */
+ watchdog_set_drvdata(&data->wdev, data);
+ watchdog_init_timeout(&data->wdev, timeout, dev);
+ 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");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support
2024-11-21 6:40 ` [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-11-21 14:15 ` Guenter Roeck
2024-11-22 8:11 ` Ming Yu
0 siblings, 1 reply; 20+ messages in thread
From: Guenter Roeck @ 2024-11-21 14:15 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc
On 11/20/24 22:40, Ming Yu wrote:
> This driver supports Watchdog timer functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> MAINTAINERS | 1 +
> drivers/watchdog/Kconfig | 11 ++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/nct6694_wdt.c | 277 +++++++++++++++++++++++++++++++++
> 4 files changed, 290 insertions(+)
> create mode 100644 drivers/watchdog/nct6694_wdt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index eb5d46825e71..496fe7d5a23f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16549,6 +16549,7 @@ F: drivers/gpio/gpio-nct6694.c
> F: drivers/i2c/busses/i2c-nct6694.c
> F: drivers/mfd/nct6694.c
> F: drivers/net/can/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 2333476a42c0..851c1f17712d 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -739,6 +739,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
> + If you say yes to this option, support will be included for Nuvoton
> + NCT6694, a USB device to watchdog timer.
> +
It is a peripheral expander, not a "USB device to watchdog timer". Watchdog is only
a small part of its 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 386d88d89fe5..8355893b4435 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..7d2c8d5c6fa3
> --- /dev/null
> +++ b/drivers/watchdog/nct6694_wdt.c
> @@ -0,0 +1,277 @@
> +// 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
> +
> +/* Host interface */
> +#define NCT6694_WDT_MOD 0x07
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define NCT6694_WDT_CMD0_LEN 0x0F
> +#define NCT6694_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */
> +
> +/* Command 01h */
> +#define NCT6694_WDT_CMD1_LEN 0x08
> +#define NCT6694_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */
> +
> +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_cmd0 {
> + __le32 pretimeout;
> + __le32 timeout;
> + u8 owner;
> + u8 scratch;
> + u8 control;
> + u8 status;
> + __le32 countdown;
> +};
> +
> +struct __packed nct6694_wdt_cmd1 {
> + u32 wdt_cmd;
> + u32 reserved;
> +};
> +
> +struct nct6694_wdt_data {
> + struct watchdog_device wdev;
> + struct device *dev;
> + struct nct6694 *nct6694;
> + struct mutex lock;
> + unsigned char *xmit_buf;
> + 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_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
> + struct nct6694 *nct6694 = data->nct6694;
> + 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(buf, 0, NCT6694_WDT_CMD0_LEN);
> + buf->timeout = cpu_to_le32(timeout_fmt);
> + buf->pretimeout = cpu_to_le32(pretimeout_fmt);
> +
> + return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
> + NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
> + NCT6694_WDT_CMD0_LEN, buf);
> +}
> +
> +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_info(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
> + data->wdev_idx, wdev->timeout, wdev->pretimeout);
> +
This is logging noise. Drop or set as debug message.
> + return ret;
> +}
> +
> +static int nct6694_wdt_stop(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694_wdt_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
> + struct nct6694 *nct6694 = data->nct6694;
> +
> + guard(mutex)(&data->lock);
> +
> + memcpy(buf, "WDTC", 4);
> + buf->reserved = 0;
> +
> + return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
> + NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
> + NCT6694_WDT_CMD1_LEN, buf);
> +}
> +
> +static int nct6694_wdt_ping(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694_wdt_cmd1 *buf = (struct nct6694_wdt_cmd1 *)data->xmit_buf;
> + struct nct6694 *nct6694 = data->nct6694;
> +
> + guard(mutex)(&data->lock);
> + memcpy(buf, "WDTS", 4);
> + buf->reserved = 0;
> +
> + return nct6694_write_msg(nct6694, NCT6694_WDT_MOD,
> + NCT6694_WDT_CMD1_OFFSET(data->wdev_idx),
> + NCT6694_WDT_CMD1_LEN, buf);
> +}
> +
> +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
> + unsigned int timeout)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + int ret;
> +
> + if (timeout < wdev->pretimeout) {
> + dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
> + wdev->pretimeout = 0;
> + }
> +
This is only necessary if the pretimeout was not validated during probe
since otherwise the watchdog core does the check. Please validate it there.
> + ret = nct6694_wdt_setting(wdev, timeout, NCT6694_ACTION_GPO,
> + wdev->pretimeout, NCT6694_ACTION_GPO);
> + if (ret)
> + return ret;
> +
> + wdev->timeout = timeout;
> +
> + return ret;
ret == 0 here, so return 0.
> +}
> +
> +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
> + unsigned int pretimeout)
> +{
> + int ret;
> +
> + ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
> + pretimeout, NCT6694_ACTION_GPO);
> + if (ret)
> + return ret;
> +
> + wdev->pretimeout = pretimeout;
> +
> + return ret;
ret == 0 here, so 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_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned int timeleft_ms;
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + ret = nct6694_read_msg(nct6694, NCT6694_WDT_MOD,
> + NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
> + NCT6694_WDT_CMD0_LEN, buf);
> + if (ret)
> + return ret;
The function does not return an error code. Return 0 instead.
> +
> + timeleft_ms = le32_to_cpu(buf->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;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->xmit_buf = devm_kcalloc(dev, NCT6694_MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!data->xmit_buf)
> + 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;
pretimeout should be validated here.
> + wdev->min_timeout = 1;
> + wdev->max_timeout = 255;
> +
> + mutex_init(&data->lock);
> +
> + platform_set_drvdata(pdev, data);
> +
> + /* Register watchdog timer device to WDT framework */
> + watchdog_set_drvdata(&data->wdev, data);
> + watchdog_init_timeout(&data->wdev, timeout, dev);
> + 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");
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support
2024-11-21 14:15 ` Guenter Roeck
@ 2024-11-22 8:11 ` Ming Yu
0 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-22 8:11 UTC (permalink / raw)
To: Guenter Roeck
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-rtc
Dear Guenter,
Thank you for your comments,
Guenter Roeck <linux@roeck-us.net> 於 2024年11月21日 週四 下午10:15寫道:
> > +config NCT6694_WATCHDOG
> > + tristate "Nuvoton NCT6694 watchdog support"
> > + depends on MFD_NCT6694
> > + select WATCHDOG_CORE
> > + help
> > + If you say yes to this option, support will be included for Nuvoton
> > + NCT6694, a USB device to watchdog timer.
> > +
>
> It is a peripheral expander, not a "USB device to watchdog timer". Watchdog is only
> a small part of its functionality.
>
Understood. I will make the modifications in v3.
> > + This driver can also be built as a module. If so, the module will
> > + be called nct6694_wdt.
...
> > + ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
> > + wdev->pretimeout, NCT6694_ACTION_GPO);
> > + if (ret)
> > + return ret;
> > +
> > + dev_info(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
> > + data->wdev_idx, wdev->timeout, wdev->pretimeout);
> > +
>
> This is logging noise. Drop or set as debug message.
>
Okay, I'll drop it in v3.
> > + return ret;
> > +}
...
> > +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
> > + unsigned int timeout)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + int ret;
> > +
> > + if (timeout < wdev->pretimeout) {
> > + dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
> > + wdev->pretimeout = 0;
> > + }
> > +
> This is only necessary if the pretimeout was not validated during probe
> since otherwise the watchdog core does the check. Please validate it there.
>
Understood. I will make the modifications in v3.
> > + ret = nct6694_wdt_setting(wdev, timeout, NCT6694_ACTION_GPO,
> > + wdev->pretimeout, NCT6694_ACTION_GPO);
> > + if (ret)
> > + return ret;
> > +
> > + wdev->timeout = timeout;
> > +
> > + return ret;
>
> ret == 0 here, so return 0.
>
Okay, fix it in v3.
> > +}
> > +
> > +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
> > + unsigned int pretimeout)
> > +{
> > + int ret;
> > +
> > + ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
> > + pretimeout, NCT6694_ACTION_GPO);
> > + if (ret)
> > + return ret;
> > +
> > + wdev->pretimeout = pretimeout;
> > +
> > + return ret;
>
> ret == 0 here, so return 0.
>
Okay, fix it in v3.
> > +}
> > +
> > +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694_wdt_cmd0 *buf = (struct nct6694_wdt_cmd0 *)data->xmit_buf;
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned int timeleft_ms;
> > + int ret;
> > +
> > + guard(mutex)(&data->lock);
> > +
> > + ret = nct6694_read_msg(nct6694, NCT6694_WDT_MOD,
> > + NCT6694_WDT_CMD0_OFFSET(data->wdev_idx),
> > + NCT6694_WDT_CMD0_LEN, buf);
> > + if (ret)
> > + return ret;
>
> The function does not return an error code. Return 0 instead.
Okay, fix it in v3.
> > +
> > + timeleft_ms = le32_to_cpu(buf->countdown);
> > +
> > + return timeleft_ms / 1000;
> > +}
...
> > + wdev = &data->wdev;
> > + wdev->info = &nct6694_wdt_info;
> > + wdev->ops = &nct6694_wdt_ops;
> > + wdev->timeout = timeout;
> > + wdev->pretimeout = pretimeout;
>
> pretimeout should be validated here.
>
Understood. I will make the modifications in v3.
Best regards,
Ming
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (4 preceding siblings ...)
2024-11-21 6:40 ` [PATCH v2 5/7] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
2024-11-21 14:22 ` Guenter Roeck
` (2 more replies)
2024-11-21 6:40 ` [PATCH v2 7/7] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
6 siblings, 3 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
This driver supports Hardware monitor functionality for NCT6694 MFD
device based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/nct6694-hwmon.c | 771 ++++++++++++++++++++++++++++++++++
4 files changed, 783 insertions(+)
create mode 100644 drivers/hwmon/nct6694-hwmon.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 496fe7d5a23f..d6414eea0463 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16546,6 +16546,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/nct6694_canfd.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index dd376602f3f1..df40986424bd 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1636,6 +1636,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 b827b92f2a78..27a43e67cdb7 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..f24cc22b8b42
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,771 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#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>
+
+/* Host interface */
+#define NCT6694_RPT_MOD 0xFF
+#define NCT6694_HWMON_MOD 0x00
+#define NCT6694_PWM_MOD 0x01
+
+/* Report Channel */
+#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))
+
+/* Message Channel*/
+/* HWMON Command */
+/* Command 00h */
+#define NCT6694_HWMON_CMD0_LEN 0x40
+#define NCT6694_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define NCT6694_VIN_EN(x) (0x00 + (x))
+#define NCT6694_TIN_EN(x) (0x02 + (x))
+#define NCT6694_FIN_EN(x) (0x04 + (x))
+#define NCT6694_PWM_EN(x) (0x06 + (x))
+#define NCT6694_PWM_FREQ_IDX(x) (0x30 + (x))
+/* Command 02h */
+#define NCT6694_HWMON_CMD2_LEN 0x90
+#define NCT6694_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
+#define NCT6694_SMI_CTRL_IDX 0x00
+#define NCT6694_VIN_HL(x) (0x10 + ((x) * 2))
+#define NCT6694_VIN_LL(x) (0x11 + ((x) * 2))
+#define NCT6694_TIN_HYST(x) (0x30 + ((x) * 2))
+#define NCT6694_TIN_HL(x) (0x31 + ((x) * 2))
+#define NCT6694_FIN_HL(x) (0x70 + ((x) * 2))
+#define NCT6694_FIN_LL(x) (0x71 + ((x) * 2))
+/* PWM Command */
+#define NCT6694_PWM_CMD1_LEN 0x18
+#define NCT6694_PWM_CMD1_OFFSET 0x0001
+#define NCT6694_MAL_VAL(x) (0x02 + (x))
+
+#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)
+
+static inline long in_from_reg(u8 reg)
+{
+ return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+ if (val <= 0)
+ return 0;
+ return val / 16;
+}
+
+static inline long temp_from_reg(u8 reg)
+{
+ return reg * 1000;
+}
+
+static inline u8 temp_to_reg(long val)
+{
+ return val / 1000;
+}
+
+struct nct6694_hwmon_data {
+ struct nct6694 *nct6694;
+ struct mutex lock;
+ unsigned char *xmit_buf;
+ unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
+};
+
+#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);
+ unsigned char vin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
+ *val = vin_en & BIT(channel % 8) ? 1 : 0;
+
+ return 0;
+ case hwmon_in_input:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_VIN_IDX(channel), 1,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->xmit_buf[0]);
+
+ return 0;
+ case hwmon_in_max:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->xmit_buf[NCT6694_VIN_HL(channel)]);
+
+ return 0;
+ case hwmon_in_min:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = in_from_reg(data->xmit_buf[NCT6694_VIN_LL(channel)]);
+
+ return 0;
+ case hwmon_in_alarm:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_VIN_STS(channel / 8), 1,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = !!(data->xmit_buf[0] & 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);
+ unsigned char temp_en, temp_hyst;
+ int ret, int_part, frac_part;
+ signed char temp_max;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
+ *val = temp_en & BIT(channel % 8) ? 1 : 0;
+
+ return 0;
+ case hwmon_temp_input:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_TIN_IDX(channel), 2,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ int_part = sign_extend32(data->xmit_buf[0], 7);
+ frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
+ if (int_part < 0)
+ *val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
+ else
+ *val = int_part * 1000 + frac_part * 125;
+
+ return 0;
+ case hwmon_temp_max:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
+
+ return 0;
+ case hwmon_temp_max_hyst:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
+ temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+ data->xmit_buf[NCT6694_TIN_HYST(channel)]);
+ if (temp_max < 0)
+ *val = temp_from_reg(temp_max + temp_hyst);
+ else
+ *val = temp_from_reg(temp_max - temp_hyst);
+
+ return 0;
+ case hwmon_temp_max_alarm:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_TIN_STS(channel / 8), 1,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = !!(data->xmit_buf[0] & 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);
+ unsigned char fanin_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ fanin_en = data->hwmon_en[NCT6694_FIN_EN(channel / 8)];
+ *val = fanin_en & BIT(channel % 8) ? 1 : 0;
+
+ return 0;
+ case hwmon_fan_input:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_FIN_IDX(channel), 2,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = (data->xmit_buf[1] |
+ (data->xmit_buf[0] << 8)) & 0xFFFF;
+
+ return 0;
+ case hwmon_fan_min:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = (data->xmit_buf[NCT6694_FIN_LL(channel)] |
+ data->xmit_buf[NCT6694_FIN_HL(channel)] << 8) & 0xFFFF;
+
+ return 0;
+ case hwmon_fan_min_alarm:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_FIN_STS(channel / 8),
+ 1, data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = data->xmit_buf[0] & 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);
+ unsigned char pwm_en;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ pwm_en = data->hwmon_en[NCT6694_PWM_EN(channel / 8)];
+ *val = pwm_en & BIT(channel % 8) ? 1 : 0;
+
+ return 0;
+ case hwmon_pwm_input:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
+ NCT6694_PWM_IDX(channel),
+ 1, data->xmit_buf);
+ if (ret)
+ return ret;
+
+ *val = data->xmit_buf[0];
+
+ return 0;
+ case hwmon_pwm_freq:
+ *val = NCT6694_FREQ_FROM_REG(data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)]);
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int nct6694_enable_channel(struct device *dev, u8 reg,
+ int channel, long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+
+ if (val == 0)
+ data->hwmon_en[reg] &= ~BIT(channel % 8);
+ else if (val == 1)
+ data->hwmon_en[reg] |= BIT(channel % 8);
+ else
+ return -EINVAL;
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD0_OFFSET,
+ NCT6694_HWMON_CMD0_LEN,
+ data->hwmon_en);
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_in_enable:
+ return nct6694_enable_channel(dev, NCT6694_VIN_EN(channel / 8),
+ channel, val);
+ case hwmon_in_max:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->xmit_buf[NCT6694_VIN_HL(channel)] = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ case hwmon_in_min:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 0, 2032);
+ data->xmit_buf[NCT6694_VIN_LL(channel)] = in_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ 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);
+ signed char temp_max, temp_hyst;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ return nct6694_enable_channel(dev, NCT6694_TIN_EN(channel / 8),
+ channel, val);
+ case hwmon_temp_max:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, -127000, 127000);
+ data->xmit_buf[NCT6694_TIN_HL(channel)] = temp_to_reg(val);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ case hwmon_temp_max_hyst:
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+
+ val = clamp_val(val, -127000, 127000);
+ temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
+ temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
+ (temp_max - val / 1000);
+ if (temp_hyst < 0 || temp_hyst > 7)
+ return -EINVAL;
+
+ data->xmit_buf[NCT6694_TIN_HYST(channel)] =
+ (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
+ FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ 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);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ return nct6694_enable_channel(dev, NCT6694_FIN_EN(channel / 8),
+ channel, val);
+ case hwmon_fan_min:
+ if (val <= 0)
+ return -EINVAL;
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ val = clamp_val(val, 1, 65535);
+ data->xmit_buf[NCT6694_FIN_HL(channel)] = (u8)((val >> 8) & 0xFF);
+ data->xmit_buf[NCT6694_FIN_LL(channel)] = (u8)(val & 0xFF);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ 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);
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ switch (attr) {
+ case hwmon_pwm_enable:
+ return nct6694_enable_channel(dev, NCT6694_PWM_EN(channel / 8),
+ channel, val);
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_PWM_MOD,
+ NCT6694_PWM_CMD1_OFFSET,
+ NCT6694_PWM_CMD1_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ data->xmit_buf[NCT6694_MAL_VAL(channel)] = val;
+
+ return nct6694_write_msg(data->nct6694, NCT6694_PWM_MOD,
+ NCT6694_PWM_CMD1_OFFSET,
+ NCT6694_PWM_CMD1_LEN,
+ data->xmit_buf);
+ case hwmon_pwm_freq:
+ data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)] = NCT6694_FREQ_TO_REG(val);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD0_OFFSET,
+ NCT6694_HWMON_CMD0_LEN,
+ 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)
+{
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ /*
+ * Record each Hardware Monitor Channel enable status
+ * and PWM frequency register
+ */
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD0_OFFSET,
+ NCT6694_HWMON_CMD0_LEN,
+ data->hwmon_en);
+ if (ret)
+ return ret;
+
+ /* Set Fan input Real Time alarm mode */
+ ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+ if (ret)
+ return ret;
+
+ data->xmit_buf[NCT6694_SMI_CTRL_IDX] = 0x02;
+
+ return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
+ NCT6694_HWMON_CMD2_OFFSET,
+ NCT6694_HWMON_CMD2_LEN,
+ data->xmit_buf);
+}
+
+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->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!data->xmit_buf)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ mutex_init(&data->lock);
+ platform_set_drvdata(pdev, data);
+
+ 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");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2024-11-21 6:40 ` [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-11-21 14:22 ` Guenter Roeck
2024-11-22 8:15 ` Ming Yu
2024-11-22 11:51 ` kernel test robot
2024-11-22 23:10 ` kernel test robot
2 siblings, 1 reply; 20+ messages in thread
From: Guenter Roeck @ 2024-11-21 14:22 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, jdelvare, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc
On 11/20/24 22:40, Ming Yu wrote:
> This driver supports Hardware monitor functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> MAINTAINERS | 1 +
> drivers/hwmon/Kconfig | 10 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/nct6694-hwmon.c | 771 ++++++++++++++++++++++++++++++++++
> 4 files changed, 783 insertions(+)
> create mode 100644 drivers/hwmon/nct6694-hwmon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 496fe7d5a23f..d6414eea0463 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16546,6 +16546,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/nct6694_canfd.c
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index dd376602f3f1..df40986424bd 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1636,6 +1636,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 b827b92f2a78..27a43e67cdb7 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..f24cc22b8b42
> --- /dev/null
> +++ b/drivers/hwmon/nct6694-hwmon.c
> @@ -0,0 +1,771 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#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>
> +
> +/* Host interface */
> +#define NCT6694_RPT_MOD 0xFF
> +#define NCT6694_HWMON_MOD 0x00
> +#define NCT6694_PWM_MOD 0x01
> +
> +/* Report Channel */
> +#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))
> +
> +/* Message Channel*/
> +/* HWMON Command */
> +/* Command 00h */
> +#define NCT6694_HWMON_CMD0_LEN 0x40
> +#define NCT6694_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define NCT6694_VIN_EN(x) (0x00 + (x))
> +#define NCT6694_TIN_EN(x) (0x02 + (x))
> +#define NCT6694_FIN_EN(x) (0x04 + (x))
> +#define NCT6694_PWM_EN(x) (0x06 + (x))
> +#define NCT6694_PWM_FREQ_IDX(x) (0x30 + (x))
> +/* Command 02h */
> +#define NCT6694_HWMON_CMD2_LEN 0x90
> +#define NCT6694_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> +#define NCT6694_SMI_CTRL_IDX 0x00
> +#define NCT6694_VIN_HL(x) (0x10 + ((x) * 2))
> +#define NCT6694_VIN_LL(x) (0x11 + ((x) * 2))
> +#define NCT6694_TIN_HYST(x) (0x30 + ((x) * 2))
> +#define NCT6694_TIN_HL(x) (0x31 + ((x) * 2))
> +#define NCT6694_FIN_HL(x) (0x70 + ((x) * 2))
> +#define NCT6694_FIN_LL(x) (0x71 + ((x) * 2))
> +/* PWM Command */
> +#define NCT6694_PWM_CMD1_LEN 0x18
> +#define NCT6694_PWM_CMD1_OFFSET 0x0001
> +#define NCT6694_MAL_VAL(x) (0x02 + (x))
> +
> +#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)
> +
> +static inline long in_from_reg(u8 reg)
> +{
> + return reg * 16;
> +}
> +
> +static inline u8 in_to_reg(long val)
> +{
> + if (val <= 0)
> + return 0;
> + return val / 16;
> +}
> +
> +static inline long temp_from_reg(u8 reg)
> +{
> + return reg * 1000;
> +}
> +
> +static inline u8 temp_to_reg(long val)
> +{
> + return val / 1000;
> +}
> +
> +struct nct6694_hwmon_data {
> + struct nct6694 *nct6694;
> + struct mutex lock;
> + unsigned char *xmit_buf;
> + unsigned char hwmon_en[NCT6694_HWMON_CMD0_LEN];
> +};
> +
> +#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);
> + unsigned char vin_en;
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_in_enable:
> + vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
> + *val = vin_en & BIT(channel % 8) ? 1 : 0;
Nit: !!(vin_en & BIT(channel % 8))
Not even worth mentioning, but !! is used below, so it would make sense
to use it here as well for consistency.
> +
> + return 0;
> + case hwmon_in_input:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_VIN_IDX(channel), 1,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> + > + *val = in_from_reg(data->xmit_buf[0]);
> +
> + return 0;
> + case hwmon_in_max:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = in_from_reg(data->xmit_buf[NCT6694_VIN_HL(channel)]);
> +
> + return 0;
> + case hwmon_in_min:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = in_from_reg(data->xmit_buf[NCT6694_VIN_LL(channel)]);
> +
> + return 0;
> + case hwmon_in_alarm:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_VIN_STS(channel / 8), 1,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = !!(data->xmit_buf[0] & 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);
> + unsigned char temp_en, temp_hyst;
> + int ret, int_part, frac_part;
> + signed char temp_max;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_temp_enable:
> + temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
> + *val = temp_en & BIT(channel % 8) ? 1 : 0;
> +
> + return 0;
> + case hwmon_temp_input:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_TIN_IDX(channel), 2,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + int_part = sign_extend32(data->xmit_buf[0], 7);
> + frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
> + if (int_part < 0)
> + *val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
> + else
> + *val = int_part * 1000 + frac_part * 125;
> +
> + return 0;
> + case hwmon_temp_max:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
> +
> + return 0;
> + case hwmon_temp_max_hyst:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> + temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
> + data->xmit_buf[NCT6694_TIN_HYST(channel)]);
> + if (temp_max < 0)
> + *val = temp_from_reg(temp_max + temp_hyst);
> + else
> + *val = temp_from_reg(temp_max - temp_hyst);
> +
> + return 0;
> + case hwmon_temp_max_alarm:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_TIN_STS(channel / 8), 1,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = !!(data->xmit_buf[0] & 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);
> + unsigned char fanin_en;
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + fanin_en = data->hwmon_en[NCT6694_FIN_EN(channel / 8)];
> + *val = fanin_en & BIT(channel % 8) ? 1 : 0;
> +
> + return 0;
> + case hwmon_fan_input:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_FIN_IDX(channel), 2,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = (data->xmit_buf[1] |
> + (data->xmit_buf[0] << 8)) & 0xFFFF;
> +
> + return 0;
> + case hwmon_fan_min:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = (data->xmit_buf[NCT6694_FIN_LL(channel)] |
> + data->xmit_buf[NCT6694_FIN_HL(channel)] << 8) & 0xFFFF;
> +
> + return 0;
> + case hwmon_fan_min_alarm:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_FIN_STS(channel / 8),
> + 1, data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = data->xmit_buf[0] & 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);
> + unsigned char pwm_en;
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_pwm_enable:
> + pwm_en = data->hwmon_en[NCT6694_PWM_EN(channel / 8)];
> + *val = pwm_en & BIT(channel % 8) ? 1 : 0;
> +
> + return 0;
> + case hwmon_pwm_input:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
> + NCT6694_PWM_IDX(channel),
> + 1, data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + *val = data->xmit_buf[0];
> +
> + return 0;
> + case hwmon_pwm_freq:
> + *val = NCT6694_FREQ_FROM_REG(data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)]);
> +
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int nct6694_enable_channel(struct device *dev, u8 reg,
> + int channel, long val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> +
> + if (val == 0)
> + data->hwmon_en[reg] &= ~BIT(channel % 8);
> + else if (val == 1)
> + data->hwmon_en[reg] |= BIT(channel % 8);
> + else
> + return -EINVAL;
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD0_OFFSET,
> + NCT6694_HWMON_CMD0_LEN,
> + data->hwmon_en);
> +}
> +
> +static int nct6694_in_write(struct device *dev, u32 attr, int channel,
> + long val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_in_enable:
> + return nct6694_enable_channel(dev, NCT6694_VIN_EN(channel / 8),
> + channel, val);
> + case hwmon_in_max:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + val = clamp_val(val, 0, 2032);
> + data->xmit_buf[NCT6694_VIN_HL(channel)] = in_to_reg(val);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + case hwmon_in_min:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + val = clamp_val(val, 0, 2032);
> + data->xmit_buf[NCT6694_VIN_LL(channel)] = in_to_reg(val);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + 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);
> + signed char temp_max, temp_hyst;
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_temp_enable:
> + return nct6694_enable_channel(dev, NCT6694_TIN_EN(channel / 8),
> + channel, val);
> + case hwmon_temp_max:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + val = clamp_val(val, -127000, 127000);
> + data->xmit_buf[NCT6694_TIN_HL(channel)] = temp_to_reg(val);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + case hwmon_temp_max_hyst:
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> +
> + val = clamp_val(val, -127000, 127000);
> + temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> + temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
> + (temp_max - val / 1000);
> + if (temp_hyst < 0 || temp_hyst > 7)
> + return -EINVAL;
> +
Just use clamp_val() again. Otherwise it is difficult for the user to determine
valid ranges.
> + data->xmit_buf[NCT6694_TIN_HYST(channel)] =
> + (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
> + FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + 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);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + return nct6694_enable_channel(dev, NCT6694_FIN_EN(channel / 8),
> + channel, val);
> + case hwmon_fan_min:
> + if (val <= 0)
> + return -EINVAL;
> +
I'd suggest to just use clamp_val() and drop this check.
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + val = clamp_val(val, 1, 65535);
> + data->xmit_buf[NCT6694_FIN_HL(channel)] = (u8)((val >> 8) & 0xFF);
> + data->xmit_buf[NCT6694_FIN_LL(channel)] = (u8)(val & 0xFF);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + 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);
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + switch (attr) {
> + case hwmon_pwm_enable:
> + return nct6694_enable_channel(dev, NCT6694_PWM_EN(channel / 8),
> + channel, val);
> + case hwmon_pwm_input:
> + if (val < 0 || val > 255)
> + return -EINVAL;
> +
> + ret = nct6694_read_msg(data->nct6694, NCT6694_PWM_MOD,
> + NCT6694_PWM_CMD1_OFFSET,
> + NCT6694_PWM_CMD1_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + data->xmit_buf[NCT6694_MAL_VAL(channel)] = val;
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_PWM_MOD,
> + NCT6694_PWM_CMD1_OFFSET,
> + NCT6694_PWM_CMD1_LEN,
> + data->xmit_buf);
> + case hwmon_pwm_freq:
> + data->hwmon_en[NCT6694_PWM_FREQ_IDX(channel)] = NCT6694_FREQ_TO_REG(val);
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD0_OFFSET,
> + NCT6694_HWMON_CMD0_LEN,
> + 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)
> +{
> + int ret;
> +
> + guard(mutex)(&data->lock);
> +
> + /*
> + * Record each Hardware Monitor Channel enable status
> + * and PWM frequency register
> + */
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD0_OFFSET,
> + NCT6694_HWMON_CMD0_LEN,
> + data->hwmon_en);
> + if (ret)
> + return ret;
> +
> + /* Set Fan input Real Time alarm mode */
> + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> + if (ret)
> + return ret;
> +
> + data->xmit_buf[NCT6694_SMI_CTRL_IDX] = 0x02;
> +
> + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> + NCT6694_HWMON_CMD2_OFFSET,
> + NCT6694_HWMON_CMD2_LEN,
> + data->xmit_buf);
> +}
> +
> +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->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!data->xmit_buf)
> + return -ENOMEM;
> +
> + data->nct6694 = nct6694;
> + mutex_init(&data->lock);
> + platform_set_drvdata(pdev, data);
> +
> + 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");
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2024-11-21 14:22 ` Guenter Roeck
@ 2024-11-22 8:15 ` Ming Yu
0 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-22 8:15 UTC (permalink / raw)
To: Guenter Roeck
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-rtc
Dear Guenter,
Thank you for your comments,
Guenter Roeck <linux@roeck-us.net> 於 2024年11月21日 週四 下午10:22寫道:
>
...
> > +static int nct6694_in_read(struct device *dev, u32 attr, int channel,
> > + long *val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + unsigned char vin_en;
> > + int ret;
> > +
> > + guard(mutex)(&data->lock);
> > +
> > + switch (attr) {
> > + case hwmon_in_enable:
> > + vin_en = data->hwmon_en[NCT6694_VIN_EN(channel / 8)];
> > + *val = vin_en & BIT(channel % 8) ? 1 : 0;
>
> Nit: !!(vin_en & BIT(channel % 8))
>
> Not even worth mentioning, but !! is used below, so it would make sense
> to use it here as well for consistency.
>
Understood. I will make the modifications in v3.
> > +
> > + return 0;
...
> > +static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
> > + long val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + signed char temp_max, temp_hyst;
> > + int ret;
> > +
> > + guard(mutex)(&data->lock);
> > +
> > + switch (attr) {
> > + case hwmon_temp_enable:
> > + return nct6694_enable_channel(dev, NCT6694_TIN_EN(channel / 8),
> > + channel, val);
> > + case hwmon_temp_max:
> > + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > + NCT6694_HWMON_CMD2_OFFSET,
> > + NCT6694_HWMON_CMD2_LEN,
> > + data->xmit_buf);
> > + if (ret)
> > + return ret;
> > +
> > + val = clamp_val(val, -127000, 127000);
> > + data->xmit_buf[NCT6694_TIN_HL(channel)] = temp_to_reg(val);
> > +
> > + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> > + NCT6694_HWMON_CMD2_OFFSET,
> > + NCT6694_HWMON_CMD2_LEN,
> > + data->xmit_buf);
> > + case hwmon_temp_max_hyst:
> > + ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
> > + NCT6694_HWMON_CMD2_OFFSET,
> > + NCT6694_HWMON_CMD2_LEN,
> > + data->xmit_buf);
> > +
> > + val = clamp_val(val, -127000, 127000);
> > + temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
> > + temp_hyst = (temp_max < 0) ? (temp_max + val / 1000) :
> > + (temp_max - val / 1000);
> > + if (temp_hyst < 0 || temp_hyst > 7)
> > + return -EINVAL;
> > +
>
> Just use clamp_val() again. Otherwise it is difficult for the user to determine
> valid ranges.
>
Understood. I will make the modifications in v3.
> > + data->xmit_buf[NCT6694_TIN_HYST(channel)] =
> > + (data->xmit_buf[NCT6694_TIN_HYST(channel)] & ~NCT6694_TIN_HYST_MASK) |
> > + FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
> > +
> > + return nct6694_write_msg(data->nct6694, NCT6694_HWMON_MOD,
> > + NCT6694_HWMON_CMD2_OFFSET,
> > + NCT6694_HWMON_CMD2_LEN,
> > + data->xmit_buf);
> > + 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);
> > + int ret;
> > +
> > + guard(mutex)(&data->lock);
> > +
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + return nct6694_enable_channel(dev, NCT6694_FIN_EN(channel / 8),
> > + channel, val);
> > + case hwmon_fan_min:
> > + if (val <= 0)
> > + return -EINVAL;
> > +
> I'd suggest to just use clamp_val() and drop this check.
>
Understood. I will make the modifications in v3.
Best regards,
Ming
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2024-11-21 6:40 ` [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-11-21 14:22 ` Guenter Roeck
@ 2024-11-22 11:51 ` kernel test robot
2024-11-22 23:10 ` kernel test robot
2 siblings, 0 replies; 20+ messages in thread
From: kernel test robot @ 2024-11-22 11:51 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: llvm, oe-kbuild-all, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc
Hi Ming,
kernel test robot noticed the following build errors:
[auto build test ERROR on andi-shyti/i2c/i2c-host]
[also build test ERROR on mkl-can-next/testing groeck-staging/hwmon-next abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.12 next-20241121]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ming-Yu/mfd-Add-core-driver-for-Nuvoton-NCT6694/20241121-155723
base: https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c/i2c-host
patch link: https://lore.kernel.org/r/20241121064046.3724726-7-tmyu0%40nuvoton.com
patch subject: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20241122/202411221902.1IFRsKWS-lkp@intel.com/config)
compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project 592c0fe55f6d9a811028b5f3507be91458ab2713)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241122/202411221902.1IFRsKWS-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202411221902.1IFRsKWS-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/hwmon/nct6694-hwmon.c:263:15: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
263 | frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
| ^
>> drivers/hwmon/nct6694-hwmon.c:508:10: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
508 | FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
| ^
2 errors generated.
vim +/FIELD_GET +263 drivers/hwmon/nct6694-hwmon.c
238
239 static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
240 long *val)
241 {
242 struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
243 unsigned char temp_en, temp_hyst;
244 int ret, int_part, frac_part;
245 signed char temp_max;
246
247 guard(mutex)(&data->lock);
248
249 switch (attr) {
250 case hwmon_temp_enable:
251 temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
252 *val = temp_en & BIT(channel % 8) ? 1 : 0;
253
254 return 0;
255 case hwmon_temp_input:
256 ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
257 NCT6694_TIN_IDX(channel), 2,
258 data->xmit_buf);
259 if (ret)
260 return ret;
261
262 int_part = sign_extend32(data->xmit_buf[0], 7);
> 263 frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
264 if (int_part < 0)
265 *val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
266 else
267 *val = int_part * 1000 + frac_part * 125;
268
269 return 0;
270 case hwmon_temp_max:
271 ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
272 NCT6694_HWMON_CMD2_OFFSET,
273 NCT6694_HWMON_CMD2_LEN,
274 data->xmit_buf);
275 if (ret)
276 return ret;
277
278 *val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
279
280 return 0;
281 case hwmon_temp_max_hyst:
282 ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
283 NCT6694_HWMON_CMD2_OFFSET,
284 NCT6694_HWMON_CMD2_LEN,
285 data->xmit_buf);
286 if (ret)
287 return ret;
288
289 temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
290 temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
291 data->xmit_buf[NCT6694_TIN_HYST(channel)]);
292 if (temp_max < 0)
293 *val = temp_from_reg(temp_max + temp_hyst);
294 else
295 *val = temp_from_reg(temp_max - temp_hyst);
296
297 return 0;
298 case hwmon_temp_max_alarm:
299 ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
300 NCT6694_TIN_STS(channel / 8), 1,
301 data->xmit_buf);
302 if (ret)
303 return ret;
304
305 *val = !!(data->xmit_buf[0] & BIT(channel % 8));
306
307 return 0;
308 default:
309 return -EOPNOTSUPP;
310 }
311 }
312
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
2024-11-21 6:40 ` [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-11-21 14:22 ` Guenter Roeck
2024-11-22 11:51 ` kernel test robot
@ 2024-11-22 23:10 ` kernel test robot
2 siblings, 0 replies; 20+ messages in thread
From: kernel test robot @ 2024-11-22 23:10 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: oe-kbuild-all, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-rtc
Hi Ming,
kernel test robot noticed the following build errors:
[auto build test ERROR on andi-shyti/i2c/i2c-host]
[also build test ERROR on mkl-can-next/testing groeck-staging/hwmon-next abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.12 next-20241122]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ming-Yu/mfd-Add-core-driver-for-Nuvoton-NCT6694/20241121-155723
base: https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c/i2c-host
patch link: https://lore.kernel.org/r/20241121064046.3724726-7-tmyu0%40nuvoton.com
patch subject: [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
config: hexagon-randconfig-r131-20241122 (https://download.01.org/0day-ci/archive/20241123/202411230620.ncqamorB-lkp@intel.com/config)
compiler: clang version 15.0.7 (https://github.com/llvm/llvm-project 8dfdcc7b7bf66834a761bd8de445840ef68e4d1a)
reproduce: (https://download.01.org/0day-ci/archive/20241123/202411230620.ncqamorB-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202411230620.ncqamorB-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/hwmon/nct6694-hwmon.c:263:15: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
^
>> drivers/hwmon/nct6694-hwmon.c:508:10: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
^
2 errors generated.
vim +/FIELD_GET +263 drivers/hwmon/nct6694-hwmon.c
238
239 static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
240 long *val)
241 {
242 struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
243 unsigned char temp_en, temp_hyst;
244 int ret, int_part, frac_part;
245 signed char temp_max;
246
247 guard(mutex)(&data->lock);
248
249 switch (attr) {
250 case hwmon_temp_enable:
251 temp_en = data->hwmon_en[NCT6694_TIN_EN(channel / 8)];
252 *val = temp_en & BIT(channel % 8) ? 1 : 0;
253
254 return 0;
255 case hwmon_temp_input:
256 ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
257 NCT6694_TIN_IDX(channel), 2,
258 data->xmit_buf);
259 if (ret)
260 return ret;
261
262 int_part = sign_extend32(data->xmit_buf[0], 7);
> 263 frac_part = FIELD_GET(NCT6694_LSB_REG_MASK, data->xmit_buf[1]);
264 if (int_part < 0)
265 *val = (int_part + 1) * 1000 - (8 - frac_part) * 125;
266 else
267 *val = int_part * 1000 + frac_part * 125;
268
269 return 0;
270 case hwmon_temp_max:
271 ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
272 NCT6694_HWMON_CMD2_OFFSET,
273 NCT6694_HWMON_CMD2_LEN,
274 data->xmit_buf);
275 if (ret)
276 return ret;
277
278 *val = temp_from_reg(data->xmit_buf[NCT6694_TIN_HL(channel)]);
279
280 return 0;
281 case hwmon_temp_max_hyst:
282 ret = nct6694_read_msg(data->nct6694, NCT6694_HWMON_MOD,
283 NCT6694_HWMON_CMD2_OFFSET,
284 NCT6694_HWMON_CMD2_LEN,
285 data->xmit_buf);
286 if (ret)
287 return ret;
288
289 temp_max = (signed char)data->xmit_buf[NCT6694_TIN_HL(channel)];
290 temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
291 data->xmit_buf[NCT6694_TIN_HYST(channel)]);
292 if (temp_max < 0)
293 *val = temp_from_reg(temp_max + temp_hyst);
294 else
295 *val = temp_from_reg(temp_max - temp_hyst);
296
297 return 0;
298 case hwmon_temp_max_alarm:
299 ret = nct6694_read_msg(data->nct6694, NCT6694_RPT_MOD,
300 NCT6694_TIN_STS(channel / 8), 1,
301 data->xmit_buf);
302 if (ret)
303 return ret;
304
305 *val = !!(data->xmit_buf[0] & BIT(channel % 8));
306
307 return 0;
308 default:
309 return -EOPNOTSUPP;
310 }
311 }
312
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 7/7] rtc: Add Nuvoton NCT6694 RTC support
2024-11-21 6:40 [PATCH v2 0/7] Add Nuvoton NCT6694 MFD drivers Ming Yu
` (5 preceding siblings ...)
2024-11-21 6:40 ` [PATCH v2 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-11-21 6:40 ` Ming Yu
6 siblings, 0 replies; 20+ messages in thread
From: Ming Yu @ 2024-11-21 6:40 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
This driver supports RTC functionality for NCT6694 MFD device
based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/rtc/Kconfig | 10 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-nct6694.c | 263 ++++++++++++++++++++++++++++++++++++++
4 files changed, 275 insertions(+)
create mode 100644 drivers/rtc/rtc-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d6414eea0463..6d1cfec28076 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16550,6 +16550,7 @@ F: drivers/hwmon/nct6694-hwmon.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/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 66eb1122248b..36829d096194 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -406,6 +406,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 f62340ecc534..64443d26bb5b 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -116,6 +116,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..097555a16b4f
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,263 @@
+// 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>
+
+/* Host interface */
+#define NCT6694_RTC_MOD 0x08
+
+/* Message Channel */
+/* Command 00h */
+#define NCT6694_RTC_CMD0_LEN 0x07
+#define NCT6694_RTC_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+/* Command 01h */
+#define NCT6694_RTC_CMD1_LEN 0x05
+#define NCT6694_RTC_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
+/* Command 02h */
+#define NCT6694_RTC_CMD2_LEN 0x02
+#define NCT6694_RTC_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
+
+#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_cmd0 {
+ u8 sec;
+ u8 min;
+ u8 hour;
+ u8 week;
+ u8 day;
+ u8 month;
+ u8 year;
+};
+
+struct __packed nct6694_rtc_cmd1 {
+ u8 sec;
+ u8 min;
+ u8 hour;
+ u8 alarm_en;
+ u8 alarm_pend;
+};
+
+struct __packed nct6694_rtc_cmd2 {
+ u8 irq_en;
+ u8 irq_pend;
+};
+
+struct nct6694_rtc_data {
+ struct nct6694 *nct6694;
+ struct rtc_device *rtc;
+ struct mutex lock;
+ unsigned char *xmit_buf;
+};
+
+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_cmd0 *buf = (struct nct6694_rtc_cmd0 *)data->xmit_buf;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD0_OFFSET,
+ NCT6694_RTC_CMD0_LEN,
+ buf);
+ if (ret)
+ return ret;
+
+ tm->tm_sec = bcd2bin(buf->sec); /* tm_sec expect 0 ~ 59 */
+ tm->tm_min = bcd2bin(buf->min); /* tm_min expect 0 ~ 59 */
+ tm->tm_hour = bcd2bin(buf->hour); /* tm_hour expect 0 ~ 23 */
+ tm->tm_wday = bcd2bin(buf->week) - 1; /* tm_wday expect 0 ~ 6 */
+ tm->tm_mday = bcd2bin(buf->day); /* tm_mday expect 1 ~ 31 */
+ tm->tm_mon = bcd2bin(buf->month) - 1; /* tm_month expect 0 ~ 11 */
+ tm->tm_year = bcd2bin(buf->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_cmd0 *buf = (struct nct6694_rtc_cmd0 *)data->xmit_buf;
+
+ guard(mutex)(&data->lock);
+
+ buf->sec = bin2bcd(tm->tm_sec);
+ buf->min = bin2bcd(tm->tm_min);
+ buf->hour = bin2bcd(tm->tm_hour);
+ buf->week = bin2bcd(tm->tm_wday + 1);
+ buf->day = bin2bcd(tm->tm_mday);
+ buf->month = bin2bcd(tm->tm_mon + 1);
+ buf->year = bin2bcd(tm->tm_year - 100);
+
+ return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD0_OFFSET,
+ NCT6694_RTC_CMD0_LEN,
+ buf);
+}
+
+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_cmd1 *buf = (struct nct6694_rtc_cmd1 *)data->xmit_buf;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = nct6694_read_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD1_OFFSET,
+ NCT6694_RTC_CMD1_LEN,
+ buf);
+ if (ret)
+ return ret;
+
+ alrm->time.tm_sec = bcd2bin(buf->sec);
+ alrm->time.tm_min = bcd2bin(buf->min);
+ alrm->time.tm_hour = bcd2bin(buf->hour);
+ alrm->enabled = buf->alarm_en;
+ alrm->pending = buf->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_cmd1 *buf = (struct nct6694_rtc_cmd1 *)data->xmit_buf;
+
+ guard(mutex)(&data->lock);
+
+ buf->sec = bin2bcd(alrm->time.tm_sec);
+ buf->min = bin2bcd(alrm->time.tm_min);
+ buf->hour = bin2bcd(alrm->time.tm_hour);
+ buf->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0;
+ buf->alarm_pend = 0;
+
+ return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD1_OFFSET,
+ NCT6694_RTC_CMD1_LEN,
+ buf);
+}
+
+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_cmd2 *buf = (struct nct6694_rtc_cmd2 *)data->xmit_buf;
+
+ guard(mutex)(&data->lock);
+
+ if (enabled)
+ buf->irq_en |= NCT6694_RTC_IRQ_EN;
+ else
+ buf->irq_en &= ~NCT6694_RTC_IRQ_EN;
+
+ buf->irq_pend = 0;
+
+ return nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD2_OFFSET,
+ NCT6694_RTC_CMD2_LEN,
+ buf);
+}
+
+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_cmd2 *buf = (struct nct6694_rtc_cmd2 *)data->xmit_buf;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ buf->irq_en = NCT6694_RTC_IRQ_EN;
+ buf->irq_pend = NCT6694_RTC_IRQ_STS;
+ ret = nct6694_write_msg(data->nct6694, NCT6694_RTC_MOD,
+ NCT6694_RTC_CMD2_OFFSET,
+ NCT6694_RTC_CMD2_LEN,
+ buf);
+ if (ret)
+ return IRQ_NONE;
+
+ rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF);
+
+ 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->xmit_buf = devm_kcalloc(&pdev->dev, NCT6694_MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!data->xmit_buf)
+ 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;
+
+ mutex_init(&data->lock);
+
+ device_set_wakeup_capable(&pdev->dev, 1);
+
+ platform_set_drvdata(pdev, data);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ nct6694_irq, IRQF_ONESHOT,
+ "nct6694-rtc", data);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+ /* Register rtc device to RTC framework */
+ return devm_rtc_register_device(data->rtc);
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+ .driver = {
+ .name = "nct6694-rtc",
+ },
+ .probe = nct6694_rtc_probe,
+};
+
+module_platform_driver(nct6694_rtc_driver);
+
+MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread