* [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
@ 2024-10-24 8:59 Ming Yu
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
` (9 more replies)
0 siblings, 10 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
This patch series introduces support for Nuvoton NCT6694, a peripheral
expander based on USB interface. It models the chip as an MFD driver
(1/9), GPIO driver(2/9), I2C Adapter driver(3/9), CANfd driver(4/9),
WDT driver(5/9), HWMON driver(6/9), IIO driver(7/9), PWM driver(8/9),
and RTC driver(9/9).
The MFD driver implements USB device functionality to issue
custom-define USB bulk pipe packets for NCT6694. Each child device can
use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
a command. They can also register a handler function that will be called
when the USB device receives its interrupt pipe.
The following introduces the custom-define USB transactions:
nct6694_read_msg - Send bulk-out pipe to write request packet
Receive bulk-in pipe to read response packet
Receive bulk-in pipe to read data packet
nct6694_write_msg - Send bulk-out pipe to write request packet
Send bulk-out pipe to write data packet
Receive bulk-in pipe to read response packet
Receive bulk-in pipe to read data packet
Ming Yu (9):
mfd: Add core driver for Nuvoton NCT6694
gpio: Add Nuvoton NCT6694 GPIO support
i2c: Add Nuvoton NCT6694 I2C support
can: Add Nuvoton NCT6694 CAN support
watchdog: Add Nuvoton NCT6694 WDT support
hwmon: Add Nuvoton NCT6694 HWMON support
iio: adc: Add Nuvoton NCT6694 IIO support
pwm: Add Nuvoton NCT6694 PWM support
rtc: Add Nuvoton NCT6694 RTC support
MAINTAINERS | 15 +
drivers/gpio/Kconfig | 12 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-nct6694.c | 489 ++++++++++++++++++
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/nct6694-hwmon.c | 407 +++++++++++++++
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-nct6694.c | 166 ++++++
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/nct6694_adc.c | 616 ++++++++++++++++++++++
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 2 +
drivers/mfd/nct6694.c | 394 +++++++++++++++
drivers/net/can/Kconfig | 10 +
drivers/net/can/Makefile | 1 +
drivers/net/can/nct6694_canfd.c | 843 +++++++++++++++++++++++++++++++
drivers/pwm/Kconfig | 10 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-nct6694.c | 245 +++++++++
drivers/rtc/Kconfig | 10 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-nct6694.c | 276 ++++++++++
drivers/watchdog/Kconfig | 11 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/nct6694_wdt.c | 329 ++++++++++++
include/linux/mfd/nct6694.h | 168 ++++++
29 files changed, 4051 insertions(+)
create mode 100644 drivers/gpio/gpio-nct6694.c
create mode 100644 drivers/hwmon/nct6694-hwmon.c
create mode 100644 drivers/i2c/busses/i2c-nct6694.c
create mode 100644 drivers/iio/adc/nct6694_adc.c
create mode 100644 drivers/mfd/nct6694.c
create mode 100644 drivers/net/can/nct6694_canfd.c
create mode 100644 drivers/pwm/pwm-nct6694.c
create mode 100644 drivers/rtc/rtc-nct6694.c
create mode 100644 drivers/watchdog/nct6694_wdt.c
create mode 100644 include/linux/mfd/nct6694.h
--
2.34.1
^ permalink raw reply [flat|nested] 80+ messages in thread
* [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 9:03 ` Marc Kleine-Budde
` (3 more replies)
2024-10-24 8:59 ` [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
` (8 subsequent siblings)
9 siblings, 4 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 register
a handler function 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 | 394 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/nct6694.h | 168 +++++++++++++++
5 files changed, 581 insertions(+)
create mode 100644 drivers/mfd/nct6694.c
create mode 100644 include/linux/mfd/nct6694.h
diff --git a/MAINTAINERS b/MAINTAINERS
index e9659a5a7fb3..30157ca95cf3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16434,6 +16434,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 f9325bcce1b9..da2600958697 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -546,6 +546,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 2a9f91e81af8..2cf816d67d03 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -116,6 +116,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..9838c7be0b98
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 MFD driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/io.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-usb_mfd"
+
+#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(IIO),
+ MFD_DEV_SIMPLE(HWMON),
+ MFD_DEV_SIMPLE(PWM),
+ MFD_DEV_SIMPLE(RTC),
+};
+
+int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit,
+ void (*handler)(void *), void *private_data)
+{
+ struct nct6694_handler_entry *entry;
+ unsigned long flags;
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->irq_bit = irq_bit;
+ entry->handler = handler;
+ entry->private_data = private_data;
+
+ spin_lock_irqsave(&nct6694->lock, flags);
+ list_add_tail(&entry->list, &nct6694->handler_list);
+ spin_unlock_irqrestore(&nct6694->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(nct6694_register_handler);
+
+int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
+ u8 rd_idx, u8 rd_len, unsigned char *buf)
+{
+ struct usb_device *udev = nct6694->udev;
+ unsigned char err_status;
+ int len, packet_len, tx_len, rx_len;
+ int i, ret;
+
+ mutex_lock(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
+ nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
+ nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
+ nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
+ nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
+ nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
+ nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
+ nct6694->timeout);
+ if (ret)
+ goto err;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
+ nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
+ nct6694->timeout);
+ if (ret)
+ goto err;
+
+ err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
+
+ /*
+ * Segmented reception of messages that exceed the size of USB bulk
+ * pipe packets.
+ */
+ for (i = 0, len = length; len > 0; i++, len -= packet_len) {
+ if (len > nct6694->maxp)
+ packet_len = nct6694->maxp;
+ else
+ packet_len = len;
+
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
+ nct6694->rx_buffer + nct6694->maxp * i,
+ packet_len, &rx_len, nct6694->timeout);
+ if (ret)
+ goto err;
+ }
+
+ for (i = 0; i < rd_len; i++)
+ buf[i] = nct6694->rx_buffer[i + rd_idx];
+
+ if (err_status) {
+ pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
+ ret = -EIO;
+ }
+
+err:
+ mutex_unlock(&nct6694->access_lock);
+ return ret;
+}
+EXPORT_SYMBOL(nct6694_read_msg);
+
+int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
+ u16 length, unsigned char *buf)
+{
+ struct usb_device *udev = nct6694->udev;
+ unsigned char err_status;
+ int len, packet_len, tx_len, rx_len;
+ int i, ret;
+
+ mutex_lock(&nct6694->access_lock);
+
+ /* Send command packet to USB device */
+ nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
+ nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
+ nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
+ nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_SET;
+ nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
+ nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
+ nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
+ nct6694->timeout);
+ if (ret)
+ goto err;
+
+ /*
+ * Segmented transmission of messages that exceed the size of USB bulk
+ * pipe packets.
+ */
+ for (i = 0, len = length; len > 0; i++, len -= packet_len) {
+ if (len > nct6694->maxp)
+ packet_len = nct6694->maxp;
+ else
+ packet_len = len;
+
+ memcpy(nct6694->tx_buffer + nct6694->maxp * i,
+ buf + nct6694->maxp * i, packet_len);
+
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
+ nct6694->tx_buffer + nct6694->maxp * i,
+ packet_len, &tx_len, nct6694->timeout);
+ if (ret)
+ goto err;
+ }
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
+ nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
+ nct6694->timeout);
+ if (ret)
+ goto err;
+
+ err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
+
+ /*
+ * Segmented reception of messages that exceed the size of USB bulk
+ * pipe packets.
+ */
+ for (i = 0, len = length; len > 0; i++, len -= packet_len) {
+ if (len > nct6694->maxp)
+ packet_len = nct6694->maxp;
+ else
+ packet_len = len;
+
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
+ nct6694->rx_buffer + nct6694->maxp * i,
+ packet_len, &rx_len, nct6694->timeout);
+ if (ret)
+ goto err;
+ }
+
+ memcpy(buf, nct6694->rx_buffer, length);
+
+ if (err_status) {
+ pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
+ ret = -EIO;
+ }
+
+err:
+ mutex_unlock(&nct6694->access_lock);
+ return ret;
+}
+EXPORT_SYMBOL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+ unsigned char *int_status = urb->transfer_buffer;
+ struct nct6694 *nct6694 = urb->context;
+ struct nct6694_handler_entry *entry;
+ unsigned long flags;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default:
+ goto resubmit;
+ }
+
+ spin_lock_irqsave(&nct6694->lock, flags);
+
+ list_for_each_entry(entry, &nct6694->handler_list, list) {
+ if (int_status[INT_IN_IRQ_IDX] & entry->irq_bit)
+ entry->handler(entry->private_data);
+ }
+
+ spin_unlock_irqrestore(&nct6694->lock, flags);
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ pr_debug("%s: Failed to resubmit urb, status %d",
+ __func__, ret);
+}
+
+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;
+ int pipe, maxp, bulk_pipe;
+ int ret = EINVAL;
+
+ interface = iface->cur_altsetting;
+ /* Binding interface class : 0xFF */
+ if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
+ interface->desc.bInterfaceSubClass != 0x00 ||
+ interface->desc.bInterfaceProtocol != 0x00)
+ return -ENODEV;
+
+ int_endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(int_endpoint))
+ return -ENODEV;
+
+ nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL);
+ if (!nct6694)
+ return -ENOMEM;
+
+ pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT);
+ maxp = usb_maxpacket(udev, pipe);
+
+ nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!nct6694->cmd_buffer)
+ return -ENOMEM;
+ nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!nct6694->rx_buffer)
+ return -ENOMEM;
+ nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
+ sizeof(unsigned char), GFP_KERNEL);
+ if (!nct6694->tx_buffer)
+ return -ENOMEM;
+ nct6694->int_buffer = devm_kcalloc(dev, 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) {
+ dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
+ return -ENOMEM;
+ }
+
+ /* Bulk pipe maximum packet for each transaction */
+ bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
+ nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
+
+ mutex_init(&nct6694->access_lock);
+ nct6694->udev = udev;
+ nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
+
+ INIT_LIST_HEAD(&nct6694->handler_list);
+ spin_lock_init(&nct6694->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(&udev->dev, nct6694);
+ usb_set_intfdata(iface, nct6694);
+
+ ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
+ ARRAY_SIZE(nct6694_dev));
+ if (ret) {
+ dev_err(&udev->dev, "Failed to add mfd's child device\n");
+ goto err_mfd;
+ }
+
+ nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
+
+ dev_info(&udev->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 ret;
+}
+
+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);
+ flush_workqueue(nct6694->async_workqueue);
+ destroy_workqueue(nct6694->async_workqueue);
+ usb_set_intfdata(iface, NULL);
+ usb_kill_urb(nct6694->int_in_urb);
+ usb_free_urb(nct6694->int_in_urb);
+}
+
+static const struct usb_device_id nct6694_ids[] = {
+ { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
+ {},
+};
+MODULE_DEVICE_TABLE(usb, nct6694_ids);
+
+static struct usb_driver nct6694_usb_driver = {
+ .name = DRVNAME,
+ .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..0797564363be
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,168 @@
+/* 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_IIO "nct6694-iio"
+#define NCT6694_DEV_HWMON "nct6694-hwmon"
+#define NCT6694_DEV_PWM "nct6694-pwm"
+#define NCT6694_DEV_RTC "nct6694-rtc"
+
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define INT_IN_ENDPOINT 0x81
+#define BULK_IN_ENDPOINT 0x82
+#define BULK_OUT_ENDPOINT 0x03
+#define MAX_PACKET_SZ 0x100
+
+#define CMD_PACKET_SZ 0x8
+#define HCTRL_SET 0x40
+#define HCTRL_GET 0x80
+
+#define REQUEST_MOD_IDX 0x01
+#define REQUEST_CMD_IDX 0x02
+#define REQUEST_SEL_IDX 0x03
+#define REQUEST_HCTRL_IDX 0x04
+#define REQUEST_LEN_L_IDX 0x06
+#define REQUEST_LEN_H_IDX 0x07
+
+#define RESPONSE_STS_IDX 0x01
+
+#define INT_IN_IRQ_IDX 0x00
+#define GPIO_IRQ_STATUS BIT(0)
+#define CAN_IRQ_STATUS BIT(2)
+#define RTC_IRQ_STATUS BIT(3)
+
+#define URB_TIMEOUT 1000
+
+/*
+ * struct nct6694 - Nuvoton NCT6694 structure
+ *
+ * @udev: Pointer to the USB device
+ * @int_in_urb: Interrupt pipe urb
+ * @access_lock: USB transaction lock
+ * @handler_list: List of registered handlers
+ * @async_workqueue: Workqueue of processing asynchronous work
+ * @tx_buffer: USB write message buffer
+ * @rx_buffer: USB read message buffer
+ * @cmd_buffer: USB send command message buffer
+ * @int_buffer: USB receive interrupt message buffer
+ * @lock: Handlers lock
+ * @timeout: URB timeout
+ * @maxp: Maximum packet of bulk pipe
+ */
+struct nct6694 {
+ struct usb_device *udev;
+ struct urb *int_in_urb;
+ struct list_head handler_list;
+ struct workqueue_struct *async_workqueue;
+
+ /* Make sure that every USB transaction is not interrupted */
+ struct mutex access_lock;
+
+ unsigned char *tx_buffer;
+ unsigned char *rx_buffer;
+ unsigned char *cmd_buffer;
+ unsigned char *int_buffer;
+
+ /* Prevent races within handlers */
+ spinlock_t lock;
+
+ /* time in msec to wait for the urb to the complete */
+ long timeout;
+
+ /* Bulk pipe maximum packet for each transaction */
+ int maxp;
+};
+
+/*
+ * struct nct6694_handler_entry - Stores the interrupt handling information
+ * for each registered peripheral
+ *
+ * @irq_bit: The bit in irq_status[INT_IN_IRQ_IDX] representing interrupt
+ * @handler: Function pointer to the interrupt handler of the peripheral
+ * @private_data: Private data specific to the peripheral driver
+ * @list: Node used to link to the handler_list
+ */
+struct nct6694_handler_entry {
+ int irq_bit;
+ void (*handler)(void *private_data);
+ void *private_data;
+ struct list_head list;
+};
+
+/*
+ * nct6694_register_handler - Register a handler with private data for
+ * interrupt pipe irq event
+ *
+ * @nct6694 - Nuvoton NCT6694 structure
+ * @irq_bit - The irq for which to register a handler
+ * @handler - The handler function
+ * @private_data - Private data for which to register a handler
+ *
+ * This function is called when peripherals need to register a handler
+ * for receiving interrupt pipe.
+ *
+ * Don't use the wait_for_completion function in handler function, as
+ * it is in interrupt context.
+ */
+int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit,
+ void (*handler)(void *),
+ void *private_data);
+
+/*
+ * 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
+ * @rd_idx - Read data from rx buffer at index
+ * @rd_len - Read length from rx buffer
+ * @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, u8 rd_idx, u8 rd_len,
+ unsigned char *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, unsigned char *buf);
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 9:47 ` Bartosz Golaszewski
2024-10-24 8:59 ` [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
` (7 subsequent siblings)
9 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 489 ++++++++++++++++++++++++++++++++++++
4 files changed, 503 insertions(+)
create mode 100644 drivers/gpio/gpio-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 30157ca95cf3..2c86d5dab3f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16438,6 +16438,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 d93cd4f722b4..aa78ad9ff4ac 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1450,6 +1450,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 1429e8c0229b..02c94aa28017 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -121,6 +121,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..42c0e6e76730
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-gpio"
+
+/* Host interface */
+#define REQUEST_GPIO_MOD 0xFF
+#define REQUEST_GPIO_LEN 0x01
+
+/* Report Channel */
+#define GPIO_VER_REG 0x90
+#define GPIO_VALID_REG 0x110
+#define GPI_DATA_REG 0x120
+#define GPO_DIR_REG 0x170
+#define GPO_TYPE_REG 0x180
+#define GPO_DATA_REG 0x190
+
+#define GPI_STS_REG 0x130
+#define GPI_CLR_REG 0x140
+#define GPI_FALLING_REG 0x150
+#define GPI_RISING_REG 0x160
+
+struct nct6694_gpio_data {
+ struct nct6694 *nct6694;
+ struct gpio_chip gpio;
+ struct work_struct irq_work;
+ struct work_struct irq_trig_work;
+
+ /* Protect irq operation */
+ struct mutex irq_lock;
+
+ unsigned char irq_trig_falling;
+ unsigned char irq_trig_rising;
+
+ /* Current gpio group */
+ unsigned char group;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ unsigned char buf;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ return !(BIT(offset) & buf);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ unsigned char buf;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ buf &= ~(1 << offset);
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, &buf);
+
+ return ret;
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+ unsigned int offset, int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ unsigned char buf;
+ int ret;
+
+ /* Set direction to output */
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ buf |= (1 << offset);
+ ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, &buf);
+ if (ret < 0)
+ return ret;
+
+ /* Then set output level */
+ ret = nct6694_read_msg(data->nct6694, 0xFF,
+ GPO_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (val)
+ buf |= (1 << offset);
+ else
+ buf &= ~(1 << offset);
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, &buf);
+
+ return ret;
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ unsigned char buf;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DIR_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (BIT(offset) & buf) {
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & buf);
+ }
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ return !!(BIT(offset) & buf);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+ int val)
+{
+ struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+ unsigned char buf;
+
+ nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+
+ if (val)
+ buf |= (1 << offset);
+ else
+ buf &= ~(1 << offset);
+
+ nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_DATA_REG + data->group,
+ REQUEST_GPIO_LEN, &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);
+ unsigned char buf;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_TYPE_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ buf |= (1 << offset);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ buf &= ~(1 << offset);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPO_TYPE_REG + data->group,
+ REQUEST_GPIO_LEN, &buf);
+
+ return ret;
+}
+
+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);
+ unsigned char buf;
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPIO_VALID_REG + data->group,
+ REQUEST_GPIO_LEN, 0, 1, &buf);
+ if (ret < 0)
+ return ret;
+
+ *valid_mask = buf;
+
+ return ret;
+}
+
+static void nct6694_irq(struct work_struct *irq_work)
+{
+ struct nct6694_gpio_data *data;
+ unsigned char status;
+
+ data = container_of(irq_work, struct nct6694_gpio_data, irq_work);
+
+ nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_STS_REG + data->group, 1,
+ 0, 1, &status);
+
+ while (status) {
+ int bit = __ffs(status);
+ unsigned char val = BIT(bit);
+
+ handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+ status &= ~(1 << bit);
+ nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_CLR_REG + data->group,
+ REQUEST_GPIO_LEN, &val);
+ }
+}
+
+static void nct6694_gpio_handler(void *private_data)
+{
+ struct nct6694_gpio_data *data = private_data;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ queue_work(nct6694->async_workqueue, &data->irq_work);
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_FALLING_REG + data->group,
+ 1, 0, 1, &data->irq_trig_falling);
+ if (ret)
+ return ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_RISING_REG + data->group,
+ 1, 0, 1, &data->irq_trig_rising);
+
+ return ret;
+}
+
+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 void nct6694_irq_trig(struct work_struct *irq_trig_work)
+{
+ struct nct6694_gpio_data *data;
+
+ data = container_of(irq_trig_work, struct nct6694_gpio_data,
+ irq_trig_work);
+
+ nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_FALLING_REG + data->group,
+ 1, &data->irq_trig_falling);
+
+ nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD,
+ GPI_RISING_REG + data->group,
+ 1, &data->irq_trig_rising);
+}
+
+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);
+ struct nct6694 *nct6694 = data->nct6694;
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ 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 -EOPNOTSUPP;
+ }
+
+ queue_work(nct6694->async_workqueue, &data->irq_trig_work);
+
+ 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);
+
+ mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694",
+ .irq_mask = nct6694_irq_mask,
+ .irq_unmask = nct6694_irq_unmask,
+ .irq_set_type = nct6694_irq_set_type,
+ .irq_bus_lock = nct6694_irq_bus_lock,
+ .irq_bus_sync_unlock = nct6694_irq_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static const char * const nct6694_gpio_name[] = {
+ "NCT6694-GPIO0",
+ "NCT6694-GPIO1",
+ "NCT6694-GPIO2",
+ "NCT6694-GPIO3",
+ "NCT6694-GPIO4",
+ "NCT6694-GPIO5",
+ "NCT6694-GPIO6",
+ "NCT6694-GPIO7",
+ "NCT6694-GPIO8",
+ "NCT6694-GPIO9",
+ "NCT6694-GPIOA",
+ "NCT6694-GPIOB",
+ "NCT6694-GPIOC",
+ "NCT6694-GPIOD",
+ "NCT6694-GPIOE",
+ "NCT6694-GPIOF",
+};
+
+static int nct6694_gpio_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_gpio_data *data;
+ struct gpio_irq_chip *girq;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ data->group = cell->id;
+
+ data->gpio.label = nct6694_gpio_name[cell->id];
+ 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 = 8;
+
+ INIT_WORK(&data->irq_work, nct6694_irq);
+ INIT_WORK(&data->irq_trig_work, nct6694_irq_trig);
+ mutex_init(&data->irq_lock);
+
+ ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS,
+ nct6694_gpio_handler, data);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
+ __func__, ERR_PTR(ret));
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ ret = nct6694_get_irq_trig(data);
+ if (ret)
+ return ret;
+
+ /* 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 = gpiochip_add_data(&data->gpio, data);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe",
+ __func__, ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static void nct6694_gpio_remove(struct platform_device *pdev)
+{
+ struct nct6694_gpio_data *data = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&data->gpio);
+ cancel_work(&data->irq_work);
+ cancel_work(&data->irq_trig_work);
+}
+
+static struct platform_driver nct6694_gpio_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_gpio_probe,
+ .remove = nct6694_gpio_remove,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_gpio_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_gpio_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_gpio_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-10-24 8:59 ` [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 10:41 ` Andi Shyti
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
` (6 subsequent siblings)
9 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 166 +++++++++++++++++++++++++++++++
4 files changed, 178 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2c86d5dab3f1..1cc64f9f154a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16439,6 +16439,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 6b3ba7e5723a..01a60de4b8a4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1315,6 +1315,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-nct6604.
+
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 ecc07c50f2a0..3c4a0ea5a46f 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -135,6 +135,7 @@ 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_CP2615) += i2c-cp2615.o
+obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 000000000000..b33d90f26f9f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,166 @@
+// 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+
+/* Host interface */
+#define REQUEST_I2C_MOD 0x03
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_I2C_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define REQUEST_I2C_LEN 0x90
+#define I2C_PORT_IDX 0x00
+#define I2C_BR_IDX 0x01
+#define I2C_ADDR_IDX 0x02
+#define I2C_W_CNT_IDX 0x03
+#define I2C_R_CNT_IDX 0x04
+
+#define I2C_RD_IDX 0x50
+#define I2C_WR_IDX 0x10
+
+#define DRVNAME "nct6694-i2c"
+
+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 nct6694_i2c_data {
+ struct nct6694 *nct6694;
+ struct i2c_adapter adapter;
+ 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;
+ int ret, i;
+
+ for (i = 0; i < num ; i++) {
+ unsigned char buf[REQUEST_I2C_LEN] = {0};
+ struct i2c_msg *msg_temp = &msgs[i];
+
+ if (msg_temp->len > 64)
+ return -EPROTO;
+
+ buf[I2C_PORT_IDX] = data->port;
+ buf[I2C_BR_IDX] = data->br;
+ buf[I2C_ADDR_IDX] = i2c_8bit_addr_from_msg(msg_temp);
+ if (msg_temp->flags & I2C_M_RD) {
+ buf[I2C_R_CNT_IDX] = msg_temp->len;
+ ret = nct6694_write_msg(data->nct6694, REQUEST_I2C_MOD,
+ REQUEST_I2C_OFFSET, REQUEST_I2C_LEN,
+ buf);
+ if (ret < 0)
+ return 0;
+ memcpy(msg_temp->buf, buf + I2C_RD_IDX, msg_temp->len);
+ } else {
+ buf[I2C_W_CNT_IDX] = msg_temp->len;
+ memcpy(buf + I2C_WR_IDX, msg_temp->buf, msg_temp->len);
+ ret = nct6694_write_msg(data->nct6694, REQUEST_I2C_MOD,
+ REQUEST_I2C_OFFSET, REQUEST_I2C_LEN,
+ buf);
+ 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;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ data->port = cell->id;
+ data->br = I2C_BR_100K;
+
+ sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", cell->id);
+ data->adapter.owner = THIS_MODULE;
+ data->adapter.algo = &algorithm;
+ data->adapter.dev.parent = &pdev->dev;
+ data->adapter.algo_data = data;
+
+ platform_set_drvdata(pdev, data);
+
+ ret = i2c_add_adapter(&data->adapter);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register I2C Adapter: %pe\n",
+ __func__, ERR_PTR(ret));
+ }
+
+ return ret;
+}
+
+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 = DRVNAME,
+ },
+ .probe = nct6694_i2c_probe,
+ .remove = nct6694_i2c_remove,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_i2c_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_i2c_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_i2c_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (2 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 12:12 ` Marc Kleine-Budde
` (3 more replies)
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
` (5 subsequent siblings)
9 siblings, 4 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 843 ++++++++++++++++++++++++++++++++
4 files changed, 855 insertions(+)
create mode 100644 drivers/net/can/nct6694_canfd.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1cc64f9f154a..eccd5e795daa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16441,6 +16441,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..569feda37731 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..825c011aead5 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) += nct6604_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..d873a9fd656d
--- /dev/null
+++ b/drivers/net/can/nct6694_canfd.c
@@ -0,0 +1,843 @@
+// 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/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-can"
+
+#define RX_QUATA 64
+
+/* Host interface */
+#define REQUEST_CAN_MOD 0x05
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_CAN_CMD0_LEN 0x18
+#define REQUEST_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000)
+#define CAN_NBR_IDX 0x00
+#define CAN_DBR_IDX 0x04
+#define CAN_CTRL1_IDX 0x0C
+#define CAN_CTRL1_MON BIT(0)
+#define CAN_CTRL1_NISO BIT(1)
+#define CAN_CTRL1_LBCK BIT(2)
+
+/* Command 01h */
+#define REQUEST_CAN_CMD1_LEN 0x08
+#define REQUEST_CAN_CMD1_OFFSET 0x0001
+#define CAN_CLK_IDX 0x04
+#define CAN_CLK_LEN 0x04
+
+/* Command 02h */
+#define REQUEST_CAN_CMD2_LEN 0x10
+#define REQUEST_CAN_CMD2_OFFSET(idx, mask) \
+ ({ typeof(mask) mask_ = (mask); \
+ idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) : \
+ ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); })
+
+#define CAN_ERR_IDX(idx) (idx ? 0x08 : 0x00) /* Read-clear */
+#define CAN_STATUS_IDX(idx) (idx ? 0x09 : 0x01)
+#define CAN_TX_EVT_IDX(idx) (idx ? 0x0A : 0x02)
+#define CAN_RX_EVT_IDX(idx) (idx ? 0x0B : 0x03)
+#define CAN_REC_IDX(idx) (idx ? 0x0C : 0x04)
+#define CAN_TEC_IDX(idx) (idx ? 0x0D : 0x05)
+#define CAN_EVENT_ERR BIT(0)
+#define CAN_EVENT_STATUS BIT(1)
+#define CAN_EVENT_TX_EVT BIT(2)
+#define CAN_EVENT_RX_EVT BIT(3)
+#define CAN_EVENT_REC BIT(4)
+#define CAN_EVENT_TEC BIT(5)
+#define CAN_EVENT_ERR_NO_ERROR 0x00 /* Read-clear */
+#define CAN_EVENT_ERR_CRC_ERROR 0x01 /* Read-clear */
+#define CAN_EVENT_ERR_STUFF_ERROR 0x02 /* Read-clear */
+#define CAN_EVENT_ERR_ACK_ERROR 0x03 /* Read-clear */
+#define CAN_EVENT_ERR_FORM_ERROR 0x04 /* Read-clear */
+#define CAN_EVENT_ERR_BIT_ERROR 0x05 /* Read-clear */
+#define CAN_EVENT_ERR_TIMEOUT_ERROR 0x06 /* Read-clear */
+#define CAN_EVENT_ERR_UNKNOWN_ERROR 0x07 /* Read-clear */
+#define CAN_EVENT_STATUS_ERROR_ACTIVE 0x00
+#define CAN_EVENT_STATUS_ERROR_PASSIVE 0x01
+#define CAN_EVENT_STATUS_BUS_OFF 0x02
+#define CAN_EVENT_STATUS_WARNING 0x03
+#define CAN_EVENT_TX_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */
+#define CAN_EVENT_RX_EVT_DATA_LOST BIT(5) /* Read-clear */
+#define CAN_EVENT_RX_EVT_HALF_FULL BIT(6) /* Read-clear */
+#define CAN_EVENT_RX_EVT_DATA_IN BIT(7)
+
+/* Command 10h */
+#define REQUEST_CAN_CMD10_LEN 0x90
+#define REQUEST_CAN_CMD10_OFFSET(buf_cnt) \
+ (((buf_cnt) & 0xFF) << 8 | 0x10)
+#define CAN_TAG_IDX 0x00
+#define CAN_FLAG_IDX 0x01
+#define CAN_DLC_IDX 0x03
+#define CAN_ID_IDX 0x04
+#define CAN_DATA_IDX 0x08
+#define CAN_TAG_CAN0 0xC0
+#define CAN_TAG_CAN1 0xC1
+#define CAN_FLAG_EFF BIT(0)
+#define CAN_FLAG_RTR BIT(1)
+#define CAN_FLAG_FD BIT(2)
+#define CAN_FLAG_BRS BIT(3)
+#define CAN_FLAG_ERR BIT(4)
+
+/* Command 11h */
+#define REQUEST_CAN_CMD11_LEN 0x90
+#define REQUEST_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); })
+
+struct nct6694_canfd_priv {
+ struct can_priv can; /* must be the first member */
+
+ struct nct6694 *nct6694;
+ struct net_device *ndev;
+ struct work_struct rx_work;
+ struct work_struct tx_work;
+ unsigned char data_buf[REQUEST_CAN_CMD10_LEN];
+ unsigned char can_idx;
+};
+
+static inline void set_buf16(void *buf, u16 u16_val)
+{
+ u8 *p = (u8 *)buf;
+
+ p[0] = u16_val & 0xFF;
+ p[1] = (u16_val >> 8) & 0xFF;
+}
+
+static inline void set_buf32(void *buf, u32 u32_val)
+{
+ u8 *p = (u8 *)buf;
+
+ p[0] = u32_val & 0xFF;
+ p[1] = (u32_val >> 8) & 0xFF;
+ p[2] = (u32_val >> 16) & 0xFF;
+ p[3] = (u32_val >> 24) & 0xFF;
+}
+
+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_set_bittiming(struct net_device *ndev,
+ unsigned char *buf)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ const struct can_bittiming *n_bt = &priv->can.bittiming;
+ const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+
+ set_buf32(&buf[CAN_NBR_IDX], n_bt->bitrate);
+ set_buf32(&buf[CAN_DBR_IDX], d_bt->bitrate);
+
+ pr_info("%s: can(%d): NBR = %d, DBR = %d\n", __func__, priv->can_idx,
+ n_bt->bitrate, d_bt->bitrate);
+}
+
+static int nct6694_canfd_start(struct net_device *ndev)
+{
+ int ret;
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ unsigned char buf[REQUEST_CAN_CMD0_LEN] = {0};
+ u16 temp = 0;
+
+ nct6694_canfd_set_bittiming(ndev, buf);
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ temp |= CAN_CTRL1_MON;
+
+ if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+ priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+ temp |= CAN_CTRL1_NISO;
+
+ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ temp |= CAN_CTRL1_LBCK;
+
+ set_buf16(&buf[CAN_CTRL1_IDX], temp);
+
+ ret = nct6694_write_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD0_OFFSET(priv->can_idx),
+ REQUEST_CAN_CMD0_LEN, buf);
+ if (ret < 0) {
+ pr_err("%s: Failed to set data bittiming\n", __func__);
+ return ret;
+ }
+
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ return 0;
+}
+
+static void nct6694_canfd_stop(struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+
+ priv->can.state = CAN_STATE_STOPPED;
+}
+
+static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ switch (mode) {
+ case CAN_MODE_START:
+ nct6694_canfd_start(ndev);
+ netif_wake_queue(ndev);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+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);
+ unsigned char mask = CAN_EVENT_REC | CAN_EVENT_TEC;
+ unsigned char buf[REQUEST_CAN_CMD2_LEN] = {0};
+ int ret;
+
+ ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD2_OFFSET(priv->can_idx, mask),
+ REQUEST_CAN_CMD2_LEN, 0x00,
+ REQUEST_CAN_CMD2_LEN, (unsigned char *)&buf);
+ if (ret < 0)
+ return -EINVAL;
+
+ bec->rxerr = buf[CAN_REC_IDX(priv->can_idx)];
+ bec->txerr = buf[CAN_TEC_IDX(priv->can_idx)];
+
+ return 0;
+}
+
+static int nct6694_canfd_open(struct net_device *ndev)
+{
+ int ret;
+
+ ret = open_candev(ndev);
+ if (ret)
+ return ret;
+
+ ret = nct6694_canfd_start(ndev);
+ if (ret) {
+ close_candev(ndev);
+ return ret;
+ }
+
+ netif_start_queue(ndev);
+
+ return 0;
+}
+
+static int nct6694_canfd_close(struct net_device *ndev)
+{
+ netif_stop_queue(ndev);
+ nct6694_canfd_stop(ndev);
+ close_candev(ndev);
+
+ return 0;
+}
+
+static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ struct nct6694 *nct6694 = priv->nct6694;
+ struct canfd_frame *cf = (struct canfd_frame *)skb->data;
+ struct net_device_stats *stats = &ndev->stats;
+ int can_idx = priv->can_idx;
+ u32 txid = 0;
+ int i;
+ unsigned int echo_byte;
+ u8 data_buf[REQUEST_CAN_CMD10_LEN] = {0};
+
+ if (can_dropped_invalid_skb(ndev, skb))
+ return NETDEV_TX_OK;
+
+ /*
+ * No check for NCT66794 because the TX bit is read-clear
+ * and may be read-cleared by other function
+ * Just check the result of tx command.
+ */
+ /* Check if the TX buffer is full */
+ netif_stop_queue(ndev);
+
+ if (can_idx == 0)
+ data_buf[CAN_TAG_IDX] = CAN_TAG_CAN0;
+ else
+ data_buf[CAN_TAG_IDX] = 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.
+ */
+ data_buf[CAN_FLAG_IDX] |= CAN_FLAG_EFF;
+ } else {
+ txid = cf->can_id & CAN_SFF_MASK;
+ }
+
+ set_buf32(&data_buf[CAN_ID_IDX], txid);
+
+ data_buf[CAN_DLC_IDX] = cf->len;
+
+ if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) {
+ data_buf[CAN_FLAG_IDX] |= CAN_FLAG_FD;
+ if (cf->flags & CANFD_BRS)
+ data_buf[CAN_FLAG_IDX] |= CAN_FLAG_BRS;
+ }
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ data_buf[CAN_FLAG_IDX] |= CAN_FLAG_RTR;
+
+ /* set data to buf */
+ for (i = 0; i < cf->len; i++)
+ data_buf[CAN_DATA_IDX + i] = *(u8 *)(cf->data + i);
+
+ can_put_echo_skb(skb, ndev, 0, 0);
+
+ memcpy(priv->data_buf, data_buf, REQUEST_CAN_CMD10_LEN);
+ queue_work(nct6694->async_workqueue, &priv->tx_work);
+
+ stats->tx_bytes += cf->len;
+ stats->tx_packets++;
+ echo_byte = can_get_echo_skb(ndev, 0, NULL);
+
+ netif_wake_queue(ndev);
+
+ return NETDEV_TX_OK;
+}
+
+static void nct6694_canfd_tx_work(struct work_struct *work)
+{
+ struct nct6694_canfd_priv *priv;
+
+ priv = container_of(work, struct nct6694_canfd_priv, tx_work);
+
+ nct6694_write_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD10_OFFSET(1),
+ REQUEST_CAN_CMD10_LEN,
+ priv->data_buf);
+}
+
+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;
+
+ pr_info("%s: CAN_ERR_CRTL_RX_OVERFLOW\r\n", __func__);
+
+ 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 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 data_buf[REQUEST_CAN_CMD11_LEN] = {0};
+ u8 fd_format = 0;
+ int i;
+
+ ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD11_OFFSET(can_idx, 1),
+ REQUEST_CAN_CMD11_LEN, 0, REQUEST_CAN_CMD11_LEN,
+ data_buf);
+ if (ret < 0)
+ return;
+
+ /* Check type of frame and create skb */
+ fd_format = data_buf[CAN_FLAG_IDX] & 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 = data_buf[CAN_DLC_IDX];
+
+ /* Get ID and set flag by its type(Standard ID format or Ext ID format) */
+ id = le32_to_cpu(*(u32 *)(&data_buf[CAN_ID_IDX]));
+ if (data_buf[CAN_FLAG_IDX] & 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 (data_buf[CAN_FLAG_IDX] & CAN_FLAG_ERR) {
+ cf->flags |= CANFD_ESI;
+ netdev_dbg(ndev, "ESI Error\n");
+ }
+
+ /* Set RTR and BRS */
+ if (!fd_format && (data_buf[CAN_FLAG_IDX] & CAN_FLAG_RTR)) {
+ cf->can_id |= CAN_RTR_FLAG;
+ } else {
+ if (data_buf[CAN_FLAG_IDX] & CAN_FLAG_BRS)
+ cf->flags |= CANFD_BRS;
+
+ for (i = 0; i < cf->len; i++)
+ *(u8 *)(cf->data + i) = data_buf[CAN_DATA_IDX + 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);
+ int can_idx = priv->can_idx;
+ u32 pkts = 0;
+ u8 data_buf_can_evt[REQUEST_CAN_CMD2_LEN] = {0};
+ u8 mask_rx = CAN_EVENT_RX_EVT;
+ u8 rx_evt;
+
+ for (;;) {
+ nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD2_OFFSET(can_idx, mask_rx),
+ REQUEST_CAN_CMD2_LEN, 0,
+ REQUEST_CAN_CMD2_LEN, data_buf_can_evt);
+
+ /* Handle lost messages when handling RX because it is read-cleared reg */
+ rx_evt = data_buf_can_evt[CAN_RX_EVT_IDX(can_idx)];
+ if (rx_evt & CAN_EVENT_RX_EVT_DATA_LOST)
+ nuv_canfd_handle_lost_msg(ndev);
+
+ /* No data */
+ if ((rx_evt & CAN_EVENT_RX_EVT_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 == CAN_EVENT_ERR_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 CAN_EVENT_ERR_CRC_ERROR:
+ cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
+ break;
+
+ case CAN_EVENT_ERR_STUFF_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+
+ case CAN_EVENT_ERR_ACK_ERROR:
+ cf->data[3] = CAN_ERR_PROT_LOC_ACK;
+ break;
+
+ case CAN_EVENT_ERR_FORM_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+
+ case CAN_EVENT_ERR_BIT_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_BIT |
+ CAN_ERR_PROT_BIT0 |
+ CAN_ERR_PROT_BIT1;
+ break;
+
+ case CAN_EVENT_ERR_TIMEOUT_ERROR:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ break;
+
+ case CAN_EVENT_ERR_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,
+ u8 *can_evt_buf)
+{
+ struct nct6694_canfd_priv *priv = netdev_priv(ndev);
+ int can_idx = priv->can_idx;
+ int work_done = 0;
+ u8 can_status;
+
+ can_status = can_evt_buf[CAN_STATUS_IDX(can_idx)];
+
+ if (can_status == CAN_EVENT_STATUS_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 == CAN_EVENT_STATUS_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 == CAN_EVENT_STATUS_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 == CAN_EVENT_STATUS_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 void nct6694_canfd_rx_work(struct work_struct *work)
+{
+ struct nct6694_canfd_priv *priv;
+ struct net_device *ndev;
+ struct net_device_stats *stats;
+ int ret, can_idx;
+ int work_done = 0;
+ int quota = RX_QUATA;
+ u8 data_buf_can_evt[REQUEST_CAN_CMD2_LEN] = {0};
+ u8 bus_err;
+ u8 mask_sts = CAN_EVENT_ERR | CAN_EVENT_STATUS;
+
+ priv = container_of(work, struct nct6694_canfd_priv, rx_work);
+ ndev = priv->ndev;
+ can_idx = priv->can_idx;
+ stats = &ndev->stats;
+
+ ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD2_OFFSET(can_idx, mask_sts),
+ REQUEST_CAN_CMD2_LEN, 0, REQUEST_CAN_CMD2_LEN,
+ data_buf_can_evt);
+ if (ret < 0)
+ return;
+
+ /* Handle bus state changes */
+ work_done += nct6694_canfd_handle_state_errors(ndev, data_buf_can_evt);
+
+ /* Handle lec errors on the bus */
+ if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
+ bus_err = data_buf_can_evt[CAN_ERR_IDX(can_idx)];
+ work_done += nct6694_canfd_handle_lec_err(ndev, bus_err);
+ }
+
+ /*
+ * Check data lost and handle normal messages on RX.
+ * Don't check rx fifo-empty here because the data-lost bit in the same reg is read-cleared,
+ * we handle it when handling rx event
+ */
+
+ work_done += nct6694_canfd_do_rx_poll(ndev, quota - work_done);
+}
+
+static void nct6694_can_handler(void *private_data)
+{
+ struct nct6694_canfd_priv *priv = private_data;
+ struct nct6694 *nct6694 = priv->nct6694;
+
+ queue_work(nct6694->async_workqueue, &priv->rx_work);
+}
+
+static const struct net_device_ops nct6694_canfd_netdev_ops = {
+ .ndo_open = nct6694_canfd_open,
+ .ndo_stop = nct6694_canfd_close,
+ .ndo_start_xmit = nct6694_canfd_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+static 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;
+ unsigned int can_clk;
+ int ret;
+
+ ret = nct6694_read_msg(nct6694, REQUEST_CAN_MOD,
+ REQUEST_CAN_CMD1_OFFSET,
+ REQUEST_CAN_CMD1_LEN,
+ CAN_CLK_IDX, CAN_CLK_LEN,
+ (unsigned char *)&can_clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "%s: Failed to get can clock frequency: %pe\n",
+ __func__, ERR_PTR(ret));
+ return ret;
+ }
+
+ pr_info("can_clk = %d\n", le32_to_cpu(can_clk));
+
+ ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
+ if (!ndev)
+ return -ENOMEM;
+
+ ndev->flags |= IFF_ECHO;
+ ndev->netdev_ops = &nct6694_canfd_netdev_ops;
+
+ priv = netdev_priv(ndev);
+ priv->nct6694 = nct6694;
+ priv->ndev = ndev;
+
+ ret = nct6694_register_handler(nct6694, CAN_IRQ_STATUS,
+ nct6694_can_handler, priv);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
+ __func__, ERR_PTR(ret));
+ free_candev(ndev);
+ return ret;
+ }
+
+ INIT_WORK(&priv->rx_work, nct6694_canfd_rx_work);
+ INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work);
+
+ priv->can_idx = cell->id;
+ priv->can.state = CAN_STATE_STOPPED;
+ priv->can.clock.freq = le32_to_cpu(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) {
+ dev_err(&pdev->dev, "register_candev failed: %d\n", ret);
+ free_candev(ndev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void nct6694_canfd_remove(struct platform_device *pdev)
+{
+ struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&priv->rx_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,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_canfd_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_canfd_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_canfd_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (3 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 15:32 ` Guenter Roeck
` (2 more replies)
2024-10-24 8:59 ` [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
` (4 subsequent siblings)
9 siblings, 3 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 329 +++++++++++++++++++++++++++++++++
4 files changed, 342 insertions(+)
create mode 100644 drivers/watchdog/nct6694_wdt.c
diff --git a/MAINTAINERS b/MAINTAINERS
index eccd5e795daa..63387c0d4ab6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16442,6 +16442,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 684b9fe84fff..bc9d63d69204 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 ab6f2b41e38e..453ceacd43ab 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -231,6 +231,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..68e2926ec504
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/watchdog.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-wdt"
+
+#define WATCHDOG_TIMEOUT 10
+#define WATCHDOG_PRETIMEOUT 0
+
+/* Host interface */
+#define REQUEST_WDT_MOD 0x07
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_WDT_CMD0_LEN 0x0F
+#define REQUEST_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */
+#define WDT_PRETIMEOUT_IDX 0x00
+#define WDT_PRETIMEOUT_LEN 0x04 /* PRETIMEOUT(3byte) | ACT(1byte) */
+#define WDT_TIMEOUT_IDX 0x04
+#define WDT_TIMEOUT_LEN 0x04 /* TIMEOUT(3byte) | ACT(1byte) */
+#define WDT_COUNTDOWN_IDX 0x0C
+#define WDT_COUNTDOWN_LEN 0x03
+
+#define WDT_PRETIMEOUT_ACT BIT(1)
+#define WDT_TIMEOUT_ACT BIT(1)
+
+/* Command 01h */
+#define REQUEST_WDT_CMD1_LEN 0x04
+#define REQUEST_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */
+#define WDT_CMD_IDX 0x00
+#define WDT_CMD_LEN 0x04
+
+static unsigned int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int 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) ")");
+
+struct nct6694_wdt_data {
+ struct nct6694 *nct6694;
+ struct watchdog_device wdev;
+ unsigned int wdev_idx;
+};
+
+static inline void set_buf32(void *buf, u32 u32_val)
+{
+ u8 *p = (u8 *)buf;
+
+ p[0] = u32_val & 0xFF;
+ p[1] = (u32_val >> 8) & 0xFF;
+ p[2] = (u32_val >> 16) & 0xFF;
+ p[3] = (u32_val >> 24) & 0xFF;
+}
+
+static int nct6694_wdt_start(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+
+ pr_debug("%s: WDT(%d) Start\n", __func__, data->wdev_idx);
+
+ return 0;
+}
+
+static int nct6694_wdt_stop(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'C'};
+ int ret;
+
+ pr_debug("%s: WDT(%d) Close\n", __func__, data->wdev_idx);
+ ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD1_LEN, buf);
+ if (ret)
+ pr_err("%s: Failed to start WDT device!\n", __func__);
+
+ return ret;
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'S'};
+ int ret;
+
+ pr_debug("%s: WDT(%d) Ping\n", __func__, data->wdev_idx);
+ ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD1_LEN, buf);
+ if (ret)
+ pr_err("%s: Failed to ping WDT device!\n", __func__);
+
+ return ret;
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int timeout)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned int timeout_fmt, pretimeout_fmt;
+ unsigned char buf[REQUEST_WDT_CMD0_LEN];
+ int ret;
+
+ if (timeout < wdev->pretimeout) {
+ pr_err("%s: 'timeout' must be greater than 'pre timeout'!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ timeout_fmt = timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
+ pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
+ set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
+ set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
+
+ ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD0_LEN, buf);
+ if (ret) {
+ pr_err("%s: Don't write the setup command in Start stage!\n",
+ __func__);
+ return ret;
+ }
+
+ wdev->timeout = timeout;
+
+ return 0;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+ unsigned int pretimeout)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned int timeout_fmt, pretimeout_fmt;
+ unsigned char buf[REQUEST_WDT_CMD0_LEN];
+ int ret;
+
+ if (pretimeout > wdev->timeout) {
+ pr_err("%s: 'pre timeout' must be less than 'timeout'!\n",
+ __func__);
+ return -EINVAL;
+ }
+ timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
+ pretimeout_fmt = pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
+ set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
+ set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
+
+ ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD0_LEN, buf);
+ if (ret) {
+ pr_err("%s: Don't write the setup command in Start stage!\n", __func__);
+ return ret;
+ }
+
+ wdev->pretimeout = pretimeout;
+ return 0;
+}
+
+static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned char buf[WDT_COUNTDOWN_LEN];
+ unsigned int timeleft_ms;
+ int ret;
+
+ ret = nct6694_read_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD0_LEN, WDT_COUNTDOWN_IDX,
+ WDT_COUNTDOWN_LEN, buf);
+ if (ret)
+ pr_err("%s: Failed to get WDT device!\n", __func__);
+
+ timeleft_ms = ((buf[2] << 16) | (buf[1] << 8) | buf[0]) & 0xFFFFFF;
+
+ return timeleft_ms / 1000;
+}
+
+static int nct6694_wdt_setup(struct watchdog_device *wdev)
+{
+ struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+ struct nct6694 *nct6694 = data->nct6694;
+ unsigned char buf[REQUEST_WDT_CMD0_LEN] = {0};
+ unsigned int timeout_fmt, pretimeout_fmt;
+ int ret;
+
+ if (timeout)
+ wdev->timeout = timeout;
+
+ if (pretimeout) {
+ wdev->pretimeout = pretimeout;
+ pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
+ } else {
+ pretimeout_fmt = 0;
+ }
+
+ timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
+ set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
+ set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
+
+ ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
+ REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
+ REQUEST_WDT_CMD0_LEN, buf);
+ if (ret)
+ return ret;
+
+ pr_info("Setting WDT(%d): timeout = %d, pretimeout = %d\n",
+ data->wdev_idx, wdev->timeout, wdev->pretimeout);
+
+ return 0;
+}
+
+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 nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ struct nct6694_wdt_data *data;
+ struct watchdog_device *wdev;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->nct6694 = nct6694;
+ data->wdev_idx = cell->id;
+
+ wdev = &data->wdev;
+ wdev->info = &nct6694_wdt_info;
+ wdev->ops = &nct6694_wdt_ops;
+ wdev->timeout = WATCHDOG_TIMEOUT;
+ wdev->pretimeout = WATCHDOG_PRETIMEOUT;
+ wdev->min_timeout = 1;
+ wdev->max_timeout = 255;
+
+ platform_set_drvdata(pdev, data);
+
+ /* Register watchdog timer device to WDT framework */
+ watchdog_set_drvdata(&data->wdev, data);
+ watchdog_init_timeout(&data->wdev, timeout, &pdev->dev);
+ watchdog_set_nowayout(&data->wdev, nowayout);
+ watchdog_stop_on_reboot(&data->wdev);
+
+ ret = devm_watchdog_register_device(&pdev->dev, &data->wdev);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register watchdog device: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = nct6694_wdt_setup(&data->wdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to setup WDT device!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver nct6694_wdt_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_wdt_probe,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_wdt_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_wdt_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_wdt_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (4 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-24 9:20 ` Kalesh Anakkur Purayil
2024-10-24 15:03 ` Guenter Roeck
2024-10-24 8:59 ` [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support Ming Yu
` (3 subsequent siblings)
9 siblings, 2 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 407 ++++++++++++++++++++++++++++++++++
4 files changed, 419 insertions(+)
create mode 100644 drivers/hwmon/nct6694-hwmon.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 63387c0d4ab6..2aa87ad84156 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -167,6 +167,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..7d7d22a650b0
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-hwmon"
+
+/* Host interface */
+#define REQUEST_RPT_MOD 0xFF
+#define REQUEST_HWMON_MOD 0x00
+
+/* Report Channel */
+#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
+#define HWMON_FIN_STS(x) (0x6E + (x))
+#define HWMON_PWM_IDX(x) (0x70 + (x))
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_HWMON_CMD0_LEN 0x40
+#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define HWMON_FIN_EN(x) (0x04 + (x))
+#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
+/* Command 02h */
+#define REQUEST_HWMON_CMD2_LEN 0x90
+#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
+#define HWMON_SMI_CTRL_IDX 0x00
+#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
+#define HWMON_CMD2_HYST_MASK 0x1F
+/* Command 03h */
+#define REQUEST_HWMON_CMD3_LEN 0x08
+#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
+
+struct nct6694_hwmon_data {
+ struct nct6694 *nct6694;
+
+ /* Make sure read & write commands are consecutive */
+ struct mutex hwmon_lock;
+};
+
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
+ HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
+
+static const struct hwmon_channel_info *nct6694_info[] = {
+ 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_fan_read(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ unsigned char buf[2];
+ int ret;
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN,
+ HWMON_FIN_EN(channel / 8),
+ 1, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf[0] & BIT(channel % 8) ? 1 : 0;
+
+ break;
+
+ case hwmon_fan_input:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ HWMON_FIN_IDX(channel), 2, 0,
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
+
+ break;
+
+ case hwmon_fan_min:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD2_OFFSET,
+ REQUEST_HWMON_CMD2_LEN,
+ HWMON_FIN_LIMIT_IDX(channel),
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
+
+ break;
+
+ case hwmon_fan_min_alarm:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ HWMON_FIN_STS(channel / 8),
+ 1, 0, 1, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf[0] & BIT(channel % 8);
+
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+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 buf;
+ int ret;
+
+ switch (attr) {
+ case hwmon_pwm_input:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ HWMON_PWM_IDX(channel),
+ 1, 0, 1, &buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf;
+
+ break;
+ case hwmon_pwm_freq:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN,
+ HWMON_PWM_FREQ_IDX(channel),
+ 1, &buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf * 25000 / 255;
+
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+ long val)
+{
+ struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+ unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
+ unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
+ u16 fan_val = (u16)val;
+ int ret;
+
+ switch (attr) {
+ case hwmon_fan_enable:
+ mutex_lock(&data->hwmon_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN, 0,
+ REQUEST_HWMON_CMD0_LEN,
+ enable_buf);
+ if (ret)
+ goto err;
+
+ if (val)
+ enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
+ else
+ enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN, enable_buf);
+ if (ret)
+ goto err;
+
+ break;
+
+ case hwmon_fan_min:
+ mutex_lock(&data->hwmon_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD2_OFFSET,
+ REQUEST_HWMON_CMD2_LEN, 0,
+ REQUEST_HWMON_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
+ buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
+ ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD2_OFFSET,
+ REQUEST_HWMON_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ goto err;
+ }
+
+err:
+ mutex_unlock(&data->hwmon_lock);
+ return ret;
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ 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;
+ }
+
+ return 0;
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_fan:
+ return nct6694_fan_write(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ 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_input:
+ case hwmon_pwm_freq:
+ return 0444;
+ default:
+ return 0;
+ }
+
+ default:
+ return 0;
+ }
+
+ 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)
+{
+ unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
+ int ret;
+
+ /* Set Fan input Real Time alarm mode */
+ mutex_lock(&data->hwmon_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD2_OFFSET,
+ REQUEST_HWMON_CMD2_LEN, 0,
+ REQUEST_HWMON_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ buf[HWMON_SMI_CTRL_IDX] = 0x02;
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD2_OFFSET,
+ REQUEST_HWMON_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+err:
+ mutex_unlock(&data->hwmon_lock);
+ return ret;
+}
+
+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->nct6694 = nct6694;
+ mutex_init(&data->hwmon_lock);
+ platform_set_drvdata(pdev, data);
+
+ ret = nct6694_hwmon_init(data);
+ if (ret)
+ return -EIO;
+
+ /* Register hwmon device to HWMON framework */
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "nct6694", data,
+ &nct6694_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev)) {
+ dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
+ __func__);
+ return PTR_ERR(hwmon_dev);
+ }
+
+ return 0;
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_hwmon_probe,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_hwmon_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_hwmon_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_hwmon_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (5 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-26 14:41 ` Jonathan Cameron
2024-10-24 8:59 ` [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support Ming Yu
` (2 subsequent siblings)
9 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
This driver supports IIO functionality for NCT6694 MFD device
based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/nct6694_adc.c | 616 ++++++++++++++++++++++++++++++++++
4 files changed, 628 insertions(+)
create mode 100644 drivers/iio/adc/nct6694_adc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2aa87ad84156..5c350eac187d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16440,6 +16440,7 @@ L: linux-kernel@vger.kernel.org
S: Supported
F: drivers/gpio/gpio-nct6694.c
F: drivers/hwmon/nct6694-hwmon.c
+F: drivers/iio/adc/nct6694_adc.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/nct6694_canfd.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6c4e74420fd2..302511d166db 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1018,6 +1018,16 @@ config NPCM_ADC
This driver can also be built as a module. If so, the module
will be called npcm_adc.
+config NCT6694_ADC
+ tristate "Nuvoton NCT6694 ADC driver"
+ depends on MFD_NCT6694
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to ADC.
+
+ This driver can also be built as a module. If so, the module
+ will be called nct6694_adc.
+
config PAC1921
tristate "Microchip Technology PAC1921 driver"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7b91cd98c0e0..db419f77365c 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
+obj-$(CONFIG_NCT6694_ADC) += nct6694_adc.o
obj-$(CONFIG_PAC1921) += pac1921.o
obj-$(CONFIG_PAC1934) += pac1934.o
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
diff --git a/drivers/iio/adc/nct6694_adc.c b/drivers/iio/adc/nct6694_adc.c
new file mode 100644
index 000000000000..de4cddc8addc
--- /dev/null
+++ b/drivers/iio/adc/nct6694_adc.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 IIO driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-iio"
+
+/* Host interface */
+#define REQUEST_RPT_MOD 0xFF
+#define REQUEST_IIO_MOD 0x00
+
+/* Report Channel */
+#define IIO_VIN_STS(x) (0x68 + (x))
+#define IIO_TMP_STS(x) (0x6A + (x))
+#define IIO_TMP_STS_CH(x) \
+ ({ typeof(x) x_ = x; \
+ (x_ < 10) ? x_ : x_ + 6; })
+
+/* Message Channel*/
+/* Command 00h */
+#define REQUEST_IIO_CMD0_LEN 0x40
+#define REQUEST_IIO_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define IIO_VIN_EN(x) (0x00 + (x))
+#define IIO_TMP_EN(x) (0x02 + (x))
+/* Command 02h */
+#define REQUEST_IIO_CMD2_LEN 0x90
+#define REQUEST_IIO_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
+#define IIO_SMI_CTRL_IDX 0x00
+#define IIO_VIN_LIMIT_IDX(x) (0x10 + ((x) * 2))
+#define IIO_TMP_LIMIT_IDX(x) (0x30 + ((x) * 2))
+#define IIO_CMD2_HYST_MASK 0x1F
+/* Command 03h */
+#define REQUEST_IIO_CMD3_LEN 0x08
+#define REQUEST_IIO_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
+
+struct nct6694_iio_data {
+ struct nct6694 *nct6694;
+
+ /* Make sure read & write commands are consecutive */
+ struct mutex iio_lock;
+};
+
+static const struct iio_event_spec nct6694_volt_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }
+};
+
+static const struct iio_event_spec nct6694_temp_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }
+};
+
+#define NCT6694_VOLTAGE_CHANNEL(num, addr) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = num, \
+ .address = addr, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
+ BIT(IIO_CHAN_INFO_PROCESSED), \
+ .event_spec = nct6694_volt_events, \
+ .num_event_specs = ARRAY_SIZE(nct6694_volt_events), \
+}
+
+#define NCT6694_TEMPERATURE_CHANNEL(num, addr) { \
+ .type = IIO_TEMP, \
+ .indexed = 1, \
+ .channel = num, \
+ .address = addr, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
+ BIT(IIO_CHAN_INFO_PROCESSED) | \
+ BIT(IIO_CHAN_INFO_HYSTERESIS), \
+ .event_spec = nct6694_temp_events, \
+ .num_event_specs = ARRAY_SIZE(nct6694_temp_events), \
+}
+
+static const struct iio_chan_spec nct6694_iio_channels[] = {
+ NCT6694_VOLTAGE_CHANNEL(0, 0x0), /* VIN0 */
+ NCT6694_VOLTAGE_CHANNEL(1, 0x1), /* VIN1 */
+ NCT6694_VOLTAGE_CHANNEL(2, 0x2), /* VIN2 */
+ NCT6694_VOLTAGE_CHANNEL(3, 0x3), /* VIN3 */
+ NCT6694_VOLTAGE_CHANNEL(4, 0x4), /* VIN5 */
+ NCT6694_VOLTAGE_CHANNEL(5, 0x5), /* VIN6 */
+ NCT6694_VOLTAGE_CHANNEL(6, 0x6), /* VIN7 */
+ NCT6694_VOLTAGE_CHANNEL(7, 0x7), /* VIN14 */
+ NCT6694_VOLTAGE_CHANNEL(8, 0x8), /* VIN15 */
+ NCT6694_VOLTAGE_CHANNEL(9, 0x9), /* VIN16 */
+ NCT6694_VOLTAGE_CHANNEL(10, 0xA), /* VBAT */
+ NCT6694_VOLTAGE_CHANNEL(11, 0xB), /* VSB */
+ NCT6694_VOLTAGE_CHANNEL(12, 0xC), /* AVSB */
+ NCT6694_VOLTAGE_CHANNEL(13, 0xD), /* VCC */
+ NCT6694_VOLTAGE_CHANNEL(14, 0xE), /* VHIF */
+ NCT6694_VOLTAGE_CHANNEL(15, 0xF), /* VTT */
+
+ NCT6694_TEMPERATURE_CHANNEL(0, 0x10), /* THR1 */
+ NCT6694_TEMPERATURE_CHANNEL(1, 0x12), /* THR2 */
+ NCT6694_TEMPERATURE_CHANNEL(2, 0x14), /* THR14 */
+ NCT6694_TEMPERATURE_CHANNEL(3, 0x16), /* THR15 */
+ NCT6694_TEMPERATURE_CHANNEL(4, 0x18), /* THR16 */
+ NCT6694_TEMPERATURE_CHANNEL(5, 0x1A), /* TDP0 */
+ NCT6694_TEMPERATURE_CHANNEL(6, 0x1C), /* TDP1 */
+ NCT6694_TEMPERATURE_CHANNEL(7, 0x1E), /* TDP2 */
+ NCT6694_TEMPERATURE_CHANNEL(8, 0x20), /* TDP3 */
+ NCT6694_TEMPERATURE_CHANNEL(9, 0x22), /* TDP4 */
+
+ NCT6694_TEMPERATURE_CHANNEL(10, 0x30), /* DTIN0 */
+ NCT6694_TEMPERATURE_CHANNEL(11, 0x32), /* DTIN1 */
+ NCT6694_TEMPERATURE_CHANNEL(12, 0x34), /* DTIN2 */
+ NCT6694_TEMPERATURE_CHANNEL(13, 0x36), /* DTIN3 */
+ NCT6694_TEMPERATURE_CHANNEL(14, 0x38), /* DTIN4 */
+ NCT6694_TEMPERATURE_CHANNEL(15, 0x3A), /* DTIN5 */
+ NCT6694_TEMPERATURE_CHANNEL(16, 0x3C), /* DTIN6 */
+ NCT6694_TEMPERATURE_CHANNEL(17, 0x3E), /* DTIN7 */
+ NCT6694_TEMPERATURE_CHANNEL(18, 0x40), /* DTIN8 */
+ NCT6694_TEMPERATURE_CHANNEL(19, 0x42), /* DTIN9 */
+ NCT6694_TEMPERATURE_CHANNEL(20, 0x44), /* DTIN10 */
+ NCT6694_TEMPERATURE_CHANNEL(21, 0x46), /* DTIN11 */
+ NCT6694_TEMPERATURE_CHANNEL(22, 0x48), /* DTIN12 */
+ NCT6694_TEMPERATURE_CHANNEL(23, 0x4A), /* DTIN13 */
+ NCT6694_TEMPERATURE_CHANNEL(24, 0x4C), /* DTIN14 */
+ NCT6694_TEMPERATURE_CHANNEL(25, 0x4E), /* DTIN15 */
+};
+
+static int nct6694_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct nct6694_iio_data *data = iio_priv(indio_dev);
+ unsigned char buf[2], tmp_hyst, enable_idx;
+ int ret;
+
+ if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+ return -EOPNOTSUPP;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ enable_idx = IIO_VIN_EN(chan->channel / 8);
+ break;
+
+ case IIO_TEMP:
+ enable_idx = IIO_TMP_EN(chan->channel / 8);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD0_OFFSET,
+ REQUEST_IIO_CMD0_LEN, enable_idx,
+ 1, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf[0] & BIT(chan->channel % 8) ? 1 : 0;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ chan->address, 2, 0, 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_VOLTAGE: /* in micro Voltage */
+ *val = buf[0] * 16;
+
+ return IIO_VAL_INT;
+
+ case IIO_TEMP: /* in milli degrees Celsius */
+ *val = (signed char)buf[0] * 1000;
+ *val += buf[1] & 0x80 ? 500 : 0;
+ *val += buf[1] & 0x40 ? 250 : 0;
+ *val += buf[1] & 0x20 ? 125 : 0;
+
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN,
+ IIO_TMP_LIMIT_IDX(chan->channel),
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_TEMP: /* in milli degrees Celsius */
+ tmp_hyst = buf[0] >> 5;
+ *val = (buf[1] - tmp_hyst) * 1000;
+
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int nct6694_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct nct6694_iio_data *data = iio_priv(indio_dev);
+ unsigned char enable_buf[REQUEST_IIO_CMD0_LEN] = {0};
+ unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+ unsigned char delta_hyst;
+ int ret;
+
+ if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+ return -EOPNOTSUPP;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ mutex_lock(&data->iio_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD0_OFFSET,
+ REQUEST_IIO_CMD0_LEN, 0,
+ REQUEST_IIO_CMD0_LEN,
+ enable_buf);
+ if (ret)
+ goto err;
+
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (val) {
+ enable_buf[IIO_VIN_EN(chan->channel / 8)] |=
+ BIT(chan->channel % 8);
+ } else {
+ enable_buf[IIO_VIN_EN(chan->channel / 8)] &=
+ ~BIT(chan->channel % 8);
+ }
+
+ break;
+
+ case IIO_TEMP:
+ if (val) {
+ enable_buf[IIO_TMP_EN(chan->channel / 8)] |=
+ BIT(chan->channel % 8);
+ } else {
+ enable_buf[IIO_TMP_EN(chan->channel / 8)] &=
+ ~BIT(chan->channel % 8);
+ }
+
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD0_OFFSET,
+ REQUEST_IIO_CMD0_LEN, enable_buf);
+ if (ret)
+ goto err;
+
+ break;
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_TEMP:
+ mutex_lock(&data->iio_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, 0,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ delta_hyst = buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] - (u8)val;
+ if (delta_hyst > 7) {
+ pr_err("%s: The Hysteresis value must be less than 7!\n",
+ __func__);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ buf[IIO_TMP_LIMIT_IDX(chan->channel)] &= IIO_CMD2_HYST_MASK;
+ buf[IIO_TMP_LIMIT_IDX(chan->channel)] |= (delta_hyst << 5);
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+ ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+err:
+ mutex_unlock(&data->iio_lock);
+ return ret;
+}
+
+static int nct6694_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct nct6694_iio_data *data = iio_priv(indio_dev);
+ unsigned char buf, ch;
+ int ret;
+
+ if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+ return -EOPNOTSUPP;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ IIO_VIN_STS(chan->channel / 8),
+ 1, 0, 1, &buf);
+ if (ret)
+ return -EINVAL;
+
+ return !!(buf & BIT(chan->channel % 8));
+
+ case IIO_TEMP:
+ ch = (u8)IIO_TMP_STS_CH(chan->channel);
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
+ IIO_TMP_STS(ch / 8),
+ 1, 0, 1, &buf);
+ if (ret)
+ return -EINVAL;
+
+ return !!(buf & BIT(ch % 8));
+
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nct6694_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct nct6694_iio_data *data = iio_priv(indio_dev);
+ unsigned char buf[2];
+ int ret;
+
+ if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+ return -EOPNOTSUPP;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN,
+ IIO_VIN_LIMIT_IDX(chan->channel),
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf[0] * 16;
+ return IIO_VAL_INT;
+
+ case IIO_TEMP:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN,
+ IIO_TMP_LIMIT_IDX(chan->channel),
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = (signed char)buf[1] * 1000;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_EV_DIR_FALLING:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN,
+ IIO_VIN_LIMIT_IDX(chan->channel),
+ 2, buf);
+ if (ret)
+ return -EINVAL;
+
+ *val = buf[1] * 16;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int nct6694_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct nct6694_iio_data *data = iio_priv(indio_dev);
+ unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+ int ret;
+
+ if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&data->iio_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, 0,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ buf[IIO_VIN_LIMIT_IDX(chan->channel)] = (u8)val;
+ break;
+
+ case IIO_TEMP:
+ buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] = (s8)val;
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+
+ case IIO_EV_DIR_FALLING:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ buf[IIO_VIN_LIMIT_IDX(chan->channel) + 1] = (u8)val;
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+err:
+ mutex_unlock(&data->iio_lock);
+ return ret;
+}
+
+static const struct iio_info nct6694_iio_info = {
+ .read_raw = nct6694_read_raw,
+ .write_raw = nct6694_write_raw,
+ .read_event_config = nct6694_read_event_config,
+ .read_event_value = nct6694_read_event_value,
+ .write_event_value = nct6694_write_event_value,
+};
+
+static int nct6694_iio_init(struct nct6694_iio_data *data)
+{
+ unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
+ int ret;
+
+ /* Set VIN & TMP Real Time alarm mode */
+ mutex_lock(&data->iio_lock);
+ ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, 0,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+ buf[IIO_SMI_CTRL_IDX] = 0x02;
+ ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
+ REQUEST_IIO_CMD2_OFFSET,
+ REQUEST_IIO_CMD2_LEN, buf);
+ if (ret)
+ goto err;
+
+err:
+ mutex_unlock(&data->iio_lock);
+ return ret;
+}
+
+static int nct6694_iio_probe(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev;
+ struct nct6694_iio_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->nct6694 = nct6694;
+ mutex_init(&data->iio_lock);
+ platform_set_drvdata(pdev, data);
+
+ ret = nct6694_iio_init(data);
+ if (ret)
+ return -EIO;
+
+ indio_dev->name = pdev->name;
+ indio_dev->channels = nct6694_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(nct6694_iio_channels);
+ indio_dev->info = &nct6694_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ /* Register iio device to IIO framework */
+ ret = devm_iio_device_register(&pdev->dev, indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register iio device!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver nct6694_iio_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_iio_probe,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_iio_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_iio_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_iio_driver);
+}
+module_exit(nct6694_exit);
+
+MODULE_DESCRIPTION("USB-IIO driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (6 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-11-22 18:05 ` Uwe Kleine-König
2024-10-24 8:59 ` [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
2024-10-24 11:57 ` [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Marc Kleine-Budde
9 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
This driver supports PWM functionality for NCT6694 MFD device
based on USB interface.
Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
---
MAINTAINERS | 1 +
drivers/pwm/Kconfig | 10 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-nct6694.c | 245 ++++++++++++++++++++++++++++++++++++++
4 files changed, 257 insertions(+)
create mode 100644 drivers/pwm/pwm-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 5c350eac187d..4d5a5eded3b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16444,6 +16444,7 @@ F: drivers/iio/adc/nct6694_adc.c
F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/nct6694_canfd.c
+F: drivers/pwm/pwm-nct6694.c
F: drivers/watchdog/nct6694_wdt.c
F: include/linux/mfd/nct6694.h
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 0915c1e7df16..00b5eb13f99d 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -471,6 +471,16 @@ config PWM_NTXEC
controller found in certain e-book readers designed by the original
design manufacturer Netronix.
+config PWM_NCT6694
+ tristate "Nuvoton NCT6694 PWM support"
+ depends on MFD_NCT6694
+ help
+ If you say yes to this option, support will be included for Nuvoton
+ NCT6694, a USB device to PWM controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called pwm-nct6694.
+
config PWM_OMAP_DMTIMER
tristate "OMAP Dual-Mode Timer PWM support"
depends on OF
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9081e0c0e9e0..5c5602b79402 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o
obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
+obj-$(CONFIG_PWM_NCT6694) += pwm-nct6694.o
obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
diff --git a/drivers/pwm/pwm-nct6694.c b/drivers/pwm/pwm-nct6694.c
new file mode 100644
index 000000000000..915a2ab50834
--- /dev/null
+++ b/drivers/pwm/pwm-nct6694.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 PWM driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-pwm"
+
+#define NR_PWM 10
+#define MAX_PERIOD_NS 40000 /* PWM Maximum Frequency = 25kHz */
+#define PERIOD_NS_CONST 10200000 /* Period_ns to Freq_reg */
+
+/* Host interface */
+#define REQUEST_RPT_MOD 0xFF
+#define REQUEST_HWMON_MOD 0x00
+#define REQUEST_PWM_MOD 0x01
+
+/* Report Channel */
+#define HWMON_PWM_IDX(x) (0x70 + (x))
+
+/* Message Channel -HWMON */
+/* Command 00h */
+#define REQUEST_HWMON_CMD0_LEN 0x40
+#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define HWMON_PWM_EN(x) (0x06 + (x))
+#define HWMON_PWM_PP(x) (0x08 + (x))
+#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
+
+/* Message Channel -FAN */
+/* Command 00h */
+#define REQUEST_PWM_CMD0_LEN 0x08
+#define REQUEST_PWM_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define PWM_CH_EN(x) (x ? 0x00 : 0x01)
+/* Command 01h */
+#define REQUEST_PWM_CMD1_LEN 0x20
+#define REQUEST_PWM_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
+#define PWM_MAL_EN(x) (x ? 0x00 : 0x01)
+#define PWM_MAL_VAL(x) (0x02 + (x))
+
+/*
+ * Frequency <-> Period
+ * (10^9 * 255) / (25000 * Freq_reg) = Period_ns
+ * 10200000 / Freq_reg = Period_ns
+ *
+ * | Freq_reg | Freq_Hz | Period_ns |
+ * | 1 (01h | 98.039 | 10200000 |
+ * | 2 (02h) | 196.078 | 5100000 |
+ * | 3 (03h) | 294.117 | 3400000 |
+ * | ... |
+ * | ... |
+ * | ... |
+ * | 253 (FDh)| 24803.9 | 40316.20 |
+ * | 254 (FEh)| 24901.9 | 40157.48 |
+ * | 255 (FFh)| 25000 | 40000 |
+ *
+ */
+
+struct nct6694_pwm_data {
+ struct nct6694 *nct6694;
+ unsigned char hwmon_cmd0_buf[REQUEST_HWMON_CMD0_LEN];
+ unsigned char pwm_cmd0_buf[REQUEST_PWM_CMD0_LEN];
+ unsigned char pwm_cmd1_buf[REQUEST_PWM_CMD1_LEN];
+};
+
+static inline struct nct6694_pwm_data *to_nct6694_pwm_data(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int nct6694_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
+ unsigned char ch_enable = data->pwm_cmd0_buf[PWM_CH_EN(pwm->hwpwm / 8)];
+ unsigned char mal_enable = data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)];
+ bool ch_en = ch_enable & BIT(pwm->hwpwm % 8);
+ bool mal_en = mal_enable & BIT(pwm->hwpwm % 8);
+
+ if (!(ch_en && mal_en)) {
+ pr_err("%s: PWM(%d) is running in other mode!\n",
+ __func__, pwm->hwpwm);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nct6694_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
+ unsigned char freq_reg, duty;
+
+ /* Get pwm device initial state */
+ state->enabled = true;
+
+ freq_reg = data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)];
+ state->period = PERIOD_NS_CONST / freq_reg;
+
+ duty = data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)];
+ state->duty_cycle = duty * state->period / 0xFF;
+
+ return 0;
+}
+
+static int nct6694_pwm_apply(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
+ unsigned char freq_reg, duty;
+ int ret;
+
+ if (state->period < MAX_PERIOD_NS)
+ return -EINVAL;
+
+ /* (10^9 * 255) / (25000 * Freq_reg) = Period_ns */
+ freq_reg = (unsigned char)(PERIOD_NS_CONST / state->period);
+ data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)] = freq_reg;
+ ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN,
+ data->hwmon_cmd0_buf);
+ if (ret)
+ return -EIO;
+
+ /* Duty = duty * 0xFF */
+ duty = (unsigned char)(state->duty_cycle * 0xFF / state->period);
+ data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)] = duty;
+ if (state->enabled)
+ data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] |= BIT(pwm->hwpwm % 8);
+ else
+ data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] &= ~BIT(pwm->hwpwm % 8);
+ ret = nct6694_write_msg(data->nct6694, REQUEST_PWM_MOD,
+ REQUEST_PWM_CMD1_OFFSET, REQUEST_PWM_CMD1_LEN,
+ data->pwm_cmd1_buf);
+ if (ret)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct pwm_ops nct6694_pwm_ops = {
+ .request = nct6694_pwm_request,
+ .apply = nct6694_pwm_apply,
+ .get_state = nct6694_pwm_get_state,
+};
+
+static int nct6694_pwm_init(struct nct6694_pwm_data *data)
+{
+ struct nct6694 *nct6694 = data->nct6694;
+ int ret;
+
+ ret = nct6694_read_msg(nct6694, REQUEST_HWMON_MOD,
+ REQUEST_HWMON_CMD0_OFFSET,
+ REQUEST_HWMON_CMD0_LEN, 0,
+ REQUEST_HWMON_CMD0_LEN,
+ data->hwmon_cmd0_buf);
+ if (ret)
+ return ret;
+
+ ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD,
+ REQUEST_PWM_CMD0_OFFSET,
+ REQUEST_PWM_CMD0_LEN, 0,
+ REQUEST_PWM_CMD0_LEN,
+ data->pwm_cmd0_buf);
+ if (ret)
+ return ret;
+
+ ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD,
+ REQUEST_PWM_CMD1_OFFSET,
+ REQUEST_PWM_CMD1_LEN, 0,
+ REQUEST_PWM_CMD1_LEN,
+ data->pwm_cmd1_buf);
+ return ret;
+}
+
+static int nct6694_pwm_probe(struct platform_device *pdev)
+{
+ struct pwm_chip *chip;
+ struct nct6694_pwm_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ chip = devm_pwmchip_alloc(&pdev->dev, NR_PWM, sizeof(*data));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ data = to_nct6694_pwm_data(chip);
+
+ data->nct6694 = nct6694;
+ chip->ops = &nct6694_pwm_ops;
+
+ ret = nct6694_pwm_init(data);
+ if (ret)
+ return -EIO;
+
+ /* Register pwm device to PWM framework */
+ ret = devm_pwmchip_add(&pdev->dev, chip);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register pwm device!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver nct6694_pwm_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_pwm_probe,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_pwm_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_pwm_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_pwm_driver);
+}
+module_exit(nct6694_exit);
+
+MODULE_DESCRIPTION("USB-PWM driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 80+ messages in thread
* [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (7 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support Ming Yu
@ 2024-10-24 8:59 ` Ming Yu
2024-10-25 23:34 ` Nobuhiro Iwamatsu
2024-10-24 11:57 ` [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Marc Kleine-Budde
9 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-24 8:59 UTC (permalink / raw)
To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, 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 | 276 ++++++++++++++++++++++++++++++++++++++
4 files changed, 288 insertions(+)
create mode 100644 drivers/rtc/rtc-nct6694.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4d5a5eded3b9..8de90bda8b5e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16445,6 +16445,7 @@ F: drivers/i2c/busses/i2c-nct6694.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/nct6694_canfd.c
F: drivers/pwm/pwm-nct6694.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..240c496d95f7 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..622bb9fbe6f6
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/nct6694.h>
+
+#define DRVNAME "nct6694-rtc"
+
+/* Host interface */
+#define REQUEST_RTC_MOD 0x08
+
+/* Message Channel */
+/* Command 00h */
+#define REQUEST_RTC_CMD0_LEN 0x07
+#define REQUEST_RTC_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
+#define RTC_SEC_IDX 0x00
+#define RTC_MIN_IDX 0x01
+#define RTC_HOUR_IDX 0x02
+#define RTC_WEEK_IDX 0x03
+#define RTC_DAY_IDX 0x04
+#define RTC_MONTH_IDX 0x05
+#define RTC_YEAR_IDX 0x06
+/* Command 01h */
+#define REQUEST_RTC_CMD1_LEN 0x05
+#define REQUEST_RTC_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
+#define RTC_ALRM_EN_IDX 0x03
+#define RTC_ALRM_PEND_IDX 0x04
+/* Command 02h */
+#define REQUEST_RTC_CMD2_LEN 0x02
+#define REQUEST_RTC_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
+#define RTC_IRQ_EN_IDX 0x00
+#define RTC_IRQ_PEND_IDX 0x01
+
+#define RTC_IRQ_EN (BIT(0) | BIT(5))
+#define RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */
+#define RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */
+#define RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */
+
+struct nct6694_rtc_data {
+ struct nct6694 *nct6694;
+ struct rtc_device *rtc;
+ struct work_struct alarm_work;
+};
+
+static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ unsigned char buf[REQUEST_RTC_CMD0_LEN];
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
+ 0, REQUEST_RTC_CMD0_LEN, buf);
+ if (ret) {
+ pr_err("%s: Failed to get rtc device!\n", __func__);
+ return -EIO;
+ }
+
+ tm->tm_sec = bcd2bin(buf[RTC_SEC_IDX]); /* tm_sec expect 0 ~ 59 */
+ tm->tm_min = bcd2bin(buf[RTC_MIN_IDX]); /* tm_min expect 0 ~ 59 */
+ tm->tm_hour = bcd2bin(buf[RTC_HOUR_IDX]); /* tm_hour expect 0 ~ 23 */
+ tm->tm_wday = bcd2bin(buf[RTC_WEEK_IDX]) - 1; /* tm_wday expect 0 ~ 6 */
+ tm->tm_mday = bcd2bin(buf[RTC_DAY_IDX]); /* tm_mday expect 1 ~ 31 */
+ tm->tm_mon = bcd2bin(buf[RTC_MONTH_IDX]) - 1; /* tm_month expect 0 ~ 11 */
+ tm->tm_year = bcd2bin(buf[RTC_YEAR_IDX]) + 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);
+ unsigned char buf[REQUEST_RTC_CMD0_LEN];
+ int ret;
+
+ buf[RTC_SEC_IDX] = bin2bcd(tm->tm_sec);
+ buf[RTC_MIN_IDX] = bin2bcd(tm->tm_min);
+ buf[RTC_HOUR_IDX] = bin2bcd(tm->tm_hour);
+ buf[RTC_WEEK_IDX] = bin2bcd(tm->tm_wday + 1);
+ buf[RTC_DAY_IDX] = bin2bcd(tm->tm_mday);
+ buf[RTC_MONTH_IDX] = bin2bcd(tm->tm_mon + 1);
+ buf[RTC_YEAR_IDX] = bin2bcd(tm->tm_year - 100);
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
+ buf);
+ if (ret) {
+ pr_err("%s: Failed to set rtc device!\n", __func__);
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ unsigned char buf[REQUEST_RTC_CMD1_LEN];
+ int ret;
+
+ ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
+ 0, REQUEST_RTC_CMD1_LEN, buf);
+ if (ret) {
+ pr_err("%s: Failed to get rtc device!\n", __func__);
+ return -EIO;
+ }
+
+ alrm->time.tm_sec = bcd2bin(buf[RTC_SEC_IDX]);
+ alrm->time.tm_min = bcd2bin(buf[RTC_MIN_IDX]);
+ alrm->time.tm_hour = bcd2bin(buf[RTC_HOUR_IDX]);
+
+ alrm->enabled = buf[RTC_ALRM_EN_IDX];
+ alrm->pending = buf[RTC_ALRM_PEND_IDX];
+
+ return ret;
+}
+
+static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ unsigned char buf[REQUEST_RTC_CMD1_LEN];
+ int ret;
+
+ buf[RTC_SEC_IDX] = bin2bcd(alrm->time.tm_sec);
+ buf[RTC_MIN_IDX] = bin2bcd(alrm->time.tm_min);
+ buf[RTC_HOUR_IDX] = bin2bcd(alrm->time.tm_hour);
+ buf[RTC_ALRM_EN_IDX] = alrm->enabled ? RTC_IRQ_EN : 0;
+ buf[RTC_ALRM_PEND_IDX] = 0;
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
+ buf);
+ if (ret) {
+ pr_err("%s: Failed to set rtc device!\n", __func__);
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+ unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
+ int ret;
+
+ if (enabled)
+ buf[RTC_IRQ_EN_IDX] |= RTC_IRQ_EN;
+ else
+ buf[RTC_IRQ_EN_IDX] &= ~RTC_IRQ_EN;
+
+ ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD2_OFFSET, REQUEST_RTC_CMD2_LEN,
+ buf);
+ if (ret) {
+ pr_err("%s: Failed to set rtc device!\n", __func__);
+ return -EIO;
+ }
+
+ return ret;
+}
+
+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 void nct6694_rtc_alarm(struct work_struct *work)
+{
+ struct nct6694_rtc_data *data;
+ unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
+
+ data = container_of(work, struct nct6694_rtc_data, alarm_work);
+
+ pr_info("%s: Got RTC alarm!\n", __func__);
+ buf[RTC_IRQ_EN_IDX] = RTC_IRQ_EN;
+ buf[RTC_IRQ_PEND_IDX] = RTC_IRQ_STS;
+ nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
+ REQUEST_RTC_CMD2_OFFSET,
+ REQUEST_RTC_CMD2_LEN, buf);
+}
+
+static void nct6694_rtc_handler(void *private_data)
+{
+ struct nct6694_rtc_data *data = private_data;
+ struct nct6694 *nct6694 = data->nct6694;
+
+ queue_work(nct6694->async_workqueue, &data->alarm_work);
+}
+
+static int nct6694_rtc_probe(struct platform_device *pdev)
+{
+ struct nct6694_rtc_data *data;
+ struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->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;
+
+ INIT_WORK(&data->alarm_work, nct6694_rtc_alarm);
+
+ ret = nct6694_register_handler(nct6694, RTC_IRQ_STATUS,
+ nct6694_rtc_handler, data);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
+ __func__, ERR_PTR(ret));
+ return ret;
+ }
+
+ device_set_wakeup_capable(&pdev->dev, 1);
+
+ platform_set_drvdata(pdev, data);
+
+ /* Register rtc device to RTC framework */
+ ret = devm_rtc_register_device(data->rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register rtc device!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = nct6694_rtc_probe,
+};
+
+static int __init nct6694_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&nct6694_rtc_driver);
+ if (!err) {
+ if (err)
+ platform_driver_unregister(&nct6694_rtc_driver);
+ }
+
+ return err;
+}
+subsys_initcall(nct6694_init);
+
+static void __exit nct6694_exit(void)
+{
+ platform_driver_unregister(&nct6694_rtc_driver);
+}
+module_exit(nct6694_exit);
+
+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] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
@ 2024-10-24 9:03 ` Marc Kleine-Budde
2024-10-25 8:00 ` Ming Yu
2024-10-24 9:57 ` Marc Kleine-Budde
` (2 subsequent siblings)
3 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 9:03 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 5693 bytes --]
On 24.10.2024 16:59:14, Ming Yu wrote:
> The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> PWM, and RTC.
>
> This 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 register
> a handler function 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 | 394 ++++++++++++++++++++++++++++++++++++
> include/linux/mfd/nct6694.h | 168 +++++++++++++++
> 5 files changed, 581 insertions(+)
> create mode 100644 drivers/mfd/nct6694.c
> create mode 100644 include/linux/mfd/nct6694.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e9659a5a7fb3..30157ca95cf3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16434,6 +16434,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 f9325bcce1b9..da2600958697 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -546,6 +546,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 2a9f91e81af8..2cf816d67d03 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -116,6 +116,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..9838c7be0b98
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
> @@ -0,0 +1,394 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 MFD driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/io.h>
> +#include <linux/usb.h>
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-usb_mfd"
> +
> +#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(IIO),
> + MFD_DEV_SIMPLE(HWMON),
> + MFD_DEV_SIMPLE(PWM),
> + MFD_DEV_SIMPLE(RTC),
> +};
> +
> +int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit,
> + void (*handler)(void *), void *private_data)
> +{
> + struct nct6694_handler_entry *entry;
> + unsigned long flags;
> +
> + entry = kmalloc(sizeof(*entry), GFP_KERNEL);
> + if (!entry)
> + return -ENOMEM;
> +
> + entry->irq_bit = irq_bit;
> + entry->handler = handler;
> + entry->private_data = private_data;
> +
> + spin_lock_irqsave(&nct6694->lock, flags);
> + list_add_tail(&entry->list, &nct6694->handler_list);
> + spin_unlock_irqrestore(&nct6694->lock, flags);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(nct6694_register_handler);
Where's the corresponding nct6694_free_handler() function?
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 8:59 ` [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
@ 2024-10-24 9:20 ` Kalesh Anakkur Purayil
2024-10-24 14:53 ` Guenter Roeck
2024-10-25 15:10 ` Ming Yu
2024-10-24 15:03 ` Guenter Roeck
1 sibling, 2 replies; 80+ messages in thread
From: Kalesh Anakkur Purayil @ 2024-10-24 9:20 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 18186 bytes --]
On Thu, Oct 24, 2024 at 2:33 PM Ming Yu <a0282524688@gmail.com> 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 | 407 ++++++++++++++++++++++++++++++++++
> 4 files changed, 419 insertions(+)
> create mode 100644 drivers/hwmon/nct6694-hwmon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 63387c0d4ab6..2aa87ad84156 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -167,6 +167,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..7d7d22a650b0
> --- /dev/null
> +++ b/drivers/hwmon/nct6694-hwmon.c
> @@ -0,0 +1,407 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/hwmon.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-hwmon"
> +
> +/* Host interface */
> +#define REQUEST_RPT_MOD 0xFF
> +#define REQUEST_HWMON_MOD 0x00
> +
> +/* Report Channel */
> +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
> +#define HWMON_FIN_STS(x) (0x6E + (x))
> +#define HWMON_PWM_IDX(x) (0x70 + (x))
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define REQUEST_HWMON_CMD0_LEN 0x40
> +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define HWMON_FIN_EN(x) (0x04 + (x))
> +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> +/* Command 02h */
> +#define REQUEST_HWMON_CMD2_LEN 0x90
> +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> +#define HWMON_SMI_CTRL_IDX 0x00
> +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
> +#define HWMON_CMD2_HYST_MASK 0x1F
> +/* Command 03h */
> +#define REQUEST_HWMON_CMD3_LEN 0x08
> +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> +
> +struct nct6694_hwmon_data {
> + struct nct6694 *nct6694;
> +
> + /* Make sure read & write commands are consecutive */
> + struct mutex hwmon_lock;
> +};
> +
> +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
> + HWMON_F_MIN | HWMON_F_MIN_ALARM)
> +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
> +
> +static const struct hwmon_channel_info *nct6694_info[] = {
> + 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_fan_read(struct device *dev, u32 attr, int channel,
> + long *val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> + unsigned char buf[2];
> + int ret;
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN,
> + HWMON_FIN_EN(channel / 8),
> + 1, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
> +
> + break;
> +
> + case hwmon_fan_input:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_FIN_IDX(channel), 2, 0,
> + 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> +
> + break;
> +
> + case hwmon_fan_min:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN,
> + HWMON_FIN_LIMIT_IDX(channel),
> + 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> +
> + break;
> +
> + case hwmon_fan_min_alarm:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_FIN_STS(channel / 8),
> + 1, 0, 1, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf[0] & BIT(channel % 8);
> +
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +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 buf;
> + int ret;
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_PWM_IDX(channel),
> + 1, 0, 1, &buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf;
> +
> + break;
> + case hwmon_pwm_freq:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN,
> + HWMON_PWM_FREQ_IDX(channel),
> + 1, &buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf * 25000 / 255;
> +
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> + long val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
[Kalesh] Please try to maintain RCT order for variable declaration
> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> + u16 fan_val = (u16)val;
> + int ret;
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + mutex_lock(&data->hwmon_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN, 0,
> + REQUEST_HWMON_CMD0_LEN,
> + enable_buf);
> + if (ret)
> + goto err;
> +
> + if (val)
> + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
> + else
> + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN, enable_buf);
> + if (ret)
> + goto err;
> +
> + break;
> +
> + case hwmon_fan_min:
> + mutex_lock(&data->hwmon_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, 0,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
> + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + break;
> +
> + default:
> + ret = -EOPNOTSUPP;
[Kalesh] If you initialize "ret = -EOPNOTSUPP;" during declararion,
you can just break from here.
> + goto err;
> + }
> +
> +err:
> + mutex_unlock(&data->hwmon_lock);
> + return ret;
> +}
> +
> +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + switch (type) {
> + 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;
> + }
> +
> + return 0;
> +}
> +
> +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + switch (type) {
> + case hwmon_fan:
> + return nct6694_fan_write(dev, attr, channel, val);
> + default:
> + return -EOPNOTSUPP;
> + }
[Kalesh] You can use simple if condition here than a switch like:
if (type != hwmon_fan)
return -EOPNOTSUPP;
return nct6694_fan_write(dev, attr, channel, val);
> +
> + return 0;
> +}
> +
> +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_enable:
> + case hwmon_fan_min:
> + return 0644;
[Kalesh] I think there is no need to leave a new line in between cases
> +
> + case hwmon_fan_input:
> + case hwmon_fan_min_alarm:
> + return 0444;
> +
> + default:
> + return 0;
> + }
> +
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_input:
> + case hwmon_pwm_freq:
> + return 0444;
> + default:
> + return 0;
> + }
> +
> + default:
> + return 0;
> + }
> +
> + return 0;
[Kalesh] This return statement looks redundant as the execution never
reaches here. Same comment applies to other functions above as well.
> +}
> +
> +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)
> +{
> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> + int ret;
> +
> + /* Set Fan input Real Time alarm mode */
> + mutex_lock(&data->hwmon_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, 0,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
[Kalesh] It would be better to rename the label as "unlock". Same
comment on other functions as well.
> +
> + buf[HWMON_SMI_CTRL_IDX] = 0x02;
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> +err:
> + mutex_unlock(&data->hwmon_lock);
> + return ret;
> +}
> +
> +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->nct6694 = nct6694;
> + mutex_init(&data->hwmon_lock);
> + platform_set_drvdata(pdev, data);
> +
> + ret = nct6694_hwmon_init(data);
> + if (ret)
> + return -EIO;
> +
> + /* Register hwmon device to HWMON framework */
> + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> + "nct6694", data,
> + &nct6694_chip_info,
> + NULL);
> + if (IS_ERR(hwmon_dev)) {
> + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
> + __func__);
> + return PTR_ERR(hwmon_dev);
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_hwmon_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_hwmon_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_hwmon_driver);
> + if (!err) {
> + if (err)
[Kalesh] This whole check looks strange. You can simplify this function as:
return platform_driver_register(&nct6694_hwmon_driver);
> + platform_driver_unregister(&nct6694_hwmon_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_hwmon_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
>
>
--
Regards,
Kalesh A P
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4239 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-24 8:59 ` [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
@ 2024-10-24 9:47 ` Bartosz Golaszewski
[not found] ` <CAOoeyxUUOCSaDLK8=ox3hwDVu=Ej-ds4=FsS8F+9GfiE-8HYvg@mail.gmail.com>
2024-10-25 7:38 ` 游子民
0 siblings, 2 replies; 80+ messages in thread
From: Bartosz Golaszewski @ 2024-10-24 9:47 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, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On Thu, Oct 24, 2024 at 10:59 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 | 489 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 503 insertions(+)
> create mode 100644 drivers/gpio/gpio-nct6694.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 30157ca95cf3..2c86d5dab3f1 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16438,6 +16438,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 d93cd4f722b4..aa78ad9ff4ac 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1450,6 +1450,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 1429e8c0229b..02c94aa28017 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -121,6 +121,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..42c0e6e76730
> --- /dev/null
> +++ b/drivers/gpio/gpio-nct6694.c
> @@ -0,0 +1,489 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 GPIO controller driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/gpio.h>
Don't include this header. It's documented as obsolete.
> +#include <linux/gpio/driver.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +
You only use it once, drop it.
> +#define DRVNAME "nct6694-gpio"
> +
> +/* Host interface */
> +#define REQUEST_GPIO_MOD 0xFF
> +#define REQUEST_GPIO_LEN 0x01
> +
> +/* Report Channel */
> +#define GPIO_VER_REG 0x90
> +#define GPIO_VALID_REG 0x110
> +#define GPI_DATA_REG 0x120
> +#define GPO_DIR_REG 0x170
> +#define GPO_TYPE_REG 0x180
> +#define GPO_DATA_REG 0x190
> +
> +#define GPI_STS_REG 0x130
> +#define GPI_CLR_REG 0x140
> +#define GPI_FALLING_REG 0x150
> +#define GPI_RISING_REG 0x160
> +
Please use the NCT6694 prefix for these defines, otherwise it's not
clear whether they come from the driver or from GPIO core.
[]
> +
> +static const char * const nct6694_gpio_name[] = {
> + "NCT6694-GPIO0",
> + "NCT6694-GPIO1",
> + "NCT6694-GPIO2",
> + "NCT6694-GPIO3",
> + "NCT6694-GPIO4",
> + "NCT6694-GPIO5",
> + "NCT6694-GPIO6",
> + "NCT6694-GPIO7",
> + "NCT6694-GPIO8",
> + "NCT6694-GPIO9",
> + "NCT6694-GPIOA",
> + "NCT6694-GPIOB",
> + "NCT6694-GPIOC",
> + "NCT6694-GPIOD",
> + "NCT6694-GPIOE",
> + "NCT6694-GPIOF",
> +};
This looks like it corresponds with the MFD cells and makes me wonder:
am I getting that wrong or do you want to register 0xf GPIO chips? Or
a single GPIO chip with 0xf lines? What is the topology?
> +
> +static int nct6694_gpio_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_gpio_data *data;
> + struct gpio_irq_chip *girq;
> + int ret;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->nct6694 = nct6694;
> + data->group = cell->id;
> +
> + data->gpio.label = nct6694_gpio_name[cell->id];
> + 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 = 8;
> +
> + INIT_WORK(&data->irq_work, nct6694_irq);
> + INIT_WORK(&data->irq_trig_work, nct6694_irq_trig);
> + mutex_init(&data->irq_lock);
> +
> + ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS,
> + nct6694_gpio_handler, data);
> + if (ret) {
> + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> + __func__, ERR_PTR(ret));
> + return ret;
> + }
> +
> + platform_set_drvdata(pdev, data);
> +
> + ret = nct6694_get_irq_trig(data);
> + if (ret)
> + return ret;
> +
> + /* 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 = gpiochip_add_data(&data->gpio, data);
> + if (ret) {
> + dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe",
> + __func__, ERR_PTR(ret));
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void nct6694_gpio_remove(struct platform_device *pdev)
> +{
> + struct nct6694_gpio_data *data = platform_get_drvdata(pdev);
> +
> + gpiochip_remove(&data->gpio);
This should be dropped in favor of using devm_gpiochip_add_data().
Especially since you probably want to cancel the irq_work before
removing the chip.
> + cancel_work(&data->irq_work);
> + cancel_work(&data->irq_trig_work);
> +}
> +
> +static struct platform_driver nct6694_gpio_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_gpio_probe,
> + .remove = nct6694_gpio_remove,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_gpio_driver);
> + if (!err) {
> + if (err)
If err is equal to 0, check if it's not equal to zero?
> + platform_driver_unregister(&nct6694_gpio_driver);
If platform_driver_register() failed, then the device was never registered.
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
Any reason why this must be initialized earlier? It's a USB driver after all.
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_gpio_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
>
Bart
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-10-24 9:03 ` Marc Kleine-Budde
@ 2024-10-24 9:57 ` Marc Kleine-Budde
2024-10-25 8:02 ` Ming Yu
2024-10-24 15:20 ` Marc Kleine-Budde
2024-10-26 14:58 ` Christophe JAILLET
3 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 9:57 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1337 bytes --]
On 24.10.2024 16:59:14, Ming Yu wrote:
> +static int nct6694_usb_probe(struct usb_interface *iface,
> + const struct usb_device_id *id)
> +{
> + struct usb_device *udev = interface_to_usbdev(iface);
> + struct device *dev = &udev->dev;
> + struct usb_host_interface *interface;
> + struct usb_endpoint_descriptor *int_endpoint;
> + struct nct6694 *nct6694;
> + int pipe, maxp, bulk_pipe;
> + int ret = EINVAL;
> +
> + interface = iface->cur_altsetting;
> + /* Binding interface class : 0xFF */
> + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> + interface->desc.bInterfaceSubClass != 0x00 ||
> + interface->desc.bInterfaceProtocol != 0x00)
> + return -ENODEV;
I think you can use USB_DEVICE_INFO() and remove this manual check
https://elixir.bootlin.com/linux/v6.11.5/source/include/linux/usb.h#L1056
[...]
> +
> +static const struct usb_device_id nct6694_ids[] = {
> + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
> + {},
> +};
> +MODULE_DEVICE_TABLE(usb, nct6694_ids);
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support
2024-10-24 8:59 ` [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
@ 2024-10-24 10:41 ` Andi Shyti
2024-10-25 7:47 ` 游子民
0 siblings, 1 reply; 80+ messages in thread
From: Andi Shyti @ 2024-10-24 10:41 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hi Ming,
...
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_i2c_driver);
> + if (!err) {
> + if (err)
> + platform_driver_unregister(&nct6694_i2c_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_i2c_driver);
> +}
> +module_exit(nct6694_exit);
Have you thought about using auxiliary driver here?
(auxiliary_bus.h)
Thanks,
Andi
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
` (8 preceding siblings ...)
2024-10-24 8:59 ` [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
@ 2024-10-24 11:57 ` Marc Kleine-Budde
2024-10-25 8:22 ` Ming Yu
9 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 11:57 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1040 bytes --]
On 24.10.2024 16:59:13, Ming Yu wrote:
> This patch series introduces support for Nuvoton NCT6694, a peripheral
> expander based on USB interface. It models the chip as an MFD driver
> (1/9), GPIO driver(2/9), I2C Adapter driver(3/9), CANfd driver(4/9),
> WDT driver(5/9), HWMON driver(6/9), IIO driver(7/9), PWM driver(8/9),
> and RTC driver(9/9).
>
> The MFD driver implements USB device functionality to issue
> custom-define USB bulk pipe packets for NCT6694. Each child device can
> use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
> a command. They can also register a handler function that will be called
> when the USB device receives its interrupt pipe.
What about implementing a proper IRQ demux handler instead?
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
@ 2024-10-24 12:12 ` Marc Kleine-Budde
2024-10-24 15:28 ` Marc Kleine-Budde
2024-10-24 14:17 ` Marc Kleine-Budde
` (2 subsequent siblings)
3 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 12:12 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 3302 bytes --]
Hello,
thanks for your contribution. It seems to me that there is no proper
TX-flow control and I have some questions.
On 24.10.2024 16:59:17, Ming Yu wrote:
[...]
> +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
> + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> + struct nct6694 *nct6694 = priv->nct6694;
> + struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> + struct net_device_stats *stats = &ndev->stats;
> + int can_idx = priv->can_idx;
> + u32 txid = 0;
> + int i;
> + unsigned int echo_byte;
> + u8 data_buf[REQUEST_CAN_CMD10_LEN] = {0};
> +
> + if (can_dropped_invalid_skb(ndev, skb))
> + return NETDEV_TX_OK;
> +
> + /*
> + * No check for NCT66794 because the TX bit is read-clear
> + * and may be read-cleared by other function
> + * Just check the result of tx command.
> + */
Where do you check the result of the TX command?
> + /* Check if the TX buffer is full */
Where's the check if the TX buffer is full?
> + netif_stop_queue(ndev);
> +
> + if (can_idx == 0)
> + data_buf[CAN_TAG_IDX] = CAN_TAG_CAN0;
> + else
> + data_buf[CAN_TAG_IDX] = 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.
> + */
> + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_EFF;
> + } else {
> + txid = cf->can_id & CAN_SFF_MASK;
> + }
> +
> + set_buf32(&data_buf[CAN_ID_IDX], txid);
> +
> + data_buf[CAN_DLC_IDX] = cf->len;
> +
> + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) {
> + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_FD;
> + if (cf->flags & CANFD_BRS)
> + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_BRS;
> + }
> +
> + if (cf->can_id & CAN_RTR_FLAG)
> + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_RTR;
> +
> + /* set data to buf */
> + for (i = 0; i < cf->len; i++)
> + data_buf[CAN_DATA_IDX + i] = *(u8 *)(cf->data + i);
> +
> + can_put_echo_skb(skb, ndev, 0, 0);
> +
> + memcpy(priv->data_buf, data_buf, REQUEST_CAN_CMD10_LEN);
> + queue_work(nct6694->async_workqueue, &priv->tx_work);
> +
> + stats->tx_bytes += cf->len;
> + stats->tx_packets++;
> + echo_byte = can_get_echo_skb(ndev, 0, NULL);
> +
> + netif_wake_queue(ndev);
How do you make sure that the tx_work has finished?
Once you wake the queue, the xmit function can be called again. If your
tx_work has not finished, you'll overwrite the priv->data_buf.
> +
> + return NETDEV_TX_OK;
> +}
> +
> +static void nct6694_canfd_tx_work(struct work_struct *work)
> +{
> + struct nct6694_canfd_priv *priv;
> +
> + priv = container_of(work, struct nct6694_canfd_priv, tx_work);
> +
> + nct6694_write_msg(priv->nct6694, REQUEST_CAN_MOD,
> + REQUEST_CAN_CMD10_OFFSET(1),
> + REQUEST_CAN_CMD10_LEN,
> + priv->data_buf);
> +}
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
2024-10-24 12:12 ` Marc Kleine-Budde
@ 2024-10-24 14:17 ` Marc Kleine-Budde
2024-10-24 14:20 ` Marc Kleine-Budde
2024-11-01 1:37 ` Ming Yu
2024-10-25 11:18 ` kernel test robot
2024-10-25 23:31 ` kernel test robot
3 siblings, 2 replies; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 14:17 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1707 bytes --]
On 24.10.2024 16:59:17, 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 | 843 ++++++++++++++++++++++++++++++++
FTBFS:
| make[5]: *** No rule to make target 'drivers/net/can/nct6604_canfd.o', needed by 'drivers/net/can/'. Stop.
[...]
> diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> index a71db2cfe990..825c011aead5 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) += nct6604_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..d873a9fd656d
> --- /dev/null
> +++ b/drivers/net/can/nct6694_canfd.c
^^^^^^^^^^^^^^^
This doesn't compile :(
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 14:17 ` Marc Kleine-Budde
@ 2024-10-24 14:20 ` Marc Kleine-Budde
2024-11-01 1:44 ` Ming Yu
2024-11-01 1:37 ` Ming Yu
1 sibling, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 14:20 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1731 bytes --]
On 24.10.2024 16:17:52, Marc Kleine-Budde wrote:
> On 24.10.2024 16:59:17, 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 | 843 ++++++++++++++++++++++++++++++++
| CC [M] drivers/net/can/nct6694_canfd.o
| drivers/net/can/nct6694_canfd.c: In function ‘nct6694_canfd_start_xmit’:
| drivers/net/can/nct6694_canfd.c:282:22: error: variable ‘echo_byte’ set but not used [-Werror=unused-but-set-variable]
| 282 | unsigned int echo_byte;
| | ^~~~~~~~~
| drivers/net/can/nct6694_canfd.c: In function ‘nct6694_canfd_rx_work’:
| drivers/net/can/nct6694_canfd.c:677:34: error: variable ‘stats’ set but not used [-Werror=unused-but-set-variable]
| 677 | struct net_device_stats *stats;
| | ^~~~~
| cc1: all warnings being treated as errors
If compiling with C=1, sparse throws the following errors:
| drivers/net/can/nct6694_canfd.c:417:14: warning: cast to restricted __le32
| drivers/net/can/nct6694_canfd.c:750:9: warning: cast to restricted __le32
| drivers/net/can/nct6694_canfd.c:777:32: warning: cast to restricted __le32
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 9:20 ` Kalesh Anakkur Purayil
@ 2024-10-24 14:53 ` Guenter Roeck
2024-10-25 15:22 ` Ming Yu
2024-10-25 15:10 ` Ming Yu
1 sibling, 1 reply; 80+ messages in thread
From: Guenter Roeck @ 2024-10-24 14:53 UTC (permalink / raw)
To: Kalesh Anakkur Purayil, Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare,
jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On 10/24/24 02:20, Kalesh Anakkur Purayil wrote:
> On Thu, Oct 24, 2024 at 2:33 PM Ming Yu <a0282524688@gmail.com> 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 | 407 ++++++++++++++++++++++++++++++++++
>> 4 files changed, 419 insertions(+)
>> create mode 100644 drivers/hwmon/nct6694-hwmon.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 63387c0d4ab6..2aa87ad84156 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -167,6 +167,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..7d7d22a650b0
>> --- /dev/null
>> +++ b/drivers/hwmon/nct6694-hwmon.c
>> @@ -0,0 +1,407 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Nuvoton NCT6694 HWMON driver based on USB interface.
>> + *
>> + * Copyright (C) 2024 Nuvoton Technology Corp.
>> + */
>> +
>> +#include <linux/slab.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/hwmon.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/mfd/nct6694.h>
>> +
>> +#define DRVNAME "nct6694-hwmon"
>> +
>> +/* Host interface */
>> +#define REQUEST_RPT_MOD 0xFF
>> +#define REQUEST_HWMON_MOD 0x00
>> +
>> +/* Report Channel */
>> +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
>> +#define HWMON_FIN_STS(x) (0x6E + (x))
>> +#define HWMON_PWM_IDX(x) (0x70 + (x))
>> +
>> +/* Message Channel*/
>> +/* Command 00h */
>> +#define REQUEST_HWMON_CMD0_LEN 0x40
>> +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
>> +#define HWMON_FIN_EN(x) (0x04 + (x))
>> +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
>> +/* Command 02h */
>> +#define REQUEST_HWMON_CMD2_LEN 0x90
>> +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
>> +#define HWMON_SMI_CTRL_IDX 0x00
>> +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
>> +#define HWMON_CMD2_HYST_MASK 0x1F
>> +/* Command 03h */
>> +#define REQUEST_HWMON_CMD3_LEN 0x08
>> +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
>> +
>> +struct nct6694_hwmon_data {
>> + struct nct6694 *nct6694;
>> +
>> + /* Make sure read & write commands are consecutive */
>> + struct mutex hwmon_lock;
>> +};
>> +
>> +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
>> + HWMON_F_MIN | HWMON_F_MIN_ALARM)
>> +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
>> +
>> +static const struct hwmon_channel_info *nct6694_info[] = {
>> + 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_fan_read(struct device *dev, u32 attr, int channel,
>> + long *val)
>> +{
>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
>> + unsigned char buf[2];
>> + int ret;
>> +
>> + switch (attr) {
>> + case hwmon_fan_enable:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD0_OFFSET,
>> + REQUEST_HWMON_CMD0_LEN,
>> + HWMON_FIN_EN(channel / 8),
>> + 1, buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
>> +
>> + break;
>> +
>> + case hwmon_fan_input:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
>> + HWMON_FIN_IDX(channel), 2, 0,
>> + 2, buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
>> +
>> + break;
>> +
>> + case hwmon_fan_min:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD2_OFFSET,
>> + REQUEST_HWMON_CMD2_LEN,
>> + HWMON_FIN_LIMIT_IDX(channel),
>> + 2, buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
>> +
>> + break;
>> +
>> + case hwmon_fan_min_alarm:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
>> + HWMON_FIN_STS(channel / 8),
>> + 1, 0, 1, buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = buf[0] & BIT(channel % 8);
>> +
>> + break;
>> +
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +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 buf;
>> + int ret;
>> +
>> + switch (attr) {
>> + case hwmon_pwm_input:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
>> + HWMON_PWM_IDX(channel),
>> + 1, 0, 1, &buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = buf;
>> +
>> + break;
>> + case hwmon_pwm_freq:
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD0_OFFSET,
>> + REQUEST_HWMON_CMD0_LEN,
>> + HWMON_PWM_FREQ_IDX(channel),
>> + 1, &buf);
>> + if (ret)
>> + return -EINVAL;
>> +
>> + *val = buf * 25000 / 255;
>> +
>> + break;
>> +
>> + default:
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
>> + long val)
>> +{
>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> [Kalesh] Please try to maintain RCT order for variable declaration
Ok, but that is already the case here ?
>> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
>> + u16 fan_val = (u16)val;
>> + int ret;
>> +
>> + switch (attr) {
>> + case hwmon_fan_enable:
>> + mutex_lock(&data->hwmon_lock);
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD0_OFFSET,
>> + REQUEST_HWMON_CMD0_LEN, 0,
>> + REQUEST_HWMON_CMD0_LEN,
>> + enable_buf);
>> + if (ret)
>> + goto err;
>> +
>> + if (val)
>> + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
>> + else
>> + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
>> +
>> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD0_OFFSET,
>> + REQUEST_HWMON_CMD0_LEN, enable_buf);
>> + if (ret)
>> + goto err;
>> +
>> + break;
>> +
>> + case hwmon_fan_min:
>> + mutex_lock(&data->hwmon_lock);
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD2_OFFSET,
>> + REQUEST_HWMON_CMD2_LEN, 0,
>> + REQUEST_HWMON_CMD2_LEN, buf);
>> + if (ret)
>> + goto err;
>> +
>> + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
>> + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
>> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD2_OFFSET,
>> + REQUEST_HWMON_CMD2_LEN, buf);
>> + if (ret)
>> + goto err;
>> +
>> + break;
>> +
>> + default:
>> + ret = -EOPNOTSUPP;
> [Kalesh] If you initialize "ret = -EOPNOTSUPP;" during declararion,
> you can just break from here.
You are missing the point. The lock wasn't acquired here in the first place.
It is conceptually wrong to acquire a lock in the switch statement and release
it outside. This patch is a case in point.
>> + goto err;
>> + }
>> +
>> +err:
>> + mutex_unlock(&data->hwmon_lock);
>> + return ret;
>> +}
>> +
>> +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, long *val)
>> +{
>> + switch (type) {
>> + 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;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int channel, long val)
>> +{
>> + switch (type) {
>> + case hwmon_fan:
>> + return nct6694_fan_write(dev, attr, channel, val);
>> + default:
>> + return -EOPNOTSUPP;
>> + }
> [Kalesh] You can use simple if condition here than a switch like:
> if (type != hwmon_fan)
> return -EOPNOTSUPP;
> return nct6694_fan_write(dev, attr, channel, val);
That is a bit POV. I'd leave that to the developer.
More important is that the return statements after the switch are unnecessary
and never reached if each case returns immediately.
>> +
>> + return 0;
>> +}
>> +
>> +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
>> + u32 attr, int channel)
>> +{
>> + switch (type) {
>> + case hwmon_fan:
>> + switch (attr) {
>> + case hwmon_fan_enable:
>> + case hwmon_fan_min:
>> + return 0644;
> [Kalesh] I think there is no need to leave a new line in between cases
>> +
>> + case hwmon_fan_input:
>> + case hwmon_fan_min_alarm:
>> + return 0444;
>> +
>> + default:
>> + return 0;
>> + }
>> +
>> + case hwmon_pwm:
>> + switch (attr) {
>> + case hwmon_pwm_input:
>> + case hwmon_pwm_freq:
>> + return 0444;
>> + default:
>> + return 0;
>> + }
>> +
>> + default:
>> + return 0;
>> + }
>> +
>> + return 0;
> [Kalesh] This return statement looks redundant as the execution never
> reaches here. Same comment applies to other functions above as well.
>> +}
>> +
>> +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)
>> +{
>> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
>> + int ret;
>> +
>> + /* Set Fan input Real Time alarm mode */
>> + mutex_lock(&data->hwmon_lock);
>> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD2_OFFSET,
>> + REQUEST_HWMON_CMD2_LEN, 0,
>> + REQUEST_HWMON_CMD2_LEN, buf);
>> + if (ret)
>> + goto err;
> [Kalesh] It would be better to rename the label as "unlock". Same
> comment on other functions as well.
The lock is not needed here in the first place. The function is called
exactly once during initialization.
>> +
>> + buf[HWMON_SMI_CTRL_IDX] = 0x02;
>> +
>> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
>> + REQUEST_HWMON_CMD2_OFFSET,
>> + REQUEST_HWMON_CMD2_LEN, buf);
>> + if (ret)
>> + goto err;
>> +
>> +err:
>> + mutex_unlock(&data->hwmon_lock);
>> + return ret;
>> +}
>> +
>> +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->nct6694 = nct6694;
>> + mutex_init(&data->hwmon_lock);
>> + platform_set_drvdata(pdev, data);
>> +
>> + ret = nct6694_hwmon_init(data);
>> + if (ret)
>> + return -EIO;
>> +
>> + /* Register hwmon device to HWMON framework */
>> + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
>> + "nct6694", data,
>> + &nct6694_chip_info,
>> + NULL);
>> + if (IS_ERR(hwmon_dev)) {
>> + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
>> + __func__);
>> + return PTR_ERR(hwmon_dev);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver nct6694_hwmon_driver = {
>> + .driver = {
>> + .name = DRVNAME,
>> + },
>> + .probe = nct6694_hwmon_probe,
>> +};
>> +
>> +static int __init nct6694_init(void)
>> +{
>> + int err;
>> +
>> + err = platform_driver_register(&nct6694_hwmon_driver);
>> + if (!err) {
>> + if (err)
> [Kalesh] This whole check looks strange. You can simplify this function as:
> return platform_driver_register(&nct6694_hwmon_driver);
>> + platform_driver_unregister(&nct6694_hwmon_driver);
>> + }
>> +
>> + return err;
>> +}
>> +subsys_initcall(nct6694_init);
>> +
>> +static void __exit nct6694_exit(void)
>> +{
>> + platform_driver_unregister(&nct6694_hwmon_driver);
>> +}
>> +module_exit(nct6694_exit);
>> +
>> +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
>> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
>> +MODULE_LICENSE("GPL");
>> --
>> 2.34.1
>>
>>
>
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 8:59 ` [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-10-24 9:20 ` Kalesh Anakkur Purayil
@ 2024-10-24 15:03 ` Guenter Roeck
2024-10-25 15:33 ` Ming Yu
1 sibling, 1 reply; 80+ messages in thread
From: Guenter Roeck @ 2024-10-24 15:03 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
On 10/24/24 01:59, 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 | 407 ++++++++++++++++++++++++++++++++++
> 4 files changed, 419 insertions(+)
> create mode 100644 drivers/hwmon/nct6694-hwmon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 63387c0d4ab6..2aa87ad84156 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -167,6 +167,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..7d7d22a650b0
> --- /dev/null
> +++ b/drivers/hwmon/nct6694-hwmon.c
> @@ -0,0 +1,407 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/hwmon.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-hwmon"
> +
> +/* Host interface */
> +#define REQUEST_RPT_MOD 0xFF
> +#define REQUEST_HWMON_MOD 0x00
> +
> +/* Report Channel */
> +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
> +#define HWMON_FIN_STS(x) (0x6E + (x))
> +#define HWMON_PWM_IDX(x) (0x70 + (x))
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define REQUEST_HWMON_CMD0_LEN 0x40
> +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define HWMON_FIN_EN(x) (0x04 + (x))
> +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> +/* Command 02h */
> +#define REQUEST_HWMON_CMD2_LEN 0x90
> +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> +#define HWMON_SMI_CTRL_IDX 0x00
> +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
> +#define HWMON_CMD2_HYST_MASK 0x1F
> +/* Command 03h */
> +#define REQUEST_HWMON_CMD3_LEN 0x08
> +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> +
> +struct nct6694_hwmon_data {
> + struct nct6694 *nct6694;
> +
> + /* Make sure read & write commands are consecutive */
> + struct mutex hwmon_lock;
> +};
> +
> +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
> + HWMON_F_MIN | HWMON_F_MIN_ALARM)
> +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
> +
> +static const struct hwmon_channel_info *nct6694_info[] = {
> + 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_fan_read(struct device *dev, u32 attr, int channel,
> + long *val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> + unsigned char buf[2];
> + int ret;
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN,
> + HWMON_FIN_EN(channel / 8),
> + 1, buf);
> + if (ret)
> + return -EINVAL;
Do not overwrite error return values.
> +
> + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
> +
> + break;
> +
> + case hwmon_fan_input:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_FIN_IDX(channel), 2, 0,
> + 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> +
> + break;
> +
> + case hwmon_fan_min:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN,
> + HWMON_FIN_LIMIT_IDX(channel),
> + 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> +
> + break;
> +
> + case hwmon_fan_min_alarm:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_FIN_STS(channel / 8),
> + 1, 0, 1, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf[0] & BIT(channel % 8);
> +
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +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 buf;
> + int ret;
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + HWMON_PWM_IDX(channel),
> + 1, 0, 1, &buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf;
> +
> + break;
> + case hwmon_pwm_freq:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN,
> + HWMON_PWM_FREQ_IDX(channel),
> + 1, &buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf * 25000 / 255;
> +
> + break;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> + long val)
> +{
> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> + u16 fan_val = (u16)val;
This is wrong. It will result in overflows/underflows if out of range
values are provided, and the driver should not write 0 if the user
writes 65536. You need to use clamp_val() instead. For values with
well defined range (specifically hwmon_fan_enable) you need to validate
the parameter, not just take it as boolean.
> + int ret;
> +
> + switch (attr) {
> + case hwmon_fan_enable:
> + mutex_lock(&data->hwmon_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN, 0,
> + REQUEST_HWMON_CMD0_LEN,
> + enable_buf);
> + if (ret)
> + goto err;
> +
> + if (val)
> + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
> + else
> + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN, enable_buf);
> + if (ret)
> + goto err;
> +
> + break;
> +
> + case hwmon_fan_min:
> + mutex_lock(&data->hwmon_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, 0,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
> + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + break;
The error handler goto and the break accomplish exactly the same,
thus the conditional goto is pointless.
> +
> + default:
> + ret = -EOPNOTSUPP;
> + goto err;
As mentioned in my other reply, the lock is not acquired here,
causing an unbalanced unlock.
> + }
> +
> +err:
> + mutex_unlock(&data->hwmon_lock);
> + return ret;
> +}
> +
> +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + switch (type) {
> + 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;
> + }
> +
> + return 0;
Unnecessary return statement. Also seen elsewhere.
> +}
> +
> +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + switch (type) {
> + case hwmon_fan:
> + return nct6694_fan_write(dev, attr, channel, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + 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_input:
> + case hwmon_pwm_freq:
> + return 0444;
> + default:
> + return 0;
> + }
> +
> + default:
> + return 0;
> + }
> +
> + 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)
> +{
> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> + int ret;
> +
> + /* Set Fan input Real Time alarm mode */
> + mutex_lock(&data->hwmon_lock);
Pointless lock (this is init code)
> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, 0,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + buf[HWMON_SMI_CTRL_IDX] = 0x02;
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD2_OFFSET,
> + REQUEST_HWMON_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> +err:
> + mutex_unlock(&data->hwmon_lock);
> + return ret;
> +}
> +
> +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->nct6694 = nct6694;
> + mutex_init(&data->hwmon_lock);
> + platform_set_drvdata(pdev, data);
> +
> + ret = nct6694_hwmon_init(data);
> + if (ret)
> + return -EIO;
Again, do not overwrite error codes.
> +
> + /* Register hwmon device to HWMON framework */
> + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> + "nct6694", data,
> + &nct6694_chip_info,
> + NULL);
> + if (IS_ERR(hwmon_dev)) {
> + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
> + __func__);
Use dev_err_probe(), and the function name is pointless.
> + return PTR_ERR(hwmon_dev);
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_hwmon_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_hwmon_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_hwmon_driver);
> + if (!err) {
> + if (err)
Seriously ? Read this code again. It is never reached (and pointless).
> + platform_driver_unregister(&nct6694_hwmon_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_hwmon_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-10-24 9:03 ` Marc Kleine-Budde
2024-10-24 9:57 ` Marc Kleine-Budde
@ 2024-10-24 15:20 ` Marc Kleine-Budde
2024-10-24 15:34 ` Marc Kleine-Budde
2024-10-25 8:08 ` Ming Yu
2024-10-26 14:58 ` Christophe JAILLET
3 siblings, 2 replies; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 15:20 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 14200 bytes --]
On 24.10.2024 16:59:14, Ming Yu wrote:
> The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> PWM, and RTC.
>
> This 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 register
> a handler function that will be called when the USB device receives
> its interrupt pipe.
[...]
> diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> new file mode 100644
> index 000000000000..9838c7be0b98
> --- /dev/null
> +++ b/drivers/mfd/nct6694.c
[...]
> +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> + u8 rd_idx, u8 rd_len, unsigned char *buf)
why not make buf a void *?
> +{
> + struct usb_device *udev = nct6694->udev;
> + unsigned char err_status;
> + int len, packet_len, tx_len, rx_len;
> + int i, ret;
> +
> + mutex_lock(&nct6694->access_lock);
> +
> + /* Send command packet to USB device */
> + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> +
> + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> + nct6694->timeout);
> + if (ret)
> + goto err;
> +
> + /* Receive response packet from USB device */
> + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> + nct6694->timeout);
> + if (ret)
> + goto err;
> +
> + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> +
> + /*
> + * Segmented reception of messages that exceed the size of USB bulk
> + * pipe packets.
> + */
The Linux USB stack can receive bulk messages longer than the max packet size.
> + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> + if (len > nct6694->maxp)
> + packet_len = nct6694->maxp;
> + else
> + packet_len = len;
> +
> + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> + nct6694->rx_buffer + nct6694->maxp * i,
> + packet_len, &rx_len, nct6694->timeout);
> + if (ret)
> + goto err;
> + }
> +
> + for (i = 0; i < rd_len; i++)
> + buf[i] = nct6694->rx_buffer[i + rd_idx];
memcpy()?
Or why don't you directly receive data into the provided buffer? Copying
of the data doesn't make it faster.
On the other hand, receiving directly into the target buffer means the
target buffer must not live on the stack.
> +
> + if (err_status) {
> + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
> + ret = -EIO;
> + }
> +
> +err:
> + mutex_unlock(&nct6694->access_lock);
> + return ret;
> +}
> +EXPORT_SYMBOL(nct6694_read_msg);
> +
> +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> + u16 length, unsigned char *buf)
> +{
> + struct usb_device *udev = nct6694->udev;
> + unsigned char err_status;
> + int len, packet_len, tx_len, rx_len;
> + int i, ret;
> +
> + mutex_lock(&nct6694->access_lock);
> +
> + /* Send command packet to USB device */
> + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_SET;
> + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
What about creating a struct that describes the cmd_buffer layout?
> +
> + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> + nct6694->timeout);
> + if (ret)
> + goto err;
> +
> + /*
> + * Segmented transmission of messages that exceed the size of USB bulk
> + * pipe packets.
> + */
same as above
> + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> + if (len > nct6694->maxp)
> + packet_len = nct6694->maxp;
> + else
> + packet_len = len;
> +
> + memcpy(nct6694->tx_buffer + nct6694->maxp * i,
> + buf + nct6694->maxp * i, packet_len);
> +
> + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> + nct6694->tx_buffer + nct6694->maxp * i,
> + packet_len, &tx_len, nct6694->timeout);
> + if (ret)
> + goto err;
> + }
> +
> + /* Receive response packet from USB device */
> + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> + nct6694->timeout);
> + if (ret)
> + goto err;
> +
> + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> +
> + /*
> + * Segmented reception of messages that exceed the size of USB bulk
> + * pipe packets.
> + */
same as above
> + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> + if (len > nct6694->maxp)
> + packet_len = nct6694->maxp;
> + else
> + packet_len = len;
> +
> + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> + nct6694->rx_buffer + nct6694->maxp * i,
> + packet_len, &rx_len, nct6694->timeout);
> + if (ret)
> + goto err;
> + }
> +
> + memcpy(buf, nct6694->rx_buffer, length);
why not rx into the destination buffer directly?
> +
> + if (err_status) {
> + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
> + ret = -EIO;
> + }
> +
> +err:
> + mutex_unlock(&nct6694->access_lock);
> + return ret;
> +}
> +EXPORT_SYMBOL(nct6694_write_msg);
[...]
> +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;
> + int pipe, maxp, bulk_pipe;
> + int ret = EINVAL;
> +
> + interface = iface->cur_altsetting;
> + /* Binding interface class : 0xFF */
> + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> + interface->desc.bInterfaceSubClass != 0x00 ||
> + interface->desc.bInterfaceProtocol != 0x00)
> + return -ENODEV;
> +
> + int_endpoint = &interface->endpoint[0].desc;
> + if (!usb_endpoint_is_int_in(int_endpoint))
> + return -ENODEV;
> +
> + nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL);
> + if (!nct6694)
> + return -ENOMEM;
> +
> + pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT);
> + maxp = usb_maxpacket(udev, pipe);
better move these 2 down to the usb_fill_int_urb().
> +
> + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->cmd_buffer)
> + return -ENOMEM;
> + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->rx_buffer)
> + return -ENOMEM;
> + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->tx_buffer)
> + return -ENOMEM;
> + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> + return -ENOMEM;
> + }
> +
> + /* Bulk pipe maximum packet for each transaction */
> + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> +
> + mutex_init(&nct6694->access_lock);
> + nct6694->udev = udev;
> + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> +
> + INIT_LIST_HEAD(&nct6694->handler_list);
> + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> + usb_set_intfdata(iface, nct6694);
> +
> + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> + ARRAY_SIZE(nct6694_dev));
> + if (ret) {
> + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> + goto err_mfd;
> + }
> +
> + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
Where is the async_workqueue used?
> +
> + dev_info(&udev->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 ret;
> +}
> +
> +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);
> + flush_workqueue(nct6694->async_workqueue);
> + destroy_workqueue(nct6694->async_workqueue);
> + usb_set_intfdata(iface, NULL);
I think this is not needed.
> + usb_kill_urb(nct6694->int_in_urb);
> + usb_free_urb(nct6694->int_in_urb);
> +}
> +
> +static const struct usb_device_id nct6694_ids[] = {
> + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
> + {},
> +};
> +MODULE_DEVICE_TABLE(usb, nct6694_ids);
> +
> +static struct usb_driver nct6694_usb_driver = {
> + .name = DRVNAME,
> + .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..0797564363be
> --- /dev/null
> +++ b/include/linux/mfd/nct6694.h
> @@ -0,0 +1,168 @@
> +/* 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_IIO "nct6694-iio"
> +#define NCT6694_DEV_HWMON "nct6694-hwmon"
> +#define NCT6694_DEV_PWM "nct6694-pwm"
> +#define NCT6694_DEV_RTC "nct6694-rtc"
> +
> +#define NCT6694_VENDOR_ID 0x0416
> +#define NCT6694_PRODUCT_ID 0x200B
> +#define INT_IN_ENDPOINT 0x81
> +#define BULK_IN_ENDPOINT 0x82
In Linux we don't add the 0x80 for IN endpoint, the framework does this
for you.
> +#define BULK_OUT_ENDPOINT 0x03
> +#define MAX_PACKET_SZ 0x100
> +
> +#define CMD_PACKET_SZ 0x8
> +#define HCTRL_SET 0x40
> +#define HCTRL_GET 0x80
> +
> +#define REQUEST_MOD_IDX 0x01
> +#define REQUEST_CMD_IDX 0x02
> +#define REQUEST_SEL_IDX 0x03
> +#define REQUEST_HCTRL_IDX 0x04
> +#define REQUEST_LEN_L_IDX 0x06
> +#define REQUEST_LEN_H_IDX 0x07
> +
> +#define RESPONSE_STS_IDX 0x01
> +
> +#define INT_IN_IRQ_IDX 0x00
> +#define GPIO_IRQ_STATUS BIT(0)
> +#define CAN_IRQ_STATUS BIT(2)
> +#define RTC_IRQ_STATUS BIT(3)
> +
> +#define URB_TIMEOUT 1000
> +
> +/*
> + * struct nct6694 - Nuvoton NCT6694 structure
> + *
> + * @udev: Pointer to the USB device
> + * @int_in_urb: Interrupt pipe urb
> + * @access_lock: USB transaction lock
> + * @handler_list: List of registered handlers
> + * @async_workqueue: Workqueue of processing asynchronous work
> + * @tx_buffer: USB write message buffer
> + * @rx_buffer: USB read message buffer
> + * @cmd_buffer: USB send command message buffer
> + * @int_buffer: USB receive interrupt message buffer
> + * @lock: Handlers lock
> + * @timeout: URB timeout
> + * @maxp: Maximum packet of bulk pipe
> + */
> +struct nct6694 {
> + struct usb_device *udev;
> + struct urb *int_in_urb;
> + struct list_head handler_list;
> + struct workqueue_struct *async_workqueue;
> +
> + /* Make sure that every USB transaction is not interrupted */
> + struct mutex access_lock;
> +
> + unsigned char *tx_buffer;
> + unsigned char *rx_buffer;
> + unsigned char *cmd_buffer;
> + unsigned char *int_buffer;
> +
> + /* Prevent races within handlers */
> + spinlock_t lock;
> +
> + /* time in msec to wait for the urb to the complete */
> + long timeout;
> +
> + /* Bulk pipe maximum packet for each transaction */
> + int maxp;
> +};
> +
> +/*
> + * struct nct6694_handler_entry - Stores the interrupt handling information
> + * for each registered peripheral
> + *
> + * @irq_bit: The bit in irq_status[INT_IN_IRQ_IDX] representing interrupt
^^^
I think this comment could be more precise, you can emphasize, that it's
not the bit number but the bit mask.
> + * @handler: Function pointer to the interrupt handler of the peripheral
> + * @private_data: Private data specific to the peripheral driver
> + * @list: Node used to link to the handler_list
> + */
> +struct nct6694_handler_entry {
> + int irq_bit;
the int_status you compare against in the IRQ callback ist a unsigned
char. Better make all a u8.
> + void (*handler)(void *private_data);
> + void *private_data;
> + struct list_head list;
> +};
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 12:12 ` Marc Kleine-Budde
@ 2024-10-24 15:28 ` Marc Kleine-Budde
2024-11-01 5:32 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 15:28 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 3213 bytes --]
On 24.10.2024 14:12:44, Marc Kleine-Budde wrote:
> Hello,
>
> thanks for your contribution. It seems to me that there is no proper
> TX-flow control and I have some questions.
>
> On 24.10.2024 16:59:17, Ming Yu wrote:
>
> [...]
>
> > +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> > + struct net_device *ndev)
> > +{
> > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > + struct nct6694 *nct6694 = priv->nct6694;
> > + struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> > + struct net_device_stats *stats = &ndev->stats;
> > + int can_idx = priv->can_idx;
> > + u32 txid = 0;
> > + int i;
> > + unsigned int echo_byte;
> > + u8 data_buf[REQUEST_CAN_CMD10_LEN] = {0};
> > +
> > + if (can_dropped_invalid_skb(ndev, skb))
> > + return NETDEV_TX_OK;
> > +
> > + /*
> > + * No check for NCT66794 because the TX bit is read-clear
> > + * and may be read-cleared by other function
> > + * Just check the result of tx command.
> > + */
>
> Where do you check the result of the TX command?
>
> > + /* Check if the TX buffer is full */
>
> Where's the check if the TX buffer is full?
>
> > + netif_stop_queue(ndev);
> > +
> > + if (can_idx == 0)
> > + data_buf[CAN_TAG_IDX] = CAN_TAG_CAN0;
> > + else
> > + data_buf[CAN_TAG_IDX] = 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.
> > + */
> > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_EFF;
> > + } else {
> > + txid = cf->can_id & CAN_SFF_MASK;
> > + }
> > +
> > + set_buf32(&data_buf[CAN_ID_IDX], txid);
> > +
> > + data_buf[CAN_DLC_IDX] = cf->len;
> > +
> > + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) {
> > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_FD;
> > + if (cf->flags & CANFD_BRS)
> > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_BRS;
> > + }
> > +
> > + if (cf->can_id & CAN_RTR_FLAG)
> > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_RTR;
> > +
> > + /* set data to buf */
> > + for (i = 0; i < cf->len; i++)
> > + data_buf[CAN_DATA_IDX + i] = *(u8 *)(cf->data + i);
> > +
> > + can_put_echo_skb(skb, ndev, 0, 0);
> > +
> > + memcpy(priv->data_buf, data_buf, REQUEST_CAN_CMD10_LEN);
> > + queue_work(nct6694->async_workqueue, &priv->tx_work);
> > +
> > + stats->tx_bytes += cf->len;
> > + stats->tx_packets++;
> > + echo_byte = can_get_echo_skb(ndev, 0, NULL);
> > +
> > + netif_wake_queue(ndev);
>
> How do you make sure that the tx_work has finished?
> Once you wake the queue, the xmit function can be called again. If your
> tx_work has not finished, you'll overwrite the priv->data_buf.
Do you get a CAN TX complete message/IRQ from your device?
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
@ 2024-10-24 15:32 ` Guenter Roeck
2024-10-28 9:49 ` Ming Yu
2024-10-24 16:06 ` Guenter Roeck
2024-10-26 9:19 ` kernel test robot
2 siblings, 1 reply; 80+ messages in thread
From: Guenter Roeck @ 2024-10-24 15:32 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
On 10/24/24 01:59, 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 | 329 +++++++++++++++++++++++++++++++++
> 4 files changed, 342 insertions(+)
> create mode 100644 drivers/watchdog/nct6694_wdt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index eccd5e795daa..63387c0d4ab6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16442,6 +16442,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 684b9fe84fff..bc9d63d69204 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 ab6f2b41e38e..453ceacd43ab 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -231,6 +231,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..68e2926ec504
> --- /dev/null
> +++ b/drivers/watchdog/nct6694_wdt.c
> @@ -0,0 +1,329 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 WDT driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/watchdog.h>
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mfd/core.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-wdt"
> +
> +#define WATCHDOG_TIMEOUT 10
> +#define WATCHDOG_PRETIMEOUT 0
> +
> +/* Host interface */
> +#define REQUEST_WDT_MOD 0x07
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define REQUEST_WDT_CMD0_LEN 0x0F
> +#define REQUEST_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */
> +#define WDT_PRETIMEOUT_IDX 0x00
> +#define WDT_PRETIMEOUT_LEN 0x04 /* PRETIMEOUT(3byte) | ACT(1byte) */
> +#define WDT_TIMEOUT_IDX 0x04
> +#define WDT_TIMEOUT_LEN 0x04 /* TIMEOUT(3byte) | ACT(1byte) */
> +#define WDT_COUNTDOWN_IDX 0x0C
> +#define WDT_COUNTDOWN_LEN 0x03
> +
> +#define WDT_PRETIMEOUT_ACT BIT(1)
> +#define WDT_TIMEOUT_ACT BIT(1)
> +
> +/* Command 01h */
> +#define REQUEST_WDT_CMD1_LEN 0x04
> +#define REQUEST_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */
> +#define WDT_CMD_IDX 0x00
> +#define WDT_CMD_LEN 0x04
> +
> +static unsigned int timeout;
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
> +
> +static unsigned int 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) ")");
> +
> +struct nct6694_wdt_data {
> + struct nct6694 *nct6694;
> + struct watchdog_device wdev;
> + unsigned int wdev_idx;
> +};
> +
> +static inline void set_buf32(void *buf, u32 u32_val)
> +{
> + u8 *p = (u8 *)buf;
> +
> + p[0] = u32_val & 0xFF;
> + p[1] = (u32_val >> 8) & 0xFF;
> + p[2] = (u32_val >> 16) & 0xFF;
> + p[3] = (u32_val >> 24) & 0xFF;
> +}
> +
> +static int nct6694_wdt_start(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> +
> + pr_debug("%s: WDT(%d) Start\n", __func__, data->wdev_idx);
> +
> + return 0;
> +}
> +
> +static int nct6694_wdt_stop(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'C'};
> + int ret;
> +
> + pr_debug("%s: WDT(%d) Close\n", __func__, data->wdev_idx);
> + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD1_LEN, buf);
> + if (ret)
> + pr_err("%s: Failed to start WDT device!\n", __func__);
Please refrain from logging noise. Besides, the message is wrong:
the watchdog is stopped here, not started.
Also, all messages should use dev_, not pr_ functions.
> +
> + return ret;
> +}
> +
> +static int nct6694_wdt_ping(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'S'};
> + int ret;
> +
> + pr_debug("%s: WDT(%d) Ping\n", __func__, data->wdev_idx);
> + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD1_LEN, buf);
> + if (ret)
> + pr_err("%s: Failed to ping WDT device!\n", __func__);
Same as above and everywhere else.
> +
> + return ret;
> +}
> +
> +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
> + unsigned int timeout)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned int timeout_fmt, pretimeout_fmt;
> + unsigned char buf[REQUEST_WDT_CMD0_LEN];
> + int ret;
> +
> + if (timeout < wdev->pretimeout) {
> + pr_err("%s: 'timeout' must be greater than 'pre timeout'!\n",
> + __func__);
> + return -EINVAL;
the driver is supposed to adjust pretimeout in this case. And please,
again, refrain from logging noise.
> + }
> +
> + timeout_fmt = timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> +
> + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD0_LEN, buf);
> + if (ret) {
> + pr_err("%s: Don't write the setup command in Start stage!\n",
> + __func__);
> + return ret;
> + }
> +
> + wdev->timeout = timeout;
> +
> + return 0;
> +}
> +
> +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
> + unsigned int pretimeout)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned int timeout_fmt, pretimeout_fmt;
> + unsigned char buf[REQUEST_WDT_CMD0_LEN];
> + int ret;
> +
> + if (pretimeout > wdev->timeout) {
> + pr_err("%s: 'pre timeout' must be less than 'timeout'!\n",
> + __func__);
> + return -EINVAL;
Already checked by the watchdog core.
> + }
> + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> + pretimeout_fmt = pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> +
> + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD0_LEN, buf);
> + if (ret) {
> + pr_err("%s: Don't write the setup command in Start stage!\n", __func__);
Besides it being noise, I don't even understand what this message is
supposed to mean, and neither would anyone else.
> + return ret;
> + }
> +
> + wdev->pretimeout = pretimeout;
> + return 0;
> +}
> +
> +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned char buf[WDT_COUNTDOWN_LEN];
> + unsigned int timeleft_ms;
> + int ret;
> +
> + ret = nct6694_read_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD0_LEN, WDT_COUNTDOWN_IDX,
> + WDT_COUNTDOWN_LEN, buf);
> + if (ret)
> + pr_err("%s: Failed to get WDT device!\n", __func__);
> +
> + timeleft_ms = ((buf[2] << 16) | (buf[1] << 8) | buf[0]) & 0xFFFFFF;
If the above command failed this will be a random number.
> +
> + return timeleft_ms / 1000;
> +}
> +
> +static int nct6694_wdt_setup(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> + struct nct6694 *nct6694 = data->nct6694;
> + unsigned char buf[REQUEST_WDT_CMD0_LEN] = {0};
> + unsigned int timeout_fmt, pretimeout_fmt;
> + int ret;
> +
> + if (timeout)
> + wdev->timeout = timeout;
> +
Already set.
> + if (pretimeout) {
> + wdev->pretimeout = pretimeout;
Pretimeout is already set in the probe function. Do it completely there.
> + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> + } else {
> + pretimeout_fmt = 0;
> + }
> +
> + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> +
> + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> + REQUEST_WDT_CMD0_LEN, buf);
This seems pretty pointless at this time. Why not do it in the watchdog
start function ?
> + if (ret)
> + return ret;
> +
> + pr_info("Setting WDT(%d): timeout = %d, pretimeout = %d\n",
> + data->wdev_idx, wdev->timeout, wdev->pretimeout);
> +
> + return 0;
> +}
> +
> +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 nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + struct nct6694_wdt_data *data;
> + struct watchdog_device *wdev;
> + int ret;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->nct6694 = nct6694;
> + data->wdev_idx = cell->id;
> +
> + wdev = &data->wdev;
> + wdev->info = &nct6694_wdt_info;
> + wdev->ops = &nct6694_wdt_ops;
> + wdev->timeout = WATCHDOG_TIMEOUT;
> + wdev->pretimeout = WATCHDOG_PRETIMEOUT;
> + wdev->min_timeout = 1;
> + wdev->max_timeout = 255;
> +
> + platform_set_drvdata(pdev, data);
> +
> + /* Register watchdog timer device to WDT framework */
> + watchdog_set_drvdata(&data->wdev, data);
> + watchdog_init_timeout(&data->wdev, timeout, &pdev->dev);
> + watchdog_set_nowayout(&data->wdev, nowayout);
> + watchdog_stop_on_reboot(&data->wdev);
> +
> + ret = devm_watchdog_register_device(&pdev->dev, &data->wdev);
> + if (ret) {
> + dev_err(&pdev->dev, "%s: Failed to register watchdog device: %d\n",
> + __func__, ret);
> + return ret;
> + }
> +
> + ret = nct6694_wdt_setup(&data->wdev);
This is too late. It needs to be done before registering the watchdog.
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to setup WDT device!\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_wdt_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_wdt_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_wdt_driver);
> + if (!err) {
> + if (err)
> + platform_driver_unregister(&nct6694_wdt_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_wdt_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 15:20 ` Marc Kleine-Budde
@ 2024-10-24 15:34 ` Marc Kleine-Budde
2024-10-25 8:14 ` Ming Yu
2024-10-25 8:08 ` Ming Yu
1 sibling, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-24 15:34 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 2557 bytes --]
On 24.10.2024 17:20:57, Marc Kleine-Budde wrote:
[...]
> > + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->cmd_buffer)
> > + return -ENOMEM;
> > + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->rx_buffer)
> > + return -ENOMEM;
> > + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->tx_buffer)
> > + return -ENOMEM;
> > + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> > + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + /* Bulk pipe maximum packet for each transaction */
> > + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> > + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> > +
> > + mutex_init(&nct6694->access_lock);
> > + nct6694->udev = udev;
> > + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> > +
> > + INIT_LIST_HEAD(&nct6694->handler_list);
> > + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> > + usb_set_intfdata(iface, nct6694);
> > +
> > + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> > + ARRAY_SIZE(nct6694_dev));
> > + if (ret) {
> > + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> > + goto err_mfd;
> > + }
> > +
> > + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
>
> Where is the async_workqueue used?
Sorry - it's used in the driver, which live in separate directories -
you can ignore this comment.
But then the question comes up, it looks racy to _first_ add the devices
and _then_ the workqueue.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
2024-10-24 15:32 ` Guenter Roeck
@ 2024-10-24 16:06 ` Guenter Roeck
2024-10-26 9:19 ` kernel test robot
2 siblings, 0 replies; 80+ messages in thread
From: Guenter Roeck @ 2024-10-24 16:06 UTC (permalink / raw)
To: Ming Yu, tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl,
mailhol.vincent, andrew+netdev, davem, edumazet, kuba, pabeni,
wim, jdelvare, jic23, lars, ukleinek, alexandre.belloni
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
On 10/24/24 01:59, 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>
> ---
[ ... ]
> +
> +static int nct6694_wdt_start(struct watchdog_device *wdev)
> +{
> + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> +
> + pr_debug("%s: WDT(%d) Start\n", __func__, data->wdev_idx);
> +
> + return 0;
> +}
> +
That doesn't make sense. How is the watchdog started if not here ?
Something is conceptually wrong.
Guenter
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
[not found] ` <CAOoeyxUUOCSaDLK8=ox3hwDVu=Ej-ds4=FsS8F+9GfiE-8HYvg@mail.gmail.com>
@ 2024-10-25 7:12 ` Bartosz Golaszewski
0 siblings, 0 replies; 80+ messages in thread
From: Bartosz Golaszewski @ 2024-10-25 7:12 UTC (permalink / raw)
To: 游子民
Cc: tmyu0, lee, linus.walleij, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On Fri, Oct 25, 2024 at 4:53 AM 游子民 <a0282524688@gmail.com> wrote:
>
> Dear Bart,
>
> Thank you for your comments.
>
I'm not going to read HTML email. Please resend as plain text.
Bart
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-24 9:47 ` Bartosz Golaszewski
[not found] ` <CAOoeyxUUOCSaDLK8=ox3hwDVu=Ej-ds4=FsS8F+9GfiE-8HYvg@mail.gmail.com>
@ 2024-10-25 7:38 ` 游子民
2024-10-25 7:46 ` Bartosz Golaszewski
1 sibling, 1 reply; 80+ messages in thread
From: 游子民 @ 2024-10-25 7:38 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, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Sorry, resending this email in plain text format.
Dear Bart,
Thank you for your comments.
Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年10月24日 週四 下午5:47寫道:
>
> On Thu, Oct 24, 2024 at 10:59 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 | 489 ++++++++++++++++++++++++++++++++++++
> > 4 files changed, 503 insertions(+)
> > create mode 100644 drivers/gpio/gpio-nct6694.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 30157ca95cf3..2c86d5dab3f1 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16438,6 +16438,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 d93cd4f722b4..aa78ad9ff4ac 100644
> > --- a/drivers/gpio/Kconfig
> > +++ b/drivers/gpio/Kconfig
> > @@ -1450,6 +1450,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 1429e8c0229b..02c94aa28017 100644
> > --- a/drivers/gpio/Makefile
> > +++ b/drivers/gpio/Makefile
> > @@ -121,6 +121,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..42c0e6e76730
> > --- /dev/null
> > +++ b/drivers/gpio/gpio-nct6694.c
> > @@ -0,0 +1,489 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 GPIO controller driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/gpio.h>
>
> Don't include this header. It's documented as obsolete.
[Ming] Okay! I'll drop it in the next patch.
>
> > +#include <linux/gpio/driver.h>
> > +#include <linux/module.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +
>
> You only use it once, drop it.
[Ming] That line is blank, did you mean #include <linux/gpio.h>?
>
> > +#define DRVNAME "nct6694-gpio"
> > +
> > +/* Host interface */
> > +#define REQUEST_GPIO_MOD 0xFF
> > +#define REQUEST_GPIO_LEN 0x01
> > +
> > +/* Report Channel */
> > +#define GPIO_VER_REG 0x90
> > +#define GPIO_VALID_REG 0x110
> > +#define GPI_DATA_REG 0x120
> > +#define GPO_DIR_REG 0x170
> > +#define GPO_TYPE_REG 0x180
> > +#define GPO_DATA_REG 0x190
> > +
> > +#define GPI_STS_REG 0x130
> > +#define GPI_CLR_REG 0x140
> > +#define GPI_FALLING_REG 0x150
> > +#define GPI_RISING_REG 0x160
> > +
>
> Please use the NCT6694 prefix for these defines, otherwise it's not
> clear whether they come from the driver or from GPIO core.
>
> []
[Ming] Okay! I'll add the prefix to the defines in the next patch.
>
> > +
> > +static const char * const nct6694_gpio_name[] = {
> > + "NCT6694-GPIO0",
> > + "NCT6694-GPIO1",
> > + "NCT6694-GPIO2",
> > + "NCT6694-GPIO3",
> > + "NCT6694-GPIO4",
> > + "NCT6694-GPIO5",
> > + "NCT6694-GPIO6",
> > + "NCT6694-GPIO7",
> > + "NCT6694-GPIO8",
> > + "NCT6694-GPIO9",
> > + "NCT6694-GPIOA",
> > + "NCT6694-GPIOB",
> > + "NCT6694-GPIOC",
> > + "NCT6694-GPIOD",
> > + "NCT6694-GPIOE",
> > + "NCT6694-GPIOF",
> > +};
>
> This looks like it corresponds with the MFD cells and makes me wonder:
> am I getting that wrong or do you want to register 0xf GPIO chips? Or
> a single GPIO chip with 0xf lines? What is the topology?
[Ming] Yes, it corresponds to the MFD cells.
I would like to register 16 GPIO chips, each with 8 lines.
The chip has 128 pins totally, the core can check if the pin is valid through
the init_valid_mask() callback.
>
> > +
> > +static int nct6694_gpio_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_gpio_data *data;
> > + struct gpio_irq_chip *girq;
> > + int ret;
> > +
> > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->nct6694 = nct6694;
> > + data->group = cell->id;
> > +
> > + data->gpio.label = nct6694_gpio_name[cell->id];
> > + 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 = 8;
> > +
> > + INIT_WORK(&data->irq_work, nct6694_irq);
> > + INIT_WORK(&data->irq_trig_work, nct6694_irq_trig);
> > + mutex_init(&data->irq_lock);
> > +
> > + ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS,
> > + nct6694_gpio_handler, data);
> > + if (ret) {
> > + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> > + __func__, ERR_PTR(ret));
> > + return ret;
> > + }
> > +
> > + platform_set_drvdata(pdev, data);
> > +
> > + ret = nct6694_get_irq_trig(data);
> > + if (ret)
> > + return ret;
> > +
> > + /* 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 = gpiochip_add_data(&data->gpio, data);
> > + if (ret) {
> > + dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe",
> > + __func__, ERR_PTR(ret));
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void nct6694_gpio_remove(struct platform_device *pdev)
> > +{
> > + struct nct6694_gpio_data *data = platform_get_drvdata(pdev);
> > +
> > + gpiochip_remove(&data->gpio);
>
> This should be dropped in favor of using devm_gpiochip_add_data().
> Especially since you probably want to cancel the irq_work before
> removing the chip.
[Ming] Okay! I'll change it in the next patch.
>
> > + cancel_work(&data->irq_work);
> > + cancel_work(&data->irq_trig_work);
> > +}
> > +
> > +static struct platform_driver nct6694_gpio_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_gpio_probe,
> > + .remove = nct6694_gpio_remove,
> > +};
> > +
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_gpio_driver);
> > + if (!err) {
> > + if (err)
>
> If err is equal to 0, check if it's not equal to zero?
>
> > + platform_driver_unregister(&nct6694_gpio_driver);
>
> If platform_driver_register() failed, then the device was never registered.
>
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
>
> Any reason why this must be initialized earlier? It's a USB driver after all.
[Ming] For platform driver registration, I'll change it to
module_platform_driver()
in the next patch.
>
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_gpio_driver);
> > +}
> > +module_exit(nct6694_exit);
> > +
> > +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.34.1
> >
>
> Bart
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-25 7:38 ` 游子民
@ 2024-10-25 7:46 ` Bartosz Golaszewski
2024-10-28 8:56 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Bartosz Golaszewski @ 2024-10-25 7:46 UTC (permalink / raw)
To: 游子民
Cc: tmyu0, lee, linus.walleij, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On Fri, Oct 25, 2024 at 9:39 AM 游子民 <a0282524688@gmail.com> wrote:
>
> Sorry, resending this email in plain text format.
>
> Dear Bart,
>
> Thank you for your comments.
>
> Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年10月24日 週四 下午5:47寫道:
> >
> > On Thu, Oct 24, 2024 at 10:59 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 | 489 ++++++++++++++++++++++++++++++++++++
> > > 4 files changed, 503 insertions(+)
> > > create mode 100644 drivers/gpio/gpio-nct6694.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 30157ca95cf3..2c86d5dab3f1 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -16438,6 +16438,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 d93cd4f722b4..aa78ad9ff4ac 100644
> > > --- a/drivers/gpio/Kconfig
> > > +++ b/drivers/gpio/Kconfig
> > > @@ -1450,6 +1450,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 1429e8c0229b..02c94aa28017 100644
> > > --- a/drivers/gpio/Makefile
> > > +++ b/drivers/gpio/Makefile
> > > @@ -121,6 +121,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..42c0e6e76730
> > > --- /dev/null
> > > +++ b/drivers/gpio/gpio-nct6694.c
> > > @@ -0,0 +1,489 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Nuvoton NCT6694 GPIO controller driver based on USB interface.
> > > + *
> > > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > > + */
> > > +
> > > +#include <linux/gpio.h>
> >
> > Don't include this header. It's documented as obsolete.
>
> [Ming] Okay! I'll drop it in the next patch.
>
> >
> > > +#include <linux/gpio/driver.h>
> > > +#include <linux/module.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/mfd/core.h>
> > > +#include <linux/mfd/nct6694.h>
> > > +
> >
> > You only use it once, drop it.
>
> [Ming] That line is blank, did you mean #include <linux/gpio.h>?
>
> >
> > > +#define DRVNAME "nct6694-gpio"
I meant this line. Just put the driver name in the driver struct
definition directly.
> > > +
> > > +/* Host interface */
> > > +#define REQUEST_GPIO_MOD 0xFF
> > > +#define REQUEST_GPIO_LEN 0x01
> > > +
> > > +/* Report Channel */
> > > +#define GPIO_VER_REG 0x90
> > > +#define GPIO_VALID_REG 0x110
> > > +#define GPI_DATA_REG 0x120
> > > +#define GPO_DIR_REG 0x170
> > > +#define GPO_TYPE_REG 0x180
> > > +#define GPO_DATA_REG 0x190
> > > +
> > > +#define GPI_STS_REG 0x130
> > > +#define GPI_CLR_REG 0x140
> > > +#define GPI_FALLING_REG 0x150
> > > +#define GPI_RISING_REG 0x160
> > > +
> >
> > Please use the NCT6694 prefix for these defines, otherwise it's not
> > clear whether they come from the driver or from GPIO core.
> >
> > []
>
> [Ming] Okay! I'll add the prefix to the defines in the next patch.
>
> >
> > > +
> > > +static const char * const nct6694_gpio_name[] = {
> > > + "NCT6694-GPIO0",
> > > + "NCT6694-GPIO1",
> > > + "NCT6694-GPIO2",
> > > + "NCT6694-GPIO3",
> > > + "NCT6694-GPIO4",
> > > + "NCT6694-GPIO5",
> > > + "NCT6694-GPIO6",
> > > + "NCT6694-GPIO7",
> > > + "NCT6694-GPIO8",
> > > + "NCT6694-GPIO9",
> > > + "NCT6694-GPIOA",
> > > + "NCT6694-GPIOB",
> > > + "NCT6694-GPIOC",
> > > + "NCT6694-GPIOD",
> > > + "NCT6694-GPIOE",
> > > + "NCT6694-GPIOF",
> > > +};
> >
> > This looks like it corresponds with the MFD cells and makes me wonder:
> > am I getting that wrong or do you want to register 0xf GPIO chips? Or
> > a single GPIO chip with 0xf lines? What is the topology?
>
> [Ming] Yes, it corresponds to the MFD cells.
> I would like to register 16 GPIO chips, each with 8 lines.
> The chip has 128 pins totally, the core can check if the pin is valid through
> the init_valid_mask() callback.
>
Ok, that's fine but the GPIO chip names should be in the MFD driver
only, it doesn't make sense to have them here. It's the MFD core that
will register the GPIO platform devices.
No for line names - as this is a dynamic USB expander, I'd suggest to
have them in the driver and assign to gc->names.
> >
> > > +
> > > +static int nct6694_gpio_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_gpio_data *data;
> > > + struct gpio_irq_chip *girq;
> > > + int ret;
> > > +
> > > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > > + if (!data)
> > > + return -ENOMEM;
> > > +
> > > + data->nct6694 = nct6694;
> > > + data->group = cell->id;
> > > +
> > > + data->gpio.label = nct6694_gpio_name[cell->id];
> > > + 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 = 8;
> > > +
> > > + INIT_WORK(&data->irq_work, nct6694_irq);
> > > + INIT_WORK(&data->irq_trig_work, nct6694_irq_trig);
> > > + mutex_init(&data->irq_lock);
> > > +
> > > + ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS,
> > > + nct6694_gpio_handler, data);
> > > + if (ret) {
> > > + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> > > + __func__, ERR_PTR(ret));
> > > + return ret;
> > > + }
> > > +
> > > + platform_set_drvdata(pdev, data);
> > > +
> > > + ret = nct6694_get_irq_trig(data);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + /* 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 = gpiochip_add_data(&data->gpio, data);
> > > + if (ret) {
> > > + dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe",
> > > + __func__, ERR_PTR(ret));
> > > + return ret;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void nct6694_gpio_remove(struct platform_device *pdev)
> > > +{
> > > + struct nct6694_gpio_data *data = platform_get_drvdata(pdev);
> > > +
> > > + gpiochip_remove(&data->gpio);
> >
> > This should be dropped in favor of using devm_gpiochip_add_data().
> > Especially since you probably want to cancel the irq_work before
> > removing the chip.
>
> [Ming] Okay! I'll change it in the next patch.
>
> >
> > > + cancel_work(&data->irq_work);
> > > + cancel_work(&data->irq_trig_work);
> > > +}
> > > +
> > > +static struct platform_driver nct6694_gpio_driver = {
> > > + .driver = {
> > > + .name = DRVNAME,
> > > + },
> > > + .probe = nct6694_gpio_probe,
> > > + .remove = nct6694_gpio_remove,
> > > +};
> > > +
> > > +static int __init nct6694_init(void)
> > > +{
> > > + int err;
> > > +
> > > + err = platform_driver_register(&nct6694_gpio_driver);
> > > + if (!err) {
> > > + if (err)
> >
> > If err is equal to 0, check if it's not equal to zero?
> >
> > > + platform_driver_unregister(&nct6694_gpio_driver);
> >
> > If platform_driver_register() failed, then the device was never registered.
> >
> > > + }
> > > +
> > > + return err;
> > > +}
> > > +subsys_initcall(nct6694_init);
> >
> > Any reason why this must be initialized earlier? It's a USB driver after all.
>
> [Ming] For platform driver registration, I'll change it to
> module_platform_driver()
> in the next patch.
>
Thanks,
Bartosz
> >
> > > +
> > > +static void __exit nct6694_exit(void)
> > > +{
> > > + platform_driver_unregister(&nct6694_gpio_driver);
> > > +}
> > > +module_exit(nct6694_exit);
> > > +
> > > +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> > > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > > +MODULE_LICENSE("GPL");
> > > --
> > > 2.34.1
> > >
> >
> > Bart
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support
2024-10-24 10:41 ` Andi Shyti
@ 2024-10-25 7:47 ` 游子民
0 siblings, 0 replies; 80+ messages in thread
From: 游子民 @ 2024-10-25 7:47 UTC (permalink / raw)
To: Andi Shyti
Cc: tmyu0, lee, linus.walleij, brgl, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Sorry, resending this email in plain text format.
Dear Andi,
Thank you for your comments.
Andi Shyti <andi.shyti@kernel.org> 於 2024年10月24日 週四 下午6:41寫道:
>
> Hi Ming,
>
> ...
>
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_i2c_driver);
> > + if (!err) {
> > + if (err)
> > + platform_driver_unregister(&nct6694_i2c_driver);
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_i2c_driver);
> > +}
> > +module_exit(nct6694_exit);
>
> Have you thought about using auxiliary driver here?
> (auxiliary_bus.h)
[Ming] For platform driver registration, I'll change it to
module_platform_driver()
in the next patch.
>
> Thanks,
> Andi
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 9:03 ` Marc Kleine-Budde
@ 2024-10-25 8:00 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-25 8:00 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Sorry, resending this email in plain text format.
Dear Marc,
Thank you for your comments.
I'll add the nct6694_free_handler() function in the next patch.
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午5:12寫道:
>
> On 24.10.2024 16:59:14, Ming Yu wrote:
> > The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> > 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> > PWM, and RTC.
> >
> > This 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 register
> > a handler function 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 | 394 ++++++++++++++++++++++++++++++++++++
> > include/linux/mfd/nct6694.h | 168 +++++++++++++++
> > 5 files changed, 581 insertions(+)
> > create mode 100644 drivers/mfd/nct6694.c
> > create mode 100644 include/linux/mfd/nct6694.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index e9659a5a7fb3..30157ca95cf3 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16434,6 +16434,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 f9325bcce1b9..da2600958697 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -546,6 +546,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 2a9f91e81af8..2cf816d67d03 100644
> > --- a/drivers/mfd/Makefile
> > +++ b/drivers/mfd/Makefile
> > @@ -116,6 +116,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..9838c7be0b98
> > --- /dev/null
> > +++ b/drivers/mfd/nct6694.c
> > @@ -0,0 +1,394 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 MFD driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/io.h>
> > +#include <linux/usb.h>
> > +#include <linux/slab.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +
> > +#define DRVNAME "nct6694-usb_mfd"
> > +
> > +#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(IIO),
> > + MFD_DEV_SIMPLE(HWMON),
> > + MFD_DEV_SIMPLE(PWM),
> > + MFD_DEV_SIMPLE(RTC),
> > +};
> > +
> > +int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit,
> > + void (*handler)(void *), void *private_data)
> > +{
> > + struct nct6694_handler_entry *entry;
> > + unsigned long flags;
> > +
> > + entry = kmalloc(sizeof(*entry), GFP_KERNEL);
> > + if (!entry)
> > + return -ENOMEM;
> > +
> > + entry->irq_bit = irq_bit;
> > + entry->handler = handler;
> > + entry->private_data = private_data;
> > +
> > + spin_lock_irqsave(&nct6694->lock, flags);
> > + list_add_tail(&entry->list, &nct6694->handler_list);
> > + spin_unlock_irqrestore(&nct6694->lock, flags);
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(nct6694_register_handler);
>
> Where's the corresponding nct6694_free_handler() function?
>
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 9:57 ` Marc Kleine-Budde
@ 2024-10-25 8:02 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-25 8:02 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Sorry, resending this email in plain text format.
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午5:57寫道:
>
> On 24.10.2024 16:59:14, Ming Yu wrote:
> > +static int nct6694_usb_probe(struct usb_interface *iface,
> > + const struct usb_device_id *id)
> > +{
> > + struct usb_device *udev = interface_to_usbdev(iface);
> > + struct device *dev = &udev->dev;
> > + struct usb_host_interface *interface;
> > + struct usb_endpoint_descriptor *int_endpoint;
> > + struct nct6694 *nct6694;
> > + int pipe, maxp, bulk_pipe;
> > + int ret = EINVAL;
> > +
> > + interface = iface->cur_altsetting;
> > + /* Binding interface class : 0xFF */
> > + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> > + interface->desc.bInterfaceSubClass != 0x00 ||
> > + interface->desc.bInterfaceProtocol != 0x00)
> > + return -ENODEV;
>
> I think you can use USB_DEVICE_INFO() and remove this manual check
>
> https://elixir.bootlin.com/linux/v6.11.5/source/include/linux/usb.h#L1056
>
> [...]
[Ming] Okay! I'll remove it and change USB_DEVICE()
to USB_DEVICE_AND_INTERFACE_INFO().
>
> > +
> > +static const struct usb_device_id nct6694_ids[] = {
> > + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(usb, nct6694_ids);
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 15:20 ` Marc Kleine-Budde
2024-10-24 15:34 ` Marc Kleine-Budde
@ 2024-10-25 8:08 ` Ming Yu
2024-10-25 10:08 ` Marc Kleine-Budde
1 sibling, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-25 8:08 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Sorry, resending this email in plain text format.
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午11:21寫道:
>
> On 24.10.2024 16:59:14, Ming Yu wrote:
> > The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
> > 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
> > PWM, and RTC.
> >
> > This 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 register
> > a handler function that will be called when the USB device receives
> > its interrupt pipe.
>
> [...]
>
> > diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
> > new file mode 100644
> > index 000000000000..9838c7be0b98
> > --- /dev/null
> > +++ b/drivers/mfd/nct6694.c
>
> [...]
>
> > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > + u8 rd_idx, u8 rd_len, unsigned char *buf)
>
> why not make buf a void *?
[Ming] I'll change the type in the next patch.
>
> > +{
> > + struct usb_device *udev = nct6694->udev;
> > + unsigned char err_status;
> > + int len, packet_len, tx_len, rx_len;
> > + int i, ret;
> > +
> > + mutex_lock(&nct6694->access_lock);
> > +
> > + /* Send command packet to USB device */
> > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > +
> > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + goto err;
> > +
> > + /* Receive response packet from USB device */
> > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + goto err;
> > +
> > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > +
> > + /*
> > + * Segmented reception of messages that exceed the size of USB bulk
> > + * pipe packets.
> > + */
>
> The Linux USB stack can receive bulk messages longer than the max packet size.
[Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
The core will divide packet 256 bytes for high speed USB device, but
it is exceeds
the hardware limitation, so I am dividing it manually.
>
> > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > + if (len > nct6694->maxp)
> > + packet_len = nct6694->maxp;
> > + else
> > + packet_len = len;
> > +
> > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > + nct6694->rx_buffer + nct6694->maxp * i,
> > + packet_len, &rx_len, nct6694->timeout);
> > + if (ret)
> > + goto err;
> > + }
> > +
> > + for (i = 0; i < rd_len; i++)
> > + buf[i] = nct6694->rx_buffer[i + rd_idx];
>
> memcpy()?
>
> Or why don't you directly receive data into the provided buffer? Copying
> of the data doesn't make it faster.
>
> On the other hand, receiving directly into the target buffer means the
> target buffer must not live on the stack.
[Ming] Okay! I'll change it to memcpy().
This is my perspective: the data is uniformly received by the rx_bffer held
by the MFD device. does it need to be changed?
>
> > +
> > + if (err_status) {
> > + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
> > + ret = -EIO;
> > + }
> > +
> > +err:
> > + mutex_unlock(&nct6694->access_lock);
> > + return ret;
> > +}
> > +EXPORT_SYMBOL(nct6694_read_msg);
> > +
> > +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset,
> > + u16 length, unsigned char *buf)
> > +{
> > + struct usb_device *udev = nct6694->udev;
> > + unsigned char err_status;
> > + int len, packet_len, tx_len, rx_len;
> > + int i, ret;
> > +
> > + mutex_lock(&nct6694->access_lock);
> > +
> > + /* Send command packet to USB device */
> > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_SET;
> > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
>
> What about creating a struct that describes the cmd_buffer layout?
[Ming] I've thought about this before, thanks for your comments.
>
> > +
> > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + goto err;
> > +
> > + /*
> > + * Segmented transmission of messages that exceed the size of USB bulk
> > + * pipe packets.
> > + */
>
> same as above
>
> > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > + if (len > nct6694->maxp)
> > + packet_len = nct6694->maxp;
> > + else
> > + packet_len = len;
> > +
> > + memcpy(nct6694->tx_buffer + nct6694->maxp * i,
> > + buf + nct6694->maxp * i, packet_len);
> > +
> > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > + nct6694->tx_buffer + nct6694->maxp * i,
> > + packet_len, &tx_len, nct6694->timeout);
> > + if (ret)
> > + goto err;
> > + }
> > +
> > + /* Receive response packet from USB device */
> > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > + nct6694->timeout);
> > + if (ret)
> > + goto err;
> > +
> > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > +
> > + /*
> > + * Segmented reception of messages that exceed the size of USB bulk
> > + * pipe packets.
> > + */
>
> same as above
>
> > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > + if (len > nct6694->maxp)
> > + packet_len = nct6694->maxp;
> > + else
> > + packet_len = len;
> > +
> > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > + nct6694->rx_buffer + nct6694->maxp * i,
> > + packet_len, &rx_len, nct6694->timeout);
> > + if (ret)
> > + goto err;
> > + }
> > +
> > + memcpy(buf, nct6694->rx_buffer, length);
>
> why not rx into the destination buffer directly?
>
> > +
> > + if (err_status) {
> > + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status);
> > + ret = -EIO;
> > + }
> > +
> > +err:
> > + mutex_unlock(&nct6694->access_lock);
> > + return ret;
> > +}
> > +EXPORT_SYMBOL(nct6694_write_msg);
>
> [...]
>
> > +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;
> > + int pipe, maxp, bulk_pipe;
> > + int ret = EINVAL;
> > +
> > + interface = iface->cur_altsetting;
> > + /* Binding interface class : 0xFF */
> > + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> > + interface->desc.bInterfaceSubClass != 0x00 ||
> > + interface->desc.bInterfaceProtocol != 0x00)
> > + return -ENODEV;
> > +
> > + int_endpoint = &interface->endpoint[0].desc;
> > + if (!usb_endpoint_is_int_in(int_endpoint))
> > + return -ENODEV;
> > +
> > + nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL);
> > + if (!nct6694)
> > + return -ENOMEM;
> > +
> > + pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT);
> > + maxp = usb_maxpacket(udev, pipe);
>
> better move these 2 down to the usb_fill_int_urb().
[Ming] Okay! I'll move these in the next patch.
>
> > +
> > + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->cmd_buffer)
> > + return -ENOMEM;
> > + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->rx_buffer)
> > + return -ENOMEM;
> > + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->tx_buffer)
> > + return -ENOMEM;
> > + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> > + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + /* Bulk pipe maximum packet for each transaction */
> > + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> > + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> > +
> > + mutex_init(&nct6694->access_lock);
> > + nct6694->udev = udev;
> > + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> > +
> > + INIT_LIST_HEAD(&nct6694->handler_list);
> > + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> > + usb_set_intfdata(iface, nct6694);
> > +
> > + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> > + ARRAY_SIZE(nct6694_dev));
> > + if (ret) {
> > + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> > + goto err_mfd;
> > + }
> > +
> > + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
>
> Where is the async_workqueue used?
>
> > +
> > + dev_info(&udev->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 ret;
> > +}
> > +
> > +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);
> > + flush_workqueue(nct6694->async_workqueue);
> > + destroy_workqueue(nct6694->async_workqueue);
> > + usb_set_intfdata(iface, NULL);
>
> I think this is not needed.
[Ming] I'll remove it in the next patch.
>
> > + usb_kill_urb(nct6694->int_in_urb);
> > + usb_free_urb(nct6694->int_in_urb);
> > +}
> > +
> > +static const struct usb_device_id nct6694_ids[] = {
> > + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(usb, nct6694_ids);
> > +
> > +static struct usb_driver nct6694_usb_driver = {
> > + .name = DRVNAME,
> > + .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..0797564363be
> > --- /dev/null
> > +++ b/include/linux/mfd/nct6694.h
> > @@ -0,0 +1,168 @@
> > +/* 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_IIO "nct6694-iio"
> > +#define NCT6694_DEV_HWMON "nct6694-hwmon"
> > +#define NCT6694_DEV_PWM "nct6694-pwm"
> > +#define NCT6694_DEV_RTC "nct6694-rtc"
> > +
> > +#define NCT6694_VENDOR_ID 0x0416
> > +#define NCT6694_PRODUCT_ID 0x200B
> > +#define INT_IN_ENDPOINT 0x81
> > +#define BULK_IN_ENDPOINT 0x82
>
> In Linux we don't add the 0x80 for IN endpoint, the framework does this
> for you.
[Ming] I'll change it in the next patch.
>
> > +#define BULK_OUT_ENDPOINT 0x03
> > +#define MAX_PACKET_SZ 0x100
> > +
> > +#define CMD_PACKET_SZ 0x8
> > +#define HCTRL_SET 0x40
> > +#define HCTRL_GET 0x80
> > +
> > +#define REQUEST_MOD_IDX 0x01
> > +#define REQUEST_CMD_IDX 0x02
> > +#define REQUEST_SEL_IDX 0x03
> > +#define REQUEST_HCTRL_IDX 0x04
> > +#define REQUEST_LEN_L_IDX 0x06
> > +#define REQUEST_LEN_H_IDX 0x07
> > +
> > +#define RESPONSE_STS_IDX 0x01
> > +
> > +#define INT_IN_IRQ_IDX 0x00
> > +#define GPIO_IRQ_STATUS BIT(0)
> > +#define CAN_IRQ_STATUS BIT(2)
> > +#define RTC_IRQ_STATUS BIT(3)
> > +
> > +#define URB_TIMEOUT 1000
> > +
> > +/*
> > + * struct nct6694 - Nuvoton NCT6694 structure
> > + *
> > + * @udev: Pointer to the USB device
> > + * @int_in_urb: Interrupt pipe urb
> > + * @access_lock: USB transaction lock
> > + * @handler_list: List of registered handlers
> > + * @async_workqueue: Workqueue of processing asynchronous work
> > + * @tx_buffer: USB write message buffer
> > + * @rx_buffer: USB read message buffer
> > + * @cmd_buffer: USB send command message buffer
> > + * @int_buffer: USB receive interrupt message buffer
> > + * @lock: Handlers lock
> > + * @timeout: URB timeout
> > + * @maxp: Maximum packet of bulk pipe
> > + */
> > +struct nct6694 {
> > + struct usb_device *udev;
> > + struct urb *int_in_urb;
> > + struct list_head handler_list;
> > + struct workqueue_struct *async_workqueue;
> > +
> > + /* Make sure that every USB transaction is not interrupted */
> > + struct mutex access_lock;
> > +
> > + unsigned char *tx_buffer;
> > + unsigned char *rx_buffer;
> > + unsigned char *cmd_buffer;
> > + unsigned char *int_buffer;
> > +
> > + /* Prevent races within handlers */
> > + spinlock_t lock;
> > +
> > + /* time in msec to wait for the urb to the complete */
> > + long timeout;
> > +
> > + /* Bulk pipe maximum packet for each transaction */
> > + int maxp;
> > +};
> > +
> > +/*
> > + * struct nct6694_handler_entry - Stores the interrupt handling information
> > + * for each registered peripheral
> > + *
> > + * @irq_bit: The bit in irq_status[INT_IN_IRQ_IDX] representing interrupt
> ^^^
>
> I think this comment could be more precise, you can emphasize, that it's
> not the bit number but the bit mask.
[Ming] Okay! I'll change it in the next patch.
>
> > + * @handler: Function pointer to the interrupt handler of the peripheral
> > + * @private_data: Private data specific to the peripheral driver
> > + * @list: Node used to link to the handler_list
> > + */
> > +struct nct6694_handler_entry {
> > + int irq_bit;
>
> the int_status you compare against in the IRQ callback ist a unsigned
> char. Better make all a u8.
[Ming] Okay! I'll change it in the next patch.
>
> > + void (*handler)(void *private_data);
> > + void *private_data;
> > + struct list_head list;
> > +};
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 15:34 ` Marc Kleine-Budde
@ 2024-10-25 8:14 ` Ming Yu
2024-10-25 8:35 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-25 8:14 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Marc,
Excuse me, I'm a bit confused. Is there anything I need to
improve on?
Thanks,
Ming
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午11:34寫道:
>
> On 24.10.2024 17:20:57, Marc Kleine-Budde wrote:
>
> [...]
>
> > > + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> > > + sizeof(unsigned char), GFP_KERNEL);
> > > + if (!nct6694->cmd_buffer)
> > > + return -ENOMEM;
> > > + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > > + sizeof(unsigned char), GFP_KERNEL);
> > > + if (!nct6694->rx_buffer)
> > > + return -ENOMEM;
> > > + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > > + sizeof(unsigned char), GFP_KERNEL);
> > > + if (!nct6694->tx_buffer)
> > > + return -ENOMEM;
> > > + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> > > + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> > > + return -ENOMEM;
> > > + }
> > > +
> > > + /* Bulk pipe maximum packet for each transaction */
> > > + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> > > + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> > > +
> > > + mutex_init(&nct6694->access_lock);
> > > + nct6694->udev = udev;
> > > + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> > > +
> > > + INIT_LIST_HEAD(&nct6694->handler_list);
> > > + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> > > + usb_set_intfdata(iface, nct6694);
> > > +
> > > + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> > > + ARRAY_SIZE(nct6694_dev));
> > > + if (ret) {
> > > + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> > > + goto err_mfd;
> > > + }
> > > +
> > > + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
> >
> > Where is the async_workqueue used?
>
> Sorry - it's used in the driver, which live in separate directories -
> you can ignore this comment.
>
> But then the question comes up, it looks racy to _first_ add the devices
> and _then_ the workqueue.
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-24 11:57 ` [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Marc Kleine-Budde
@ 2024-10-25 8:22 ` Ming Yu
2024-10-25 8:33 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-25 8:22 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hi Marc,
I think the currently planned IRQ process meets expectations.
Is there anything that needs improvement?
Thanks,
Ming
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午7:57寫道:
>
> On 24.10.2024 16:59:13, Ming Yu wrote:
> > This patch series introduces support for Nuvoton NCT6694, a peripheral
> > expander based on USB interface. It models the chip as an MFD driver
> > (1/9), GPIO driver(2/9), I2C Adapter driver(3/9), CANfd driver(4/9),
> > WDT driver(5/9), HWMON driver(6/9), IIO driver(7/9), PWM driver(8/9),
> > and RTC driver(9/9).
> >
> > The MFD driver implements USB device functionality to issue
> > custom-define USB bulk pipe packets for NCT6694. Each child device can
> > use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
> > a command. They can also register a handler function that will be called
> > when the USB device receives its interrupt pipe.
>
> What about implementing a proper IRQ demux handler instead?
>
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-25 8:22 ` Ming Yu
@ 2024-10-25 8:33 ` Marc Kleine-Budde
2024-10-30 8:30 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-25 8:33 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1692 bytes --]
On 25.10.2024 16:22:01, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午7:57寫道:
> > On 24.10.2024 16:59:13, Ming Yu wrote:
> > > This patch series introduces support for Nuvoton NCT6694, a peripheral
> > > expander based on USB interface. It models the chip as an MFD driver
> > > (1/9), GPIO driver(2/9), I2C Adapter driver(3/9), CANfd driver(4/9),
> > > WDT driver(5/9), HWMON driver(6/9), IIO driver(7/9), PWM driver(8/9),
> > > and RTC driver(9/9).
> > >
> > > The MFD driver implements USB device functionality to issue
> > > custom-define USB bulk pipe packets for NCT6694. Each child device can
> > > use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
> > > a command. They can also register a handler function that will be called
> > > when the USB device receives its interrupt pipe.
> >
> > What about implementing a proper IRQ demux handler instead?
> I think the currently planned IRQ process meets expectations.
> Is there anything that needs improvement?
You can register the IRQs of the MFD device with the Linux kernel. This
way the devices can request a threaded IRQ handler directly via the
kernel function, instead of registering the callback.
With a threaded IRQ handler you can directly call the
nct6694_read_msg(), nct6694_write_msg() without the need to start a
workqueue from the callback.
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 8:14 ` Ming Yu
@ 2024-10-25 8:35 ` Marc Kleine-Budde
2024-10-25 9:02 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-25 8:35 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 3494 bytes --]
On 25.10.2024 16:14:03, Ming Yu wrote:
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午11:34寫道:
> >
> > On 24.10.2024 17:20:57, Marc Kleine-Budde wrote:
> >
> > [...]
> >
> > > > + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> > > > + sizeof(unsigned char), GFP_KERNEL);
> > > > + if (!nct6694->cmd_buffer)
> > > > + return -ENOMEM;
> > > > + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > > > + sizeof(unsigned char), GFP_KERNEL);
> > > > + if (!nct6694->rx_buffer)
> > > > + return -ENOMEM;
> > > > + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > > > + sizeof(unsigned char), GFP_KERNEL);
> > > > + if (!nct6694->tx_buffer)
> > > > + return -ENOMEM;
> > > > + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> > > > + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> > > > + return -ENOMEM;
> > > > + }
> > > > +
> > > > + /* Bulk pipe maximum packet for each transaction */
> > > > + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> > > > + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> > > > +
> > > > + mutex_init(&nct6694->access_lock);
> > > > + nct6694->udev = udev;
> > > > + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> > > > +
> > > > + INIT_LIST_HEAD(&nct6694->handler_list);
> > > > + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> > > > + usb_set_intfdata(iface, nct6694);
> > > > +
> > > > + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> > > > + ARRAY_SIZE(nct6694_dev));
> > > > + if (ret) {
> > > > + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> > > > + goto err_mfd;
> > > > + }
> > > > +
> > > > + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
> > >
> > > Where is the async_workqueue used?
> >
> > Sorry - it's used in the driver, which live in separate directories -
> > you can ignore this comment.
> >
> > But then the question comes up, it looks racy to _first_ add the devices
> > and _then_ the workqueue.
> Excuse me, I'm a bit confused. Is there anything I need to
> improve on?
It looks racy to _first_ add the devices and _then_ the workqueue.
So the obvious solution is to allocate the worklist first and then add
the devices.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 8:35 ` Marc Kleine-Budde
@ 2024-10-25 9:02 ` Marc Kleine-Budde
2024-10-25 10:22 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-25 9:02 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 631 bytes --]
On 25.10.2024 10:35:35, Marc Kleine-Budde wrote:
> > Excuse me, I'm a bit confused. Is there anything I need to
> > improve on?
>
> It looks racy to _first_ add the devices and _then_ the workqueue.
>
> So the obvious solution is to allocate the worklist first and then add
workqueue
> the devices.
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 8:08 ` Ming Yu
@ 2024-10-25 10:08 ` Marc Kleine-Budde
2024-10-25 11:03 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-25 10:08 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 3935 bytes --]
On 25.10.2024 16:08:10, Ming Yu wrote:
> > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> >
> > why not make buf a void *?
>
> [Ming] I'll change the type in the next patch.
>
> >
> > > +{
> > > + struct usb_device *udev = nct6694->udev;
> > > + unsigned char err_status;
> > > + int len, packet_len, tx_len, rx_len;
> > > + int i, ret;
> > > +
> > > + mutex_lock(&nct6694->access_lock);
> > > +
> > > + /* Send command packet to USB device */
> > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > +
> > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > + nct6694->timeout);
> > > + if (ret)
> > > + goto err;
> > > +
> > > + /* Receive response packet from USB device */
> > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > + nct6694->timeout);
> > > + if (ret)
> > > + goto err;
> > > +
> > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > +
> > > + /*
> > > + * Segmented reception of messages that exceed the size of USB bulk
> > > + * pipe packets.
> > > + */
> >
> > The Linux USB stack can receive bulk messages longer than the max packet size.
>
> [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> The core will divide packet 256 bytes for high speed USB device, but
> it is exceeds
> the hardware limitation, so I am dividing it manually.
You say the endpoint descriptor is correctly reporting it's max packet
size of 128, but the Linux USB will send packets of 256 bytes?
>
> >
> > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > + if (len > nct6694->maxp)
> > > + packet_len = nct6694->maxp;
> > > + else
> > > + packet_len = len;
> > > +
> > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > + packet_len, &rx_len, nct6694->timeout);
> > > + if (ret)
> > > + goto err;
> > > + }
> > > +
> > > + for (i = 0; i < rd_len; i++)
> > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> >
> > memcpy()?
> >
> > Or why don't you directly receive data into the provided buffer? Copying
> > of the data doesn't make it faster.
> >
> > On the other hand, receiving directly into the target buffer means the
> > target buffer must not live on the stack.
>
> [Ming] Okay! I'll change it to memcpy().
fine!
> This is my perspective: the data is uniformly received by the rx_bffer held
> by the MFD device. does it need to be changed?
My question is: Why do you first receive into the nct6694->rx_buffer and
then memcpy() to the buffer provided by the caller, why don't you
directly receive into the memory provided by the caller?
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 9:02 ` Marc Kleine-Budde
@ 2024-10-25 10:22 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-25 10:22 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Got it! I'll make the change in the next patch.
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午5:02寫道:
>
> On 25.10.2024 10:35:35, Marc Kleine-Budde wrote:
> > > Excuse me, I'm a bit confused. Is there anything I need to
> > > improve on?
> >
> > It looks racy to _first_ add the devices and _then_ the workqueue.
> >
> > So the obvious solution is to allocate the worklist first and then add
> workqueue
> > the devices.
>
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 10:08 ` Marc Kleine-Budde
@ 2024-10-25 11:03 ` Ming Yu
2024-10-25 12:23 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-25 11:03 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Oh! I'm sorry about that I confused the packet size.
The NCT6694 bulk maximum packet size is 256 bytes,
and USB High speed bulk maximum packet size is 512 bytes.
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午6:08寫道:
>
> On 25.10.2024 16:08:10, Ming Yu wrote:
> > > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> > >
> > > why not make buf a void *?
> >
> > [Ming] I'll change the type in the next patch.
> >
> > >
> > > > +{
> > > > + struct usb_device *udev = nct6694->udev;
> > > > + unsigned char err_status;
> > > > + int len, packet_len, tx_len, rx_len;
> > > > + int i, ret;
> > > > +
> > > > + mutex_lock(&nct6694->access_lock);
> > > > +
> > > > + /* Send command packet to USB device */
> > > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > > +
> > > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > > + nct6694->timeout);
> > > > + if (ret)
> > > > + goto err;
> > > > +
> > > > + /* Receive response packet from USB device */
> > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > > + nct6694->timeout);
> > > > + if (ret)
> > > > + goto err;
> > > > +
> > > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > > +
> > > > + /*
> > > > + * Segmented reception of messages that exceed the size of USB bulk
> > > > + * pipe packets.
> > > > + */
> > >
> > > The Linux USB stack can receive bulk messages longer than the max packet size.
> >
> > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > The core will divide packet 256 bytes for high speed USB device, but
> > it is exceeds
> > the hardware limitation, so I am dividing it manually.
>
> You say the endpoint descriptor is correctly reporting it's max packet
> size of 128, but the Linux USB will send packets of 256 bytes?
[Ming] The endpoint descriptor is correctly reporting it's max packet
size of 256, but the Linux USB may send more than 256 (max is 512)
https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
>
> >
> > >
> > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > + if (len > nct6694->maxp)
> > > > + packet_len = nct6694->maxp;
> > > > + else
> > > > + packet_len = len;
> > > > +
> > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > + packet_len, &rx_len, nct6694->timeout);
> > > > + if (ret)
> > > > + goto err;
> > > > + }
> > > > +
> > > > + for (i = 0; i < rd_len; i++)
> > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > >
> > > memcpy()?
> > >
> > > Or why don't you directly receive data into the provided buffer? Copying
> > > of the data doesn't make it faster.
> > >
> > > On the other hand, receiving directly into the target buffer means the
> > > target buffer must not live on the stack.
> >
> > [Ming] Okay! I'll change it to memcpy().
>
> fine!
>
> > This is my perspective: the data is uniformly received by the rx_bffer held
> > by the MFD device. does it need to be changed?
>
> My question is: Why do you first receive into the nct6694->rx_buffer and
> then memcpy() to the buffer provided by the caller, why don't you
> directly receive into the memory provided by the caller?
[Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
using the MFD'd dynamically allocated buffer to submit URBs will better
manage USB-related operations
>
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
2024-10-24 12:12 ` Marc Kleine-Budde
2024-10-24 14:17 ` Marc Kleine-Budde
@ 2024-10-25 11:18 ` kernel test robot
2024-10-25 23:31 ` kernel test robot
3 siblings, 0 replies; 80+ messages in thread
From: kernel test robot @ 2024-10-25 11:18 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, jic23, lars, ukleinek, alexandre.belloni
Cc: oe-kbuild-all, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Hi Ming,
kernel test robot noticed the following build errors:
[auto build test ERROR on lee-mfd/for-mfd-next]
[also build test ERROR on brgl/gpio/for-next andi-shyti/i2c/i2c-host mkl-can-next/testing groeck-staging/hwmon-next jic23-iio/togreg abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.12-rc4 next-20241024]
[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/20241024-170528
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link: https://lore.kernel.org/r/20241024085922.133071-5-tmyu0%40nuvoton.com
patch subject: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20241025/202410251825.ZVzMFUSc-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 14.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241025/202410251825.ZVzMFUSc-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/202410251825.ZVzMFUSc-lkp@intel.com/
All errors (new ones prefixed by >>):
>> make[6]: *** No rule to make target 'drivers/net/can/nct6604_canfd.o', needed by 'drivers/net/can/built-in.a'.
make[6]: Target 'drivers/net/can/' not remade because of errors.
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 11:03 ` Ming Yu
@ 2024-10-25 12:23 ` Marc Kleine-Budde
2024-10-28 7:33 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-25 12:23 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 5312 bytes --]
On 25.10.2024 19:03:55, Ming Yu wrote:
> Oh! I'm sorry about that I confused the packet size.
> The NCT6694 bulk maximum packet size is 256 bytes,
> and USB High speed bulk maximum packet size is 512 bytes.
>
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午6:08寫道:
> >
> > On 25.10.2024 16:08:10, Ming Yu wrote:
> > > > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> > > >
> > > > why not make buf a void *?
> > >
> > > [Ming] I'll change the type in the next patch.
> > >
> > > >
> > > > > +{
> > > > > + struct usb_device *udev = nct6694->udev;
> > > > > + unsigned char err_status;
> > > > > + int len, packet_len, tx_len, rx_len;
> > > > > + int i, ret;
> > > > > +
> > > > > + mutex_lock(&nct6694->access_lock);
> > > > > +
> > > > > + /* Send command packet to USB device */
> > > > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > > > +
> > > > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > > > + nct6694->timeout);
> > > > > + if (ret)
> > > > > + goto err;
> > > > > +
> > > > > + /* Receive response packet from USB device */
> > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > > > + nct6694->timeout);
> > > > > + if (ret)
> > > > > + goto err;
> > > > > +
> > > > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > > > +
> > > > > + /*
> > > > > + * Segmented reception of messages that exceed the size of USB bulk
> > > > > + * pipe packets.
> > > > > + */
> > > >
> > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > >
> > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > The core will divide packet 256 bytes for high speed USB device, but
> > > it is exceeds
> > > the hardware limitation, so I am dividing it manually.
> >
> > You say the endpoint descriptor is correctly reporting it's max packet
> > size of 128, but the Linux USB will send packets of 256 bytes?
>
> [Ming] The endpoint descriptor is correctly reporting it's max packet
> size of 256, but the Linux USB may send more than 256 (max is 512)
> https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
AFAIK according to the USB-2.0 spec the maximum packet size for
high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
your device is a non-compliant USB device?
> > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > + if (len > nct6694->maxp)
> > > > > + packet_len = nct6694->maxp;
> > > > > + else
> > > > > + packet_len = len;
> > > > > +
> > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > + if (ret)
> > > > > + goto err;
> > > > > + }
> > > > > +
> > > > > + for (i = 0; i < rd_len; i++)
> > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > >
> > > > memcpy()?
> > > >
> > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > of the data doesn't make it faster.
> > > >
> > > > On the other hand, receiving directly into the target buffer means the
> > > > target buffer must not live on the stack.
> > >
> > > [Ming] Okay! I'll change it to memcpy().
> >
> > fine!
> >
> > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > by the MFD device. does it need to be changed?
> >
> > My question is: Why do you first receive into the nct6694->rx_buffer and
> > then memcpy() to the buffer provided by the caller, why don't you
> > directly receive into the memory provided by the caller?
>
> [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> using the MFD'd dynamically allocated buffer to submit URBs will better
> manage USB-related operations
The non-compliant max packet size limitation is unrelated to the
question which RX or TX buffer to use.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 9:20 ` Kalesh Anakkur Purayil
2024-10-24 14:53 ` Guenter Roeck
@ 2024-10-25 15:10 ` Ming Yu
1 sibling, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-25 15:10 UTC (permalink / raw)
To: Kalesh Anakkur Purayil
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Kalesh,
Thank you for your comments.
Ming
Kalesh Anakkur Purayil <kalesh-anakkur.purayil@broadcom.com> 於
2024年10月24日 週四 下午5:20寫道:
>
> On Thu, Oct 24, 2024 at 2:33 PM Ming Yu <a0282524688@gmail.com> 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 | 407 ++++++++++++++++++++++++++++++++++
> > 4 files changed, 419 insertions(+)
> > create mode 100644 drivers/hwmon/nct6694-hwmon.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 63387c0d4ab6..2aa87ad84156 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -167,6 +167,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..7d7d22a650b0
> > --- /dev/null
> > +++ b/drivers/hwmon/nct6694-hwmon.c
> > @@ -0,0 +1,407 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 HWMON driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/slab.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/nct6694.h>
> > +
> > +#define DRVNAME "nct6694-hwmon"
> > +
> > +/* Host interface */
> > +#define REQUEST_RPT_MOD 0xFF
> > +#define REQUEST_HWMON_MOD 0x00
> > +
> > +/* Report Channel */
> > +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
> > +#define HWMON_FIN_STS(x) (0x6E + (x))
> > +#define HWMON_PWM_IDX(x) (0x70 + (x))
> > +
> > +/* Message Channel*/
> > +/* Command 00h */
> > +#define REQUEST_HWMON_CMD0_LEN 0x40
> > +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> > +#define HWMON_FIN_EN(x) (0x04 + (x))
> > +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> > +/* Command 02h */
> > +#define REQUEST_HWMON_CMD2_LEN 0x90
> > +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> > +#define HWMON_SMI_CTRL_IDX 0x00
> > +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
> > +#define HWMON_CMD2_HYST_MASK 0x1F
> > +/* Command 03h */
> > +#define REQUEST_HWMON_CMD3_LEN 0x08
> > +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> > +
> > +struct nct6694_hwmon_data {
> > + struct nct6694 *nct6694;
> > +
> > + /* Make sure read & write commands are consecutive */
> > + struct mutex hwmon_lock;
> > +};
> > +
> > +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
> > + HWMON_F_MIN | HWMON_F_MIN_ALARM)
> > +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
> > +
> > +static const struct hwmon_channel_info *nct6694_info[] = {
> > + 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_fan_read(struct device *dev, u32 attr, int channel,
> > + long *val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[2];
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN,
> > + HWMON_FIN_EN(channel / 8),
> > + 1, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
> > +
> > + break;
> > +
> > + case hwmon_fan_input:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_FIN_IDX(channel), 2, 0,
> > + 2, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> > +
> > + break;
> > +
> > + case hwmon_fan_min:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN,
> > + HWMON_FIN_LIMIT_IDX(channel),
> > + 2, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> > +
> > + break;
> > +
> > + case hwmon_fan_min_alarm:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_FIN_STS(channel / 8),
> > + 1, 0, 1, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf[0] & BIT(channel % 8);
> > +
> > + break;
> > +
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +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 buf;
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_PWM_IDX(channel),
> > + 1, 0, 1, &buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf;
> > +
> > + break;
> > + case hwmon_pwm_freq:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN,
> > + HWMON_PWM_FREQ_IDX(channel),
> > + 1, &buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf * 25000 / 255;
> > +
> > + break;
> > +
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> > + long val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> [Kalesh] Please try to maintain RCT order for variable declaration
> > + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> > + u16 fan_val = (u16)val;
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + mutex_lock(&data->hwmon_lock);
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN, 0,
> > + REQUEST_HWMON_CMD0_LEN,
> > + enable_buf);
> > + if (ret)
> > + goto err;
> > +
> > + if (val)
> > + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
> > + else
> > + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN, enable_buf);
> > + if (ret)
> > + goto err;
> > +
> > + break;
> > +
> > + case hwmon_fan_min:
> > + mutex_lock(&data->hwmon_lock);
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, 0,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
> > + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > + break;
> > +
> > + default:
> > + ret = -EOPNOTSUPP;
> [Kalesh] If you initialize "ret = -EOPNOTSUPP;" during declararion,
> you can just break from here.
> > + goto err;
> > + }
> > +
> > +err:
> > + mutex_unlock(&data->hwmon_lock);
> > + return ret;
> > +}
> > +
> > +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long *val)
> > +{
> > + switch (type) {
> > + 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;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long val)
> > +{
> > + switch (type) {
> > + case hwmon_fan:
> > + return nct6694_fan_write(dev, attr, channel, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> [Kalesh] You can use simple if condition here than a switch like:
> if (type != hwmon_fan)
> return -EOPNOTSUPP;
> return nct6694_fan_write(dev, attr, channel, val);
> > +
> > + return 0;
> > +}
> > +
> > +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
> > + u32 attr, int channel)
> > +{
> > + switch (type) {
> > + case hwmon_fan:
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + case hwmon_fan_min:
> > + return 0644;
> [Kalesh] I think there is no need to leave a new line in between cases
> > +
> > + case hwmon_fan_input:
> > + case hwmon_fan_min_alarm:
> > + return 0444;
> > +
> > + default:
> > + return 0;
> > + }
> > +
> > + case hwmon_pwm:
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + case hwmon_pwm_freq:
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > +
> > + default:
> > + return 0;
> > + }
> > +
> > + return 0;
> [Kalesh] This return statement looks redundant as the execution never
> reaches here. Same comment applies to other functions above as well.
> > +}
> > +
> > +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)
> > +{
> > + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> > + int ret;
> > +
> > + /* Set Fan input Real Time alarm mode */
> > + mutex_lock(&data->hwmon_lock);
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, 0,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> [Kalesh] It would be better to rename the label as "unlock". Same
> comment on other functions as well.
> > +
> > + buf[HWMON_SMI_CTRL_IDX] = 0x02;
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > +err:
> > + mutex_unlock(&data->hwmon_lock);
> > + return ret;
> > +}
> > +
> > +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->nct6694 = nct6694;
> > + mutex_init(&data->hwmon_lock);
> > + platform_set_drvdata(pdev, data);
> > +
> > + ret = nct6694_hwmon_init(data);
> > + if (ret)
> > + return -EIO;
> > +
> > + /* Register hwmon device to HWMON framework */
> > + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> > + "nct6694", data,
> > + &nct6694_chip_info,
> > + NULL);
> > + if (IS_ERR(hwmon_dev)) {
> > + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
> > + __func__);
> > + return PTR_ERR(hwmon_dev);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver nct6694_hwmon_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_hwmon_probe,
> > +};
> > +
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_hwmon_driver);
> > + if (!err) {
> > + if (err)
> [Kalesh] This whole check looks strange. You can simplify this function as:
> return platform_driver_register(&nct6694_hwmon_driver);
> > + platform_driver_unregister(&nct6694_hwmon_driver);
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_hwmon_driver);
> > +}
> > +module_exit(nct6694_exit);
> > +
> > +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.34.1
> >
> >
>
>
> --
> Regards,
> Kalesh A P
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 14:53 ` Guenter Roeck
@ 2024-10-25 15:22 ` Ming Yu
2024-10-25 15:44 ` Guenter Roeck
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-25 15:22 UTC (permalink / raw)
To: Guenter Roeck
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, jic23, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Dear Guenter,
Thank you for your comments.
Guenter Roeck <linux@roeck-us.net> 於 2024年10月24日 週四 下午10:53寫道:
>
> On 10/24/24 02:20, Kalesh Anakkur Purayil wrote:
> > On Thu, Oct 24, 2024 at 2:33 PM Ming Yu <a0282524688@gmail.com> 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 | 407 ++++++++++++++++++++++++++++++++++
> >> 4 files changed, 419 insertions(+)
> >> create mode 100644 drivers/hwmon/nct6694-hwmon.c
> >>
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index 63387c0d4ab6..2aa87ad84156 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
> >> --- a/drivers/hwmon/Kconfig
> >> +++ b/drivers/hwmon/Kconfig
> >> @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
> >> --- a/drivers/hwmon/Makefile
> >> +++ b/drivers/hwmon/Makefile
> >> @@ -167,6 +167,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..7d7d22a650b0
> >> --- /dev/null
> >> +++ b/drivers/hwmon/nct6694-hwmon.c
> >> @@ -0,0 +1,407 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Nuvoton NCT6694 HWMON driver based on USB interface.
> >> + *
> >> + * Copyright (C) 2024 Nuvoton Technology Corp.
> >> + */
> >> +
> >> +#include <linux/slab.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/module.h>
> >> +#include <linux/hwmon.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/mfd/nct6694.h>
> >> +
> >> +#define DRVNAME "nct6694-hwmon"
> >> +
> >> +/* Host interface */
> >> +#define REQUEST_RPT_MOD 0xFF
> >> +#define REQUEST_HWMON_MOD 0x00
> >> +
> >> +/* Report Channel */
> >> +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
> >> +#define HWMON_FIN_STS(x) (0x6E + (x))
> >> +#define HWMON_PWM_IDX(x) (0x70 + (x))
> >> +
> >> +/* Message Channel*/
> >> +/* Command 00h */
> >> +#define REQUEST_HWMON_CMD0_LEN 0x40
> >> +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> >> +#define HWMON_FIN_EN(x) (0x04 + (x))
> >> +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> >> +/* Command 02h */
> >> +#define REQUEST_HWMON_CMD2_LEN 0x90
> >> +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> >> +#define HWMON_SMI_CTRL_IDX 0x00
> >> +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
> >> +#define HWMON_CMD2_HYST_MASK 0x1F
> >> +/* Command 03h */
> >> +#define REQUEST_HWMON_CMD3_LEN 0x08
> >> +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> >> +
> >> +struct nct6694_hwmon_data {
> >> + struct nct6694 *nct6694;
> >> +
> >> + /* Make sure read & write commands are consecutive */
> >> + struct mutex hwmon_lock;
> >> +};
> >> +
> >> +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
> >> + HWMON_F_MIN | HWMON_F_MIN_ALARM)
> >> +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
> >> +
> >> +static const struct hwmon_channel_info *nct6694_info[] = {
> >> + 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_fan_read(struct device *dev, u32 attr, int channel,
> >> + long *val)
> >> +{
> >> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> >> + unsigned char buf[2];
> >> + int ret;
> >> +
> >> + switch (attr) {
> >> + case hwmon_fan_enable:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD0_OFFSET,
> >> + REQUEST_HWMON_CMD0_LEN,
> >> + HWMON_FIN_EN(channel / 8),
> >> + 1, buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
> >> +
> >> + break;
> >> +
> >> + case hwmon_fan_input:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> >> + HWMON_FIN_IDX(channel), 2, 0,
> >> + 2, buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> >> +
> >> + break;
> >> +
> >> + case hwmon_fan_min:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD2_OFFSET,
> >> + REQUEST_HWMON_CMD2_LEN,
> >> + HWMON_FIN_LIMIT_IDX(channel),
> >> + 2, buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> >> +
> >> + break;
> >> +
> >> + case hwmon_fan_min_alarm:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> >> + HWMON_FIN_STS(channel / 8),
> >> + 1, 0, 1, buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = buf[0] & BIT(channel % 8);
> >> +
> >> + break;
> >> +
> >> + default:
> >> + return -EOPNOTSUPP;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +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 buf;
> >> + int ret;
> >> +
> >> + switch (attr) {
> >> + case hwmon_pwm_input:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> >> + HWMON_PWM_IDX(channel),
> >> + 1, 0, 1, &buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = buf;
> >> +
> >> + break;
> >> + case hwmon_pwm_freq:
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD0_OFFSET,
> >> + REQUEST_HWMON_CMD0_LEN,
> >> + HWMON_PWM_FREQ_IDX(channel),
> >> + 1, &buf);
> >> + if (ret)
> >> + return -EINVAL;
> >> +
> >> + *val = buf * 25000 / 255;
> >> +
> >> + break;
> >> +
> >> + default:
> >> + return -EOPNOTSUPP;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> >> + long val)
> >> +{
> >> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> >> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> > [Kalesh] Please try to maintain RCT order for variable declaration
>
> Ok, but that is already the case here ?
[Ming] Is there anything that needs to be changed?
>
> >> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> >> + u16 fan_val = (u16)val;
> >> + int ret;
> >> +
> >> + switch (attr) {
> >> + case hwmon_fan_enable:
> >> + mutex_lock(&data->hwmon_lock);
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD0_OFFSET,
> >> + REQUEST_HWMON_CMD0_LEN, 0,
> >> + REQUEST_HWMON_CMD0_LEN,
> >> + enable_buf);
> >> + if (ret)
> >> + goto err;
> >> +
> >> + if (val)
> >> + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
> >> + else
> >> + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
> >> +
> >> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD0_OFFSET,
> >> + REQUEST_HWMON_CMD0_LEN, enable_buf);
> >> + if (ret)
> >> + goto err;
> >> +
> >> + break;
> >> +
> >> + case hwmon_fan_min:
> >> + mutex_lock(&data->hwmon_lock);
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD2_OFFSET,
> >> + REQUEST_HWMON_CMD2_LEN, 0,
> >> + REQUEST_HWMON_CMD2_LEN, buf);
> >> + if (ret)
> >> + goto err;
> >> +
> >> + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
> >> + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
> >> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD2_OFFSET,
> >> + REQUEST_HWMON_CMD2_LEN, buf);
> >> + if (ret)
> >> + goto err;
> >> +
> >> + break;
> >> +
> >> + default:
> >> + ret = -EOPNOTSUPP;
> > [Kalesh] If you initialize "ret = -EOPNOTSUPP;" during declararion,
> > you can just break from here.
>
> You are missing the point. The lock wasn't acquired here in the first place.
> It is conceptually wrong to acquire a lock in the switch statement and release
> it outside. This patch is a case in point.
[Ming] Okay! I'll acquire the lock before switch() in the next patch.
>
> >> + goto err;
> >> + }
> >> +
> >> +err:
> >> + mutex_unlock(&data->hwmon_lock);
> >> + return ret;
> >> +}
> >> +
> >> +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> >> + u32 attr, int channel, long *val)
> >> +{
> >> + switch (type) {
> >> + 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;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> >> + u32 attr, int channel, long val)
> >> +{
> >> + switch (type) {
> >> + case hwmon_fan:
> >> + return nct6694_fan_write(dev, attr, channel, val);
> >> + default:
> >> + return -EOPNOTSUPP;
> >> + }
> > [Kalesh] You can use simple if condition here than a switch like:
> > if (type != hwmon_fan)
> > return -EOPNOTSUPP;
> > return nct6694_fan_write(dev, attr, channel, val);
>
> That is a bit POV. I'd leave that to the developer.
> More important is that the return statements after the switch are unnecessary
> and never reached if each case returns immediately.
[Ming] Okay! I'll drop it in the next patch.
>
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
> >> + u32 attr, int channel)
> >> +{
> >> + switch (type) {
> >> + case hwmon_fan:
> >> + switch (attr) {
> >> + case hwmon_fan_enable:
> >> + case hwmon_fan_min:
> >> + return 0644;
> > [Kalesh] I think there is no need to leave a new line in between cases
> >> +
> >> + case hwmon_fan_input:
> >> + case hwmon_fan_min_alarm:
> >> + return 0444;
> >> +
> >> + default:
> >> + return 0;
> >> + }
> >> +
> >> + case hwmon_pwm:
> >> + switch (attr) {
> >> + case hwmon_pwm_input:
> >> + case hwmon_pwm_freq:
> >> + return 0444;
> >> + default:
> >> + return 0;
> >> + }
> >> +
> >> + default:
> >> + return 0;
> >> + }
> >> +
> >> + return 0;
> > [Kalesh] This return statement looks redundant as the execution never
> > reaches here. Same comment applies to other functions above as well.
> >> +}
> >> +
> >> +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)
> >> +{
> >> + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> >> + int ret;
> >> +
> >> + /* Set Fan input Real Time alarm mode */
> >> + mutex_lock(&data->hwmon_lock);
> >> + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD2_OFFSET,
> >> + REQUEST_HWMON_CMD2_LEN, 0,
> >> + REQUEST_HWMON_CMD2_LEN, buf);
> >> + if (ret)
> >> + goto err;
> > [Kalesh] It would be better to rename the label as "unlock". Same
> > comment on other functions as well.
>
> The lock is not needed here in the first place. The function is called
> exactly once during initialization.
[Ming] I'll remove the lock in the next patch!
>
> >> +
> >> + buf[HWMON_SMI_CTRL_IDX] = 0x02;
> >> +
> >> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> >> + REQUEST_HWMON_CMD2_OFFSET,
> >> + REQUEST_HWMON_CMD2_LEN, buf);
> >> + if (ret)
> >> + goto err;
> >> +
> >> +err:
> >> + mutex_unlock(&data->hwmon_lock);
> >> + return ret;
> >> +}
> >> +
> >> +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->nct6694 = nct6694;
> >> + mutex_init(&data->hwmon_lock);
> >> + platform_set_drvdata(pdev, data);
> >> +
> >> + ret = nct6694_hwmon_init(data);
> >> + if (ret)
> >> + return -EIO;
> >> +
> >> + /* Register hwmon device to HWMON framework */
> >> + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> >> + "nct6694", data,
> >> + &nct6694_chip_info,
> >> + NULL);
> >> + if (IS_ERR(hwmon_dev)) {
> >> + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
> >> + __func__);
> >> + return PTR_ERR(hwmon_dev);
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static struct platform_driver nct6694_hwmon_driver = {
> >> + .driver = {
> >> + .name = DRVNAME,
> >> + },
> >> + .probe = nct6694_hwmon_probe,
> >> +};
> >> +
> >> +static int __init nct6694_init(void)
> >> +{
> >> + int err;
> >> +
> >> + err = platform_driver_register(&nct6694_hwmon_driver);
> >> + if (!err) {
> >> + if (err)
> > [Kalesh] This whole check looks strange. You can simplify this function as:
> > return platform_driver_register(&nct6694_hwmon_driver);
> >> + platform_driver_unregister(&nct6694_hwmon_driver);
> >> + }
> >> +
> >> + return err;
> >> +}
> >> +subsys_initcall(nct6694_init);
> >> +
> >> +static void __exit nct6694_exit(void)
> >> +{
> >> + platform_driver_unregister(&nct6694_hwmon_driver);
> >> +}
> >> +module_exit(nct6694_exit);
> >> +
> >> +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> >> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> >> +MODULE_LICENSE("GPL");
> >> --
> >> 2.34.1
> >>
> >>
> >
> >
>
[Ming] For platform driver registration, I'll change it to
module_platform_driver() in the next patch.
Thank,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-24 15:03 ` Guenter Roeck
@ 2024-10-25 15:33 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-25 15:33 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,
jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hi Guenter,
I will remove the unnecessary return statement and lock, and update
the part of the code you mentioned.
Thanks,
Ming
Guenter Roeck <linux@roeck-us.net> 於 2024年10月24日 週四 下午11:03寫道:
>
> On 10/24/24 01:59, 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 | 407 ++++++++++++++++++++++++++++++++++
> > 4 files changed, 419 insertions(+)
> > create mode 100644 drivers/hwmon/nct6694-hwmon.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 63387c0d4ab6..2aa87ad84156 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16439,6 +16439,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 08a3c863f80a..740e4afe6582 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1625,6 +1625,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 9554d2fdcf7b..729961176d00 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -167,6 +167,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..7d7d22a650b0
> > --- /dev/null
> > +++ b/drivers/hwmon/nct6694-hwmon.c
> > @@ -0,0 +1,407 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 HWMON driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/slab.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/nct6694.h>
> > +
> > +#define DRVNAME "nct6694-hwmon"
> > +
> > +/* Host interface */
> > +#define REQUEST_RPT_MOD 0xFF
> > +#define REQUEST_HWMON_MOD 0x00
> > +
> > +/* Report Channel */
> > +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2))
> > +#define HWMON_FIN_STS(x) (0x6E + (x))
> > +#define HWMON_PWM_IDX(x) (0x70 + (x))
> > +
> > +/* Message Channel*/
> > +/* Command 00h */
> > +#define REQUEST_HWMON_CMD0_LEN 0x40
> > +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> > +#define HWMON_FIN_EN(x) (0x04 + (x))
> > +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> > +/* Command 02h */
> > +#define REQUEST_HWMON_CMD2_LEN 0x90
> > +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> > +#define HWMON_SMI_CTRL_IDX 0x00
> > +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2))
> > +#define HWMON_CMD2_HYST_MASK 0x1F
> > +/* Command 03h */
> > +#define REQUEST_HWMON_CMD3_LEN 0x08
> > +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> > +
> > +struct nct6694_hwmon_data {
> > + struct nct6694 *nct6694;
> > +
> > + /* Make sure read & write commands are consecutive */
> > + struct mutex hwmon_lock;
> > +};
> > +
> > +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \
> > + HWMON_F_MIN | HWMON_F_MIN_ALARM)
> > +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ)
> > +
> > +static const struct hwmon_channel_info *nct6694_info[] = {
> > + 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_fan_read(struct device *dev, u32 attr, int channel,
> > + long *val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[2];
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN,
> > + HWMON_FIN_EN(channel / 8),
> > + 1, buf);
> > + if (ret)
> > + return -EINVAL;
>
> Do not overwrite error return values.
>
> > +
> > + *val = buf[0] & BIT(channel % 8) ? 1 : 0;
> > +
> > + break;
> > +
> > + case hwmon_fan_input:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_FIN_IDX(channel), 2, 0,
> > + 2, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> > +
> > + break;
> > +
> > + case hwmon_fan_min:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN,
> > + HWMON_FIN_LIMIT_IDX(channel),
> > + 2, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF;
> > +
> > + break;
> > +
> > + case hwmon_fan_min_alarm:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_FIN_STS(channel / 8),
> > + 1, 0, 1, buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf[0] & BIT(channel % 8);
> > +
> > + break;
> > +
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +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 buf;
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> > + HWMON_PWM_IDX(channel),
> > + 1, 0, 1, &buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf;
> > +
> > + break;
> > + case hwmon_pwm_freq:
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN,
> > + HWMON_PWM_FREQ_IDX(channel),
> > + 1, &buf);
> > + if (ret)
> > + return -EINVAL;
> > +
> > + *val = buf * 25000 / 255;
> > +
> > + break;
> > +
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> > + long val)
> > +{
> > + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> > + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> > + u16 fan_val = (u16)val;
>
> This is wrong. It will result in overflows/underflows if out of range
> values are provided, and the driver should not write 0 if the user
> writes 65536. You need to use clamp_val() instead. For values with
> well defined range (specifically hwmon_fan_enable) you need to validate
> the parameter, not just take it as boolean.
[Ming] Okay! I'll update the code in the next patch.
>
> > + int ret;
> > +
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + mutex_lock(&data->hwmon_lock);
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN, 0,
> > + REQUEST_HWMON_CMD0_LEN,
> > + enable_buf);
> > + if (ret)
> > + goto err;
> > +
> > + if (val)
> > + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8);
> > + else
> > + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8);
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD0_OFFSET,
> > + REQUEST_HWMON_CMD0_LEN, enable_buf);
> > + if (ret)
> > + goto err;
> > +
> > + break;
> > +
> > + case hwmon_fan_min:
> > + mutex_lock(&data->hwmon_lock);
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, 0,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF);
> > + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF);
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > + break;
>
> The error handler goto and the break accomplish exactly the same,
> thus the conditional goto is pointless.
>
> > +
> > + default:
> > + ret = -EOPNOTSUPP;
> > + goto err;
>
> As mentioned in my other reply, the lock is not acquired here,
> causing an unbalanced unlock.
>
> > + }
> > +
> > +err:
> > + mutex_unlock(&data->hwmon_lock);
> > + return ret;
> > +}
> > +
> > +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long *val)
> > +{
> > + switch (type) {
> > + 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;
> > + }
> > +
> > + return 0;
>
> Unnecessary return statement. Also seen elsewhere.
>
> > +}
> > +
> > +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long val)
> > +{
> > + switch (type) {
> > + case hwmon_fan:
> > + return nct6694_fan_write(dev, attr, channel, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type,
> > + u32 attr, int channel)
> > +{
> > + switch (type) {
> > + 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_input:
> > + case hwmon_pwm_freq:
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > +
> > + default:
> > + return 0;
> > + }
> > +
> > + 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)
> > +{
> > + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0};
> > + int ret;
> > +
> > + /* Set Fan input Real Time alarm mode */
> > + mutex_lock(&data->hwmon_lock);
>
> Pointless lock (this is init code)
>
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, 0,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > + buf[HWMON_SMI_CTRL_IDX] = 0x02;
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> > + REQUEST_HWMON_CMD2_OFFSET,
> > + REQUEST_HWMON_CMD2_LEN, buf);
> > + if (ret)
> > + goto err;
> > +
> > +err:
> > + mutex_unlock(&data->hwmon_lock);
> > + return ret;
> > +}
> > +
> > +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->nct6694 = nct6694;
> > + mutex_init(&data->hwmon_lock);
> > + platform_set_drvdata(pdev, data);
> > +
> > + ret = nct6694_hwmon_init(data);
> > + if (ret)
> > + return -EIO;
>
> Again, do not overwrite error codes.
>
> > +
> > + /* Register hwmon device to HWMON framework */
> > + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> > + "nct6694", data,
> > + &nct6694_chip_info,
> > + NULL);
> > + if (IS_ERR(hwmon_dev)) {
> > + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n",
> > + __func__);
>
> Use dev_err_probe(), and the function name is pointless.
[Ming] Okay! I'll change it in the next patch.
>
> > + return PTR_ERR(hwmon_dev);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver nct6694_hwmon_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_hwmon_probe,
> > +};
> > +
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_hwmon_driver);
> > + if (!err) {
> > + if (err)
>
> Seriously ? Read this code again. It is never reached (and pointless).
[Ming] For platform driver registration, I'll change it to
module_platform_driver() in the next patch.
>
> > + platform_driver_unregister(&nct6694_hwmon_driver);
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_hwmon_driver);
> > +}
> > +module_exit(nct6694_exit);
> > +
> > +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-25 15:22 ` Ming Yu
@ 2024-10-25 15:44 ` Guenter Roeck
2024-10-26 14:50 ` Guenter Roeck
2024-10-28 7:42 ` Ming Yu
0 siblings, 2 replies; 80+ messages in thread
From: Guenter Roeck @ 2024-10-25 15:44 UTC (permalink / raw)
To: Ming Yu
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, jic23, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
On 10/25/24 08:22, Ming Yu wrote:
[ ... ]
>>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
>>>> + long val)
>>>> +{
>>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
>>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
>>> [Kalesh] Please try to maintain RCT order for variable declaration
>>
>> Ok, but that is already the case here ?
>
> [Ming] Is there anything that needs to be changed?
>
I don't think so, If two lines have the same length, the order is up
to the developer to decide.
Question though is if the buffer needs to be initialized. You should drop
the initialization if it is not necessary. In that case the second line
would be shorter anyway, and the order question would not arise.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
` (2 preceding siblings ...)
2024-10-25 11:18 ` kernel test robot
@ 2024-10-25 23:31 ` kernel test robot
3 siblings, 0 replies; 80+ messages in thread
From: kernel test robot @ 2024-10-25 23:31 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, jic23, lars, ukleinek, alexandre.belloni
Cc: oe-kbuild-all, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Hi Ming,
kernel test robot noticed the following build errors:
[auto build test ERROR on lee-mfd/for-mfd-next]
[also build test ERROR on brgl/gpio/for-next andi-shyti/i2c/i2c-host mkl-can-next/testing groeck-staging/hwmon-next jic23-iio/togreg abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.12-rc4 next-20241025]
[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/20241024-170528
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link: https://lore.kernel.org/r/20241024085922.133071-5-tmyu0%40nuvoton.com
patch subject: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
config: loongarch-allmodconfig (https://download.01.org/0day-ci/archive/20241026/202410260755.TBWkI6Jh-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 14.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241026/202410260755.TBWkI6Jh-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/202410260755.TBWkI6Jh-lkp@intel.com/
All errors (new ones prefixed by >>):
>> make[6]: *** No rule to make target 'drivers/net/can/nct6604_canfd.o', needed by 'drivers/net/can/'.
make[6]: Target 'drivers/net/can/' not remade because of errors.
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support
2024-10-24 8:59 ` [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
@ 2024-10-25 23:34 ` Nobuhiro Iwamatsu
2024-10-28 8:42 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Nobuhiro Iwamatsu @ 2024-10-25 23:34 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hello,
2024年10月24日(木) 18:04 Ming Yu <a0282524688@gmail.com>:
>
> 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 | 276 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 288 insertions(+)
> create mode 100644 drivers/rtc/rtc-nct6694.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4d5a5eded3b9..8de90bda8b5e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16445,6 +16445,7 @@ F: drivers/i2c/busses/i2c-nct6694.c
> F: drivers/mfd/nct6694.c
> F: drivers/net/can/nct6694_canfd.c
> F: drivers/pwm/pwm-nct6694.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..240c496d95f7 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..622bb9fbe6f6
> --- /dev/null
> +++ b/drivers/rtc/rtc-nct6694.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 RTC driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/rtc.h>
> +#include <linux/bcd.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
Please sort header files alphabetically.
> +
> +#define DRVNAME "nct6694-rtc"
> +
> +/* Host interface */
> +#define REQUEST_RTC_MOD 0x08
> +
> +/* Message Channel */
> +/* Command 00h */
> +#define REQUEST_RTC_CMD0_LEN 0x07
> +#define REQUEST_RTC_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define RTC_SEC_IDX 0x00
> +#define RTC_MIN_IDX 0x01
> +#define RTC_HOUR_IDX 0x02
> +#define RTC_WEEK_IDX 0x03
> +#define RTC_DAY_IDX 0x04
> +#define RTC_MONTH_IDX 0x05
> +#define RTC_YEAR_IDX 0x06
> +/* Command 01h */
> +#define REQUEST_RTC_CMD1_LEN 0x05
> +#define REQUEST_RTC_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
> +#define RTC_ALRM_EN_IDX 0x03
> +#define RTC_ALRM_PEND_IDX 0x04
> +/* Command 02h */
> +#define REQUEST_RTC_CMD2_LEN 0x02
> +#define REQUEST_RTC_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> +#define RTC_IRQ_EN_IDX 0x00
> +#define RTC_IRQ_PEND_IDX 0x01
> +
> +#define RTC_IRQ_EN (BIT(0) | BIT(5))
RTC_IRQ_INT_EN | RTC_IRQ_GPO_EN ?
> +#define RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */
> +#define RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */
> +#define RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */
> +
> +struct nct6694_rtc_data {
> + struct nct6694 *nct6694;
> + struct rtc_device *rtc;
> + struct work_struct alarm_work;
> +};
> +
> +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> + unsigned char buf[REQUEST_RTC_CMD0_LEN];
> + int ret;
> +
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
> + 0, REQUEST_RTC_CMD0_LEN, buf);
> + if (ret) {
> + pr_err("%s: Failed to get rtc device!\n", __func__);
> + return -EIO;
> + }
> +
> + tm->tm_sec = bcd2bin(buf[RTC_SEC_IDX]); /* tm_sec expect 0 ~ 59 */
> + tm->tm_min = bcd2bin(buf[RTC_MIN_IDX]); /* tm_min expect 0 ~ 59 */
> + tm->tm_hour = bcd2bin(buf[RTC_HOUR_IDX]); /* tm_hour expect 0 ~ 23 */
> + tm->tm_wday = bcd2bin(buf[RTC_WEEK_IDX]) - 1; /* tm_wday expect 0 ~ 6 */
> + tm->tm_mday = bcd2bin(buf[RTC_DAY_IDX]); /* tm_mday expect 1 ~ 31 */
> + tm->tm_mon = bcd2bin(buf[RTC_MONTH_IDX]) - 1; /* tm_month expect 0 ~ 11 */
> + tm->tm_year = bcd2bin(buf[RTC_YEAR_IDX]) + 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);
> + unsigned char buf[REQUEST_RTC_CMD0_LEN];
> + int ret;
> +
> + buf[RTC_SEC_IDX] = bin2bcd(tm->tm_sec);
> + buf[RTC_MIN_IDX] = bin2bcd(tm->tm_min);
> + buf[RTC_HOUR_IDX] = bin2bcd(tm->tm_hour);
> + buf[RTC_WEEK_IDX] = bin2bcd(tm->tm_wday + 1);
> + buf[RTC_DAY_IDX] = bin2bcd(tm->tm_mday);
> + buf[RTC_MONTH_IDX] = bin2bcd(tm->tm_mon + 1);
> + buf[RTC_YEAR_IDX] = bin2bcd(tm->tm_year - 100);
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
> + buf);
> + if (ret) {
> + pr_err("%s: Failed to set rtc device!\n", __func__);
> + return -EIO;
Why do you return -EIO? Please do not overwrite error codes.
> + }
> +
> + return ret;
> +}
> +
> +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> + unsigned char buf[REQUEST_RTC_CMD1_LEN];
> + int ret;
> +
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
> + 0, REQUEST_RTC_CMD1_LEN, buf);
> + if (ret) {
> + pr_err("%s: Failed to get rtc device!\n", __func__);
> + return -EIO;
same as above.
> + }
> +
> + alrm->time.tm_sec = bcd2bin(buf[RTC_SEC_IDX]);
> + alrm->time.tm_min = bcd2bin(buf[RTC_MIN_IDX]);
> + alrm->time.tm_hour = bcd2bin(buf[RTC_HOUR_IDX]);
> +
> + alrm->enabled = buf[RTC_ALRM_EN_IDX];
> + alrm->pending = buf[RTC_ALRM_PEND_IDX];
> +
> + return ret;
> +}
> +
> +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> + unsigned char buf[REQUEST_RTC_CMD1_LEN];
> + int ret;
> +
> + buf[RTC_SEC_IDX] = bin2bcd(alrm->time.tm_sec);
> + buf[RTC_MIN_IDX] = bin2bcd(alrm->time.tm_min);
> + buf[RTC_HOUR_IDX] = bin2bcd(alrm->time.tm_hour);
> + buf[RTC_ALRM_EN_IDX] = alrm->enabled ? RTC_IRQ_EN : 0;
> + buf[RTC_ALRM_PEND_IDX] = 0;
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
> + buf);
> + if (ret) {
> + pr_err("%s: Failed to set rtc device!\n", __func__);
> + return -EIO;
same as above.
> + }
> +
> + return ret;
> +}
> +
> +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
> +{
> + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
> + int ret;
> +
> + if (enabled)
> + buf[RTC_IRQ_EN_IDX] |= RTC_IRQ_EN;
> + else
> + buf[RTC_IRQ_EN_IDX] &= ~RTC_IRQ_EN;
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD2_OFFSET, REQUEST_RTC_CMD2_LEN,
> + buf);
> + if (ret) {
> + pr_err("%s: Failed to set rtc device!\n", __func__);
> + return -EIO;
same as above.
> + }
> +
> + return ret;
> +}
> +
> +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 void nct6694_rtc_alarm(struct work_struct *work)
> +{
> + struct nct6694_rtc_data *data;
> + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
> +
> + data = container_of(work, struct nct6694_rtc_data, alarm_work);
> +
> + pr_info("%s: Got RTC alarm!\n", __func__);
> + buf[RTC_IRQ_EN_IDX] = RTC_IRQ_EN;
> + buf[RTC_IRQ_PEND_IDX] = RTC_IRQ_STS;
> + nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> + REQUEST_RTC_CMD2_OFFSET,
> + REQUEST_RTC_CMD2_LEN, buf);
> +}
> +
> +static void nct6694_rtc_handler(void *private_data)
> +{
> + struct nct6694_rtc_data *data = private_data;
> + struct nct6694 *nct6694 = data->nct6694;
> +
> + queue_work(nct6694->async_workqueue, &data->alarm_work);
> +}
> +
> +static int nct6694_rtc_probe(struct platform_device *pdev)
> +{
> + struct nct6694_rtc_data *data;
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + int ret;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->rtc = devm_rtc_allocate_device(&pdev->dev);
> + if (IS_ERR(data->rtc))
> + return PTR_ERR(data->rtc);
Please use dev_err_probe.
> +
> + 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;
> +
> + INIT_WORK(&data->alarm_work, nct6694_rtc_alarm);
> +
> + ret = nct6694_register_handler(nct6694, RTC_IRQ_STATUS,
> + nct6694_rtc_handler, data);
> + if (ret) {
> + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> + __func__, ERR_PTR(ret));
Please use dev_err_probe.
> + return ret;
> + }
> +
> + device_set_wakeup_capable(&pdev->dev, 1);
> +
> + platform_set_drvdata(pdev, data);
> +
> + /* Register rtc device to RTC framework */
> + ret = devm_rtc_register_device(data->rtc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register rtc device!\n");
> + return ret;
> + }
You can simplify return devm_rtc_register_device.
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_rtc_driver = {
> + .driver = {
> + .name = DRVNAME,
> + },
> + .probe = nct6694_rtc_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_rtc_driver);
> + if (!err) {
> + if (err)
This looks strange. You can simplify return platform_driver_register.
> + platform_driver_unregister(&nct6694_rtc_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_rtc_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
>
>
Best regards,
Nobuhiro
--
Nobuhiro Iwamatsu
iwamatsu at {nigauri.org / debian.org / kernel.org}
GPG ID: 32247FBB40AD1FA6
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
2024-10-24 15:32 ` Guenter Roeck
2024-10-24 16:06 ` Guenter Roeck
@ 2024-10-26 9:19 ` kernel test robot
2 siblings, 0 replies; 80+ messages in thread
From: kernel test robot @ 2024-10-26 9:19 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, jic23, lars, ukleinek, alexandre.belloni
Cc: oe-kbuild-all, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Hi Ming,
kernel test robot noticed the following build warnings:
[auto build test WARNING on lee-mfd/for-mfd-next]
[also build test WARNING on brgl/gpio/for-next andi-shyti/i2c/i2c-host mkl-can-next/testing groeck-staging/hwmon-next jic23-iio/togreg abelloni/rtc-next linus/master lee-mfd/for-mfd-fixes v6.12-rc4 next-20241025]
[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/20241024-170528
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link: https://lore.kernel.org/r/20241024085922.133071-6-tmyu0%40nuvoton.com
patch subject: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
config: arc-randconfig-r132-20241026 (https://download.01.org/0day-ci/archive/20241026/202410261752.lUVTJO2Y-lkp@intel.com/config)
compiler: arceb-elf-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20241026/202410261752.lUVTJO2Y-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/202410261752.lUVTJO2Y-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
>> drivers/watchdog/nct6694_wdt.c:133:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:134:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:166:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:167:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:220:42: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
drivers/watchdog/nct6694_wdt.c:221:45: sparse: sparse: cast to restricted __le32
vim +133 drivers/watchdog/nct6694_wdt.c
115
116 static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
117 unsigned int timeout)
118 {
119 struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
120 struct nct6694 *nct6694 = data->nct6694;
121 unsigned int timeout_fmt, pretimeout_fmt;
122 unsigned char buf[REQUEST_WDT_CMD0_LEN];
123 int ret;
124
125 if (timeout < wdev->pretimeout) {
126 pr_err("%s: 'timeout' must be greater than 'pre timeout'!\n",
127 __func__);
128 return -EINVAL;
129 }
130
131 timeout_fmt = timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
132 pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> 133 set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
134 set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
135
136 ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
137 REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
138 REQUEST_WDT_CMD0_LEN, buf);
139 if (ret) {
140 pr_err("%s: Don't write the setup command in Start stage!\n",
141 __func__);
142 return ret;
143 }
144
145 wdev->timeout = timeout;
146
147 return 0;
148 }
149
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support
2024-10-24 8:59 ` [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support Ming Yu
@ 2024-10-26 14:41 ` Jonathan Cameron
2024-11-05 6:21 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Jonathan Cameron @ 2024-10-26 14:41 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On Thu, 24 Oct 2024 16:59:20 +0800
Ming Yu <a0282524688@gmail.com> wrote:
> This driver supports IIO functionality for NCT6694 MFD device
> based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
Hi Ming Yu,
Welcome to IIO.
Various comments inline. I'm assuming the stuff commented on in other
reviews for other parts of the driver will be fixed up in v2 of this
patch as well so mostly haven't duplicated that.
Jonathan
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 6c4e74420fd2..302511d166db 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1018,6 +1018,16 @@ config NPCM_ADC
> This driver can also be built as a module. If so, the module
> will be called npcm_adc.
>
> +config NCT6694_ADC
Same comment on ordering.
> + tristate "Nuvoton NCT6694 ADC driver"
> + depends on MFD_NCT6694
> + help
Match formatting of other entries. Help text should be indented.
> + If you say yes to this option, support will be included for Nuvoton
> + NCT6694, a USB device to ADC.
> +
> + This driver can also be built as a module. If so, the module
> + will be called nct6694_adc.
> +
> config PAC1921
> tristate "Microchip Technology PAC1921 driver"
> depends on I2C
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 7b91cd98c0e0..db419f77365c 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -92,6 +92,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
> obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
> obj-$(CONFIG_NAU7802) += nau7802.o
> obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
> +obj-$(CONFIG_NCT6694_ADC) += nct6694_adc.o
Alphabetical order. So one line up is appropriate.
> obj-$(CONFIG_PAC1921) += pac1921.o
> obj-$(CONFIG_PAC1934) += pac1934.o
> obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> diff --git a/drivers/iio/adc/nct6694_adc.c b/drivers/iio/adc/nct6694_adc.c
> new file mode 100644
> index 000000000000..de4cddc8addc
> --- /dev/null
> +++ b/drivers/iio/adc/nct6694_adc.c
> @@ -0,0 +1,616 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 IIO driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/iio/iio.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-iio"
Use string directly inline and drop this define. Whilst common this is
not helpful for code readability or for grepping etc.
({ typeof(x) x_ = x; \
> + (x_ < 10) ? x_ : x_ + 6; })
> +
> +/* Message Channel*/
> +/* Command 00h */
> +#define REQUEST_IIO_CMD0_LEN 0x40
> +#define REQUEST_IIO_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define IIO_VIN_EN(x) (0x00 + (x))
> +#define IIO_TMP_EN(x) (0x02 + (x))
> +/* Command 02h */
> +#define REQUEST_IIO_CMD2_LEN 0x90
> +#define REQUEST_IIO_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> +#define IIO_SMI_CTRL_IDX 0x00
> +#define IIO_VIN_LIMIT_IDX(x) (0x10 + ((x) * 2))
> +#define IIO_TMP_LIMIT_IDX(x) (0x30 + ((x) * 2))
> +#define IIO_CMD2_HYST_MASK 0x1F
> +/* Command 03h */
> +#define REQUEST_IIO_CMD3_LEN 0x08
> +#define REQUEST_IIO_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */
> +
Namespace for the part,
NCT6694_REQUEST_CMD3_LEN etc
the IIO bit isn't particularly useful in an IIO driver.
> +struct nct6694_iio_data {
> + struct nct6694 *nct6694;
> +
> + /* Make sure read & write commands are consecutive */
> + struct mutex iio_lock;
Not sure the iio part is useful here. Name it after what is being
protected or just call it lock
> +};
> +
> +static const struct iio_event_spec nct6694_volt_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_separate = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_ENABLE),
> + },
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_separate = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_ENABLE),
> + }
> +};
> +
> +static const struct iio_event_spec nct6694_temp_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_separate = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_ENABLE),
> + }
> +};
> +
> +#define NCT6694_VOLTAGE_CHANNEL(num, addr) { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .channel = num, \
> + .address = addr, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
ADC channels do not normally ENABLE. Can you not enable on demand
when the channel is read?
> + BIT(IIO_CHAN_INFO_PROCESSED), \
It is very rare to have processed channels for an ADC.
Provide RAW and SCALE instead as the relationship seems to be linear
and that pushes the maths to the consumer (who may not care).
The main reason for this design choice in IIO is that the _RAW data
tends to be easier to back if you add buffered access in future.
> + .event_spec = nct6694_volt_events, \
> + .num_event_specs = ARRAY_SIZE(nct6694_volt_events), \
> +}
> +
> +#define NCT6694_TEMPERATURE_CHANNEL(num, addr) { \
> + .type = IIO_TEMP, \
> + .indexed = 1, \
> + .channel = num, \
> + .address = addr, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
As with ADCs, unusual to see ENABLE for a temperature channel.
Can we not do this on demand?
> + BIT(IIO_CHAN_INFO_PROCESSED) | \
As above, provide _RAW and _SCALE (and maybe _OFFSET)
> + BIT(IIO_CHAN_INFO_HYSTERESIS), \
I ask about this below. Not sure what it is.
> + .event_spec = nct6694_temp_events, \
> + .num_event_specs = ARRAY_SIZE(nct6694_temp_events), \
> +}
> +
> +static const struct iio_chan_spec nct6694_iio_channels[] = {
> + NCT6694_VOLTAGE_CHANNEL(0, 0x0), /* VIN0 */
Set datasheet_name. That is both useful when binding to these channels
and will make the comments here unnecessary (as that name will be
in the macro parameters).
> + NCT6694_VOLTAGE_CHANNEL(1, 0x1), /* VIN1 */
> + NCT6694_VOLTAGE_CHANNEL(2, 0x2), /* VIN2 */
> + NCT6694_VOLTAGE_CHANNEL(3, 0x3), /* VIN3 */
> + NCT6694_VOLTAGE_CHANNEL(4, 0x4), /* VIN5 */
> + NCT6694_VOLTAGE_CHANNEL(5, 0x5), /* VIN6 */
> + NCT6694_VOLTAGE_CHANNEL(6, 0x6), /* VIN7 */
> + NCT6694_VOLTAGE_CHANNEL(7, 0x7), /* VIN14 */
> + NCT6694_VOLTAGE_CHANNEL(8, 0x8), /* VIN15 */
> + NCT6694_VOLTAGE_CHANNEL(9, 0x9), /* VIN16 */
> + NCT6694_VOLTAGE_CHANNEL(10, 0xA), /* VBAT */
> + NCT6694_VOLTAGE_CHANNEL(11, 0xB), /* VSB */
> + NCT6694_VOLTAGE_CHANNEL(12, 0xC), /* AVSB */
> + NCT6694_VOLTAGE_CHANNEL(13, 0xD), /* VCC */
> + NCT6694_VOLTAGE_CHANNEL(14, 0xE), /* VHIF */
> + NCT6694_VOLTAGE_CHANNEL(15, 0xF), /* VTT */
> +
> + NCT6694_TEMPERATURE_CHANNEL(0, 0x10), /* THR1 */
> + NCT6694_TEMPERATURE_CHANNEL(1, 0x12), /* THR2 */
> + NCT6694_TEMPERATURE_CHANNEL(2, 0x14), /* THR14 */
> + NCT6694_TEMPERATURE_CHANNEL(3, 0x16), /* THR15 */
> + NCT6694_TEMPERATURE_CHANNEL(4, 0x18), /* THR16 */
> + NCT6694_TEMPERATURE_CHANNEL(5, 0x1A), /* TDP0 */
> + NCT6694_TEMPERATURE_CHANNEL(6, 0x1C), /* TDP1 */
> + NCT6694_TEMPERATURE_CHANNEL(7, 0x1E), /* TDP2 */
> + NCT6694_TEMPERATURE_CHANNEL(8, 0x20), /* TDP3 */
> + NCT6694_TEMPERATURE_CHANNEL(9, 0x22), /* TDP4 */
> +
> + NCT6694_TEMPERATURE_CHANNEL(10, 0x30), /* DTIN0 */
> + NCT6694_TEMPERATURE_CHANNEL(11, 0x32), /* DTIN1 */
> + NCT6694_TEMPERATURE_CHANNEL(12, 0x34), /* DTIN2 */
> + NCT6694_TEMPERATURE_CHANNEL(13, 0x36), /* DTIN3 */
> + NCT6694_TEMPERATURE_CHANNEL(14, 0x38), /* DTIN4 */
> + NCT6694_TEMPERATURE_CHANNEL(15, 0x3A), /* DTIN5 */
> + NCT6694_TEMPERATURE_CHANNEL(16, 0x3C), /* DTIN6 */
> + NCT6694_TEMPERATURE_CHANNEL(17, 0x3E), /* DTIN7 */
> + NCT6694_TEMPERATURE_CHANNEL(18, 0x40), /* DTIN8 */
> + NCT6694_TEMPERATURE_CHANNEL(19, 0x42), /* DTIN9 */
> + NCT6694_TEMPERATURE_CHANNEL(20, 0x44), /* DTIN10 */
> + NCT6694_TEMPERATURE_CHANNEL(21, 0x46), /* DTIN11 */
> + NCT6694_TEMPERATURE_CHANNEL(22, 0x48), /* DTIN12 */
> + NCT6694_TEMPERATURE_CHANNEL(23, 0x4A), /* DTIN13 */
> + NCT6694_TEMPERATURE_CHANNEL(24, 0x4C), /* DTIN14 */
> + NCT6694_TEMPERATURE_CHANNEL(25, 0x4E), /* DTIN15 */
> +};
> +
> +static int nct6694_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct nct6694_iio_data *data = iio_priv(indio_dev);
> + unsigned char buf[2], tmp_hyst, enable_idx;
> + int ret;
> +
> + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> + return -EOPNOTSUPP;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_ENABLE:
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + enable_idx = IIO_VIN_EN(chan->channel / 8);
> + break;
> +
> + case IIO_TEMP:
> + enable_idx = IIO_TMP_EN(chan->channel / 8);
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD0_OFFSET,
> + REQUEST_IIO_CMD0_LEN, enable_idx,
> + 1, buf);
> + if (ret)
> + return -EINVAL;
> +
> + *val = buf[0] & BIT(chan->channel % 8) ? 1 : 0;
> +
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_PROCESSED:
Commented on elsewhere - provide _RAW access as processed is only needed
when there is a nonlinear conversion involved that we cannot easily
represent to userspace.
> + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD,
> + chan->address, 2, 0, 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + switch (chan->type) {
> + case IIO_VOLTAGE: /* in micro Voltage */
> + *val = buf[0] * 16;
> +
> + return IIO_VAL_INT;
> +
> + case IIO_TEMP: /* in milli degrees Celsius */
> + *val = (signed char)buf[0] * 1000;
> + *val += buf[1] & 0x80 ? 500 : 0;
> + *val += buf[1] & 0x40 ? 250 : 0;
> + *val += buf[1] & 0x20 ? 125 : 0;
As above provide attrs to allow (_RAW + _OFFSET) * _SCALE for
userspace.
I'm not sure what this maths is doing but looks like a really
odd way to encode *val += 125 * FIELD_GET(buf[1], GENMASK(7, 5))
or something like that.
> +
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + case IIO_CHAN_INFO_HYSTERESIS:
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN,
> + IIO_TMP_LIMIT_IDX(chan->channel),
> + 2, buf);
> + if (ret)
> + return -EINVAL;
> +
> + switch (chan->type) {
> + case IIO_TEMP: /* in milli degrees Celsius */
Check channel type earlier as no point in reading if it's not temperature.
As mentioned elsewhere I'm a little confused as to what this is.
> + tmp_hyst = buf[0] >> 5;
> + *val = (buf[1] - tmp_hyst) * 1000;
> +
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int nct6694_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct nct6694_iio_data *data = iio_priv(indio_dev);
> + unsigned char enable_buf[REQUEST_IIO_CMD0_LEN] = {0};
> + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
> + unsigned char delta_hyst;
> + int ret;
> +
> + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> + return -EOPNOTSUPP;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_ENABLE:
This is very unusual for an ADC channel.
You should not have explicit enable /disable but rather just do it when
needed to deliver requested data.
> + mutex_lock(&data->iio_lock);
Add some scope with { } and use guard(mutex)(...) within it.
Then you can just return on error rather than goto.
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD0_OFFSET,
> + REQUEST_IIO_CMD0_LEN, 0,
> + REQUEST_IIO_CMD0_LEN,
> + enable_buf);
> + if (ret)
> + goto err;
> +
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + if (val) {
> + enable_buf[IIO_VIN_EN(chan->channel / 8)] |=
> + BIT(chan->channel % 8);
Indent this line by one more tab. Sam ein other similar cases.
> + } else {
> + enable_buf[IIO_VIN_EN(chan->channel / 8)] &=
> + ~BIT(chan->channel % 8);
> + }
> +
> + break;
> +
> + case IIO_TEMP:
> + if (val) {
> + enable_buf[IIO_TMP_EN(chan->channel / 8)] |=
> + BIT(chan->channel % 8);
> + } else {
> + enable_buf[IIO_TMP_EN(chan->channel / 8)] &=
> + ~BIT(chan->channel % 8);
> + }
> +
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD0_OFFSET,
> + REQUEST_IIO_CMD0_LEN, enable_buf);
> + if (ret)
> + goto err;
> +
> + break;
> +
> + case IIO_CHAN_INFO_HYSTERESIS:
Hysteresis for an ADC is normally event related as it doesn't have obvious meaning otherwise.
Perhaps you can give more detail on what this is, and why it isn't IIO_EV_INFO_HYSTERESIS and
in the event description.
> + switch (chan->type) {
> + case IIO_TEMP:
> + mutex_lock(&data->iio_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, 0,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + delta_hyst = buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] - (u8)val;
> + if (delta_hyst > 7) {
> + pr_err("%s: The Hysteresis value must be less than 7!\n",
> + __func__);
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + buf[IIO_TMP_LIMIT_IDX(chan->channel)] &= IIO_CMD2_HYST_MASK;
> + buf[IIO_TMP_LIMIT_IDX(chan->channel)] |= (delta_hyst << 5);
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> +
> +err:
> + mutex_unlock(&data->iio_lock);
> + return ret;
> +}
> +
> +static int nct6694_write_event_value(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info,
> + int val, int val2)
> +{
> + struct nct6694_iio_data *data = iio_priv(indio_dev);
> + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
> + int ret;
> +
> + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
> + return -EOPNOTSUPP;
> +
> + mutex_lock(&data->iio_lock);
guard(mutex)(&data->iio_lock);
and direct returns in error paths.
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, 0,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
With guard() as above, can return here instead of goto.
> + goto err;
> +
> + switch (dir) {
> + case IIO_EV_DIR_RISING:
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + buf[IIO_VIN_LIMIT_IDX(chan->channel)] = (u8)val;
Check range of val before using it. That goes directly from userspace.
Also check val2 == 0 as user might have provided a decimal form.
> + break;
> +
> + case IIO_TEMP:
> + buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] = (s8)val;
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> + break;
> +
> + case IIO_EV_DIR_FALLING:
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + buf[IIO_VIN_LIMIT_IDX(chan->channel) + 1] = (u8)val;
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> + break;
> +
> + default:
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> +err:
> + mutex_unlock(&data->iio_lock);
With guard above can drop the explicit lock as it is then managed with the scope.
> + return ret;
> +}
> +
> +static const struct iio_info nct6694_iio_info = {
> + .read_raw = nct6694_read_raw,
> + .write_raw = nct6694_write_raw,
> + .read_event_config = nct6694_read_event_config,
No ability to disable the event? That is unusual.
> + .read_event_value = nct6694_read_event_value,
> + .write_event_value = nct6694_write_event_value,
> +};
> +
> +static int nct6694_iio_init(struct nct6694_iio_data *data)
> +{
> + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0};
{};
Has the same effect and makes it clear you just want to clear everything.
> + int ret;
> +
> + /* Set VIN & TMP Real Time alarm mode */
> + mutex_lock(&data->iio_lock);
guard(mutex)(&data->iio_lock);
> + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, 0,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
With guard() in use, can return directly instead of the goto.
> + goto err;
> +
> + buf[IIO_SMI_CTRL_IDX] = 0x02;
> + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD,
> + REQUEST_IIO_CMD2_OFFSET,
> + REQUEST_IIO_CMD2_LEN, buf);
> + if (ret)
> + goto err;
> +
> +err:
> + mutex_unlock(&data->iio_lock);
> + return ret;
> +}
> +
> +static int nct6694_iio_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev;
> + struct nct6694_iio_data *data;
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + data = iio_priv(indio_dev);
> + data->nct6694 = nct6694;
> + mutex_init(&data->iio_lock);
> + platform_set_drvdata(pdev, data);
Not used, so don't set it.
> +
> + ret = nct6694_iio_init(data);
> + if (ret)
> + return -EIO;
> +
> + indio_dev->name = pdev->name;
Name should be the part number. Just encode the string here as "nct6694"
> + indio_dev->channels = nct6694_iio_channels;
> + indio_dev->num_channels = ARRAY_SIZE(nct6694_iio_channels);
> + indio_dev->info = &nct6694_iio_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + /* Register iio device to IIO framework */
> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register iio device!\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_iio_driver = {
> + .driver = {
> + .name = DRVNAME,
Put the string inline.
> + },
> + .probe = nct6694_iio_probe,
> +};
> +
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_iio_driver);
> + if (!err) {
> + if (err)
> + platform_driver_unregister(&nct6694_iio_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
> +
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_iio_driver);
> +}
> +module_exit(nct6694_exit);
I'll not comment on this handling as it is will covered in other
parts of the series.
> +
> +MODULE_DESCRIPTION("USB-IIO driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-25 15:44 ` Guenter Roeck
@ 2024-10-26 14:50 ` Guenter Roeck
2024-10-28 7:58 ` Ming Yu
2024-10-28 7:42 ` Ming Yu
1 sibling, 1 reply; 80+ messages in thread
From: Guenter Roeck @ 2024-10-26 14:50 UTC (permalink / raw)
To: Ming Yu
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, jic23, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
On 10/25/24 08:44, Guenter Roeck wrote:
> On 10/25/24 08:22, Ming Yu wrote:
> [ ... ]
>
>>>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
>>>>> + long val)
>>>>> +{
>>>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
>>>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
>>>> [Kalesh] Please try to maintain RCT order for variable declaration
>>>
>>> Ok, but that is already the case here ?
>>
>> [Ming] Is there anything that needs to be changed?
>>
>
> I don't think so, If two lines have the same length, the order is up
> to the developer to decide.
>
> Question though is if the buffer needs to be initialized. You should drop
> the initialization if it is not necessary. In that case the second line
> would be shorter anyway, and the order question would not arise.
>
Actually, I just noticed that you also submitted an IIO driver which
reports the same data again. If a chip has an IIO driver, there should
be no HWMON driver since the IIO -> HWMON bridge can then be used if
necessary. So please drop this driver.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
` (2 preceding siblings ...)
2024-10-24 15:20 ` Marc Kleine-Budde
@ 2024-10-26 14:58 ` Christophe JAILLET
2024-10-28 7:37 ` Ming Yu
3 siblings, 1 reply; 80+ messages in thread
From: Christophe JAILLET @ 2024-10-26 14:58 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Le 24/10/2024 à 10:59, Ming Yu a écrit :
> 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 register
> a handler function that will be called when the USB device receives
> its interrupt pipe.
>
> Signed-off-by: Ming Yu <tmyu0-KrzQf0k3Iz9BDgjK7y7TUQ@public.gmane.org>
> ---
...
> +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;
> + int pipe, maxp, bulk_pipe;
> + int ret = EINVAL;
Nitpick: no need to init
> +
> + interface = iface->cur_altsetting;
> + /* Binding interface class : 0xFF */
> + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> + interface->desc.bInterfaceSubClass != 0x00 ||
> + interface->desc.bInterfaceProtocol != 0x00)
> + return -ENODEV;
> +
> + int_endpoint = &interface->endpoint[0].desc;
> + if (!usb_endpoint_is_int_in(int_endpoint))
> + return -ENODEV;
> +
> + nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL);
> + if (!nct6694)
> + return -ENOMEM;
> +
> + pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT);
> + maxp = usb_maxpacket(udev, pipe);
> +
> + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->cmd_buffer)
> + return -ENOMEM;
> + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->rx_buffer)
> + return -ENOMEM;
> + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> + sizeof(unsigned char), GFP_KERNEL);
> + if (!nct6694->tx_buffer)
> + return -ENOMEM;
> + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> + return -ENOMEM;
> + }
> +
> + /* Bulk pipe maximum packet for each transaction */
> + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> +
> + mutex_init(&nct6694->access_lock);
> + nct6694->udev = udev;
> + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> +
> + INIT_LIST_HEAD(&nct6694->handler_list);
> + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> + usb_set_intfdata(iface, nct6694);
> +
> + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> + ARRAY_SIZE(nct6694_dev));
> + if (ret) {
> + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> + goto err_mfd;
> + }
> +
> + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
Missing error handling.
> +
> + dev_info(&udev->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 ret;
> +}
> +
> +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);
> + flush_workqueue(nct6694->async_workqueue);
> + destroy_workqueue(nct6694->async_workqueue);
> + usb_set_intfdata(iface, NULL);
> + usb_kill_urb(nct6694->int_in_urb);
> + usb_free_urb(nct6694->int_in_urb);
> +}
> +
> +static const struct usb_device_id nct6694_ids[] = {
> + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
Nitpick: space missing before the ending }
> + {},
Nitpick: usually, no comma is added after a {} terminator.
> +};
> +MODULE_DEVICE_TABLE(usb, nct6694_ids);
...
CJ
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-25 12:23 ` Marc Kleine-Budde
@ 2024-10-28 7:33 ` Ming Yu
2024-10-28 7:52 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-28 7:33 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Marc,
Thank you for your comments,
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午8:24寫道:
>
> On 25.10.2024 19:03:55, Ming Yu wrote:
> > Oh! I'm sorry about that I confused the packet size.
> > The NCT6694 bulk maximum packet size is 256 bytes,
> > and USB High speed bulk maximum packet size is 512 bytes.
> >
> > Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午6:08寫道:
> > >
> > > On 25.10.2024 16:08:10, Ming Yu wrote:
> > > > > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > > > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> > > > >
> > > > > why not make buf a void *?
> > > >
> > > > [Ming] I'll change the type in the next patch.
> > > >
> > > > >
> > > > > > +{
> > > > > > + struct usb_device *udev = nct6694->udev;
> > > > > > + unsigned char err_status;
> > > > > > + int len, packet_len, tx_len, rx_len;
> > > > > > + int i, ret;
> > > > > > +
> > > > > > + mutex_lock(&nct6694->access_lock);
> > > > > > +
> > > > > > + /* Send command packet to USB device */
> > > > > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > > > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > > > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > > > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > > > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > > > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > > > > +
> > > > > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > > > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > > > > + nct6694->timeout);
> > > > > > + if (ret)
> > > > > > + goto err;
> > > > > > +
> > > > > > + /* Receive response packet from USB device */
> > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > > > > + nct6694->timeout);
> > > > > > + if (ret)
> > > > > > + goto err;
> > > > > > +
> > > > > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > > > > +
> > > > > > + /*
> > > > > > + * Segmented reception of messages that exceed the size of USB bulk
> > > > > > + * pipe packets.
> > > > > > + */
> > > > >
> > > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > > >
> > > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > > The core will divide packet 256 bytes for high speed USB device, but
> > > > it is exceeds
> > > > the hardware limitation, so I am dividing it manually.
> > >
> > > You say the endpoint descriptor is correctly reporting it's max packet
> > > size of 128, but the Linux USB will send packets of 256 bytes?
> >
> > [Ming] The endpoint descriptor is correctly reporting it's max packet
> > size of 256, but the Linux USB may send more than 256 (max is 512)
> > https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
>
> AFAIK according to the USB-2.0 spec the maximum packet size for
> high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
> your device is a non-compliant USB device?
We will reduce the endpoint size of other interfaces to ensure that MFD device
meets the USB2.0 spec. In other words, I will remove the code for manual
unpacking in the next patch.
>
> > > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > > + if (len > nct6694->maxp)
> > > > > > + packet_len = nct6694->maxp;
> > > > > > + else
> > > > > > + packet_len = len;
> > > > > > +
> > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > > + if (ret)
> > > > > > + goto err;
> > > > > > + }
> > > > > > +
> > > > > > + for (i = 0; i < rd_len; i++)
> > > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > > >
> > > > > memcpy()?
> > > > >
> > > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > > of the data doesn't make it faster.
> > > > >
> > > > > On the other hand, receiving directly into the target buffer means the
> > > > > target buffer must not live on the stack.
> > > >
> > > > [Ming] Okay! I'll change it to memcpy().
> > >
> > > fine!
> > >
> > > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > > by the MFD device. does it need to be changed?
> > >
> > > My question is: Why do you first receive into the nct6694->rx_buffer and
> > > then memcpy() to the buffer provided by the caller, why don't you
> > > directly receive into the memory provided by the caller?
> >
> > [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> > using the MFD'd dynamically allocated buffer to submit URBs will better
> > manage USB-related operations
>
> The non-compliant max packet size limitation is unrelated to the
> question which RX or TX buffer to use.
I think these two USB functions can be easily called using the buffer
dynamically
allocated by the MFD. However, if they transfer data directly to the
target buffer,
they must ensure that it is not located on the stack.
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-26 14:58 ` Christophe JAILLET
@ 2024-10-28 7:37 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-28 7:37 UTC (permalink / raw)
To: Christophe JAILLET
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hi Christophe
Thank you for your comments,
I will update the code in the next patch.
Best regards,
Ming
Christophe JAILLET <christophe.jaillet@wanadoo.fr> 於 2024年10月26日 週六 下午10:58寫道:
>
> Le 24/10/2024 à 10:59, Ming Yu a écrit :
> > 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 register
> > a handler function that will be called when the USB device receives
> > its interrupt pipe.
> >
> > Signed-off-by: Ming Yu <tmyu0-KrzQf0k3Iz9BDgjK7y7TUQ@public.gmane.org>
> > ---
>
> ...
>
> > +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;
> > + int pipe, maxp, bulk_pipe;
> > + int ret = EINVAL;
>
> Nitpick: no need to init
>
> > +
> > + interface = iface->cur_altsetting;
> > + /* Binding interface class : 0xFF */
> > + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
> > + interface->desc.bInterfaceSubClass != 0x00 ||
> > + interface->desc.bInterfaceProtocol != 0x00)
> > + return -ENODEV;
> > +
> > + int_endpoint = &interface->endpoint[0].desc;
> > + if (!usb_endpoint_is_int_in(int_endpoint))
> > + return -ENODEV;
> > +
> > + nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL);
> > + if (!nct6694)
> > + return -ENOMEM;
> > +
> > + pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT);
> > + maxp = usb_maxpacket(udev, pipe);
> > +
> > + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->cmd_buffer)
> > + return -ENOMEM;
> > + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->rx_buffer)
> > + return -ENOMEM;
> > + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ,
> > + sizeof(unsigned char), GFP_KERNEL);
> > + if (!nct6694->tx_buffer)
> > + return -ENOMEM;
> > + nct6694->int_buffer = devm_kcalloc(dev, 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) {
> > + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n");
> > + return -ENOMEM;
> > + }
> > +
> > + /* Bulk pipe maximum packet for each transaction */
> > + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT);
> > + nct6694->maxp = usb_maxpacket(udev, bulk_pipe);
> > +
> > + mutex_init(&nct6694->access_lock);
> > + nct6694->udev = udev;
> > + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */
> > +
> > + INIT_LIST_HEAD(&nct6694->handler_list);
> > + spin_lock_init(&nct6694->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(&udev->dev, nct6694);
> > + usb_set_intfdata(iface, nct6694);
> > +
> > + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev,
> > + ARRAY_SIZE(nct6694_dev));
> > + if (ret) {
> > + dev_err(&udev->dev, "Failed to add mfd's child device\n");
> > + goto err_mfd;
> > + }
> > +
> > + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0);
>
> Missing error handling.
>
> > +
> > + dev_info(&udev->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 ret;
> > +}
> > +
> > +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);
> > + flush_workqueue(nct6694->async_workqueue);
> > + destroy_workqueue(nct6694->async_workqueue);
> > + usb_set_intfdata(iface, NULL);
> > + usb_kill_urb(nct6694->int_in_urb);
> > + usb_free_urb(nct6694->int_in_urb);
> > +}
> > +
> > +static const struct usb_device_id nct6694_ids[] = {
> > + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)},
>
> Nitpick: space missing before the ending }
>
> > + {},
>
> Nitpick: usually, no comma is added after a {} terminator.
>
> > +};
> > +MODULE_DEVICE_TABLE(usb, nct6694_ids);
>
> ...
>
> CJ
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-25 15:44 ` Guenter Roeck
2024-10-26 14:50 ` Guenter Roeck
@ 2024-10-28 7:42 ` Ming Yu
1 sibling, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-28 7:42 UTC (permalink / raw)
To: Guenter Roeck
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, jic23, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Understood,
Thank you.
Best regards,
Ming
Guenter Roeck <linux@roeck-us.net> 於 2024年10月25日 週五 下午11:44寫道:
>
> On 10/25/24 08:22, Ming Yu wrote:
> [ ... ]
>
> >>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> >>>> + long val)
> >>>> +{
> >>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> >>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> >>> [Kalesh] Please try to maintain RCT order for variable declaration
> >>
> >> Ok, but that is already the case here ?
> >
> > [Ming] Is there anything that needs to be changed?
> >
>
> I don't think so, If two lines have the same length, the order is up
> to the developer to decide.
>
> Question though is if the buffer needs to be initialized. You should drop
> the initialization if it is not necessary. In that case the second line
> would be shorter anyway, and the order question would not arise.
>
> Thanks,
> Guenter
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-28 7:33 ` Ming Yu
@ 2024-10-28 7:52 ` Marc Kleine-Budde
2024-10-28 8:31 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-28 7:52 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 6817 bytes --]
On 28.10.2024 15:33:08, Ming Yu wrote:
> Dear Marc,
>
> Thank you for your comments,
>
> Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午8:24寫道:
> >
> > On 25.10.2024 19:03:55, Ming Yu wrote:
> > > Oh! I'm sorry about that I confused the packet size.
> > > The NCT6694 bulk maximum packet size is 256 bytes,
> > > and USB High speed bulk maximum packet size is 512 bytes.
> > >
> > > Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午6:08寫道:
> > > >
> > > > On 25.10.2024 16:08:10, Ming Yu wrote:
> > > > > > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > > > > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> > > > > >
> > > > > > why not make buf a void *?
> > > > >
> > > > > [Ming] I'll change the type in the next patch.
> > > > >
> > > > > >
> > > > > > > +{
> > > > > > > + struct usb_device *udev = nct6694->udev;
> > > > > > > + unsigned char err_status;
> > > > > > > + int len, packet_len, tx_len, rx_len;
> > > > > > > + int i, ret;
> > > > > > > +
> > > > > > > + mutex_lock(&nct6694->access_lock);
> > > > > > > +
> > > > > > > + /* Send command packet to USB device */
> > > > > > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > > > > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > > > > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > > > > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > > > > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > > > > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > > > > > +
> > > > > > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > > > > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > > > > > + nct6694->timeout);
> > > > > > > + if (ret)
> > > > > > > + goto err;
> > > > > > > +
> > > > > > > + /* Receive response packet from USB device */
> > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > > > > > + nct6694->timeout);
> > > > > > > + if (ret)
> > > > > > > + goto err;
> > > > > > > +
> > > > > > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > > > > > +
> > > > > > > + /*
> > > > > > > + * Segmented reception of messages that exceed the size of USB bulk
> > > > > > > + * pipe packets.
> > > > > > > + */
> > > > > >
> > > > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > > > >
> > > > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > > > The core will divide packet 256 bytes for high speed USB device, but
> > > > > it is exceeds
> > > > > the hardware limitation, so I am dividing it manually.
> > > >
> > > > You say the endpoint descriptor is correctly reporting it's max packet
> > > > size of 128, but the Linux USB will send packets of 256 bytes?
> > >
> > > [Ming] The endpoint descriptor is correctly reporting it's max packet
> > > size of 256, but the Linux USB may send more than 256 (max is 512)
> > > https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
> >
> > AFAIK according to the USB-2.0 spec the maximum packet size for
> > high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
> > your device is a non-compliant USB device?
>
> We will reduce the endpoint size of other interfaces to ensure that MFD device
> meets the USB2.0 spec. In other words, I will remove the code for manual
> unpacking in the next patch.
I was not talking about the driver, but your USB device. According to
the USB2.0 spec, the packet size is fixed to 512 for high-speed bulk
transfers. So your device must be able to handle 512 byte transfers or
it's a non-compliant USB device.
> > > > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > > > + if (len > nct6694->maxp)
> > > > > > > + packet_len = nct6694->maxp;
> > > > > > > + else
> > > > > > > + packet_len = len;
> > > > > > > +
> > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > > > + if (ret)
> > > > > > > + goto err;
> > > > > > > + }
> > > > > > > +
> > > > > > > + for (i = 0; i < rd_len; i++)
> > > > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > > > >
> > > > > > memcpy()?
> > > > > >
> > > > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > > > of the data doesn't make it faster.
> > > > > >
> > > > > > On the other hand, receiving directly into the target buffer means the
> > > > > > target buffer must not live on the stack.
> > > > >
> > > > > [Ming] Okay! I'll change it to memcpy().
> > > >
> > > > fine!
> > > >
> > > > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > > > by the MFD device. does it need to be changed?
> > > >
> > > > My question is: Why do you first receive into the nct6694->rx_buffer and
> > > > then memcpy() to the buffer provided by the caller, why don't you
> > > > directly receive into the memory provided by the caller?
> > >
> > > [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> > > using the MFD'd dynamically allocated buffer to submit URBs will better
> > > manage USB-related operations
> >
> > The non-compliant max packet size limitation is unrelated to the
> > question which RX or TX buffer to use.
>
> I think these two USB functions can be easily called using the buffer
> dynamically
> allocated by the MFD. However, if they transfer data directly to the
> target buffer,
> they must ensure that it is not located on the stack.
You have a high coupling between the MFD driver and the individual
drivers anyways, so why not directly use the dynamically allocated
buffer provided by the caller and get rid of the memcpy()?
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-26 14:50 ` Guenter Roeck
@ 2024-10-28 7:58 ` Ming Yu
2024-10-28 18:54 ` Jonathan Cameron
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-28 7:58 UTC (permalink / raw)
To: Guenter Roeck
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, jic23, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Dear Guenter,
The original plan was to use the IIO driver to access the temperature
and voltage sensors, and the HWMON driver to access the tachometers.
However, since the device is a hot-plug USB device, as far as I know,
IIO-HWMON is not applicable. I will merge the IIO driver part into the
HWMON driver in the next patch.
In other words, the driver will be used to access TIN, VIN and FIN.
Best regards
Ming
Guenter Roeck <linux@roeck-us.net> 於 2024年10月26日 週六 下午10:50寫道:
>
> On 10/25/24 08:44, Guenter Roeck wrote:
> > On 10/25/24 08:22, Ming Yu wrote:
> > [ ... ]
> >
> >>>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> >>>>> + long val)
> >>>>> +{
> >>>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> >>>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> >>>> [Kalesh] Please try to maintain RCT order for variable declaration
> >>>
> >>> Ok, but that is already the case here ?
> >>
> >> [Ming] Is there anything that needs to be changed?
> >>
> >
> > I don't think so, If two lines have the same length, the order is up
> > to the developer to decide.
> >
> > Question though is if the buffer needs to be initialized. You should drop
> > the initialization if it is not necessary. In that case the second line
> > would be shorter anyway, and the order question would not arise.
> >
>
> Actually, I just noticed that you also submitted an IIO driver which
> reports the same data again. If a chip has an IIO driver, there should
> be no HWMON driver since the IIO -> HWMON bridge can then be used if
> necessary. So please drop this driver.
>
> Thanks,
> Guenter
>
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-28 7:52 ` Marc Kleine-Budde
@ 2024-10-28 8:31 ` Ming Yu
2024-10-28 14:06 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-28 8:31 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月28日 週一 下午3:52寫道:
>
> On 28.10.2024 15:33:08, Ming Yu wrote:
> > Dear Marc,
> >
> > Thank you for your comments,
> >
> > Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午8:24寫道:
> > >
> > > On 25.10.2024 19:03:55, Ming Yu wrote:
> > > > Oh! I'm sorry about that I confused the packet size.
> > > > The NCT6694 bulk maximum packet size is 256 bytes,
> > > > and USB High speed bulk maximum packet size is 512 bytes.
> > > >
> > > > Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午6:08寫道:
> > > > >
> > > > > On 25.10.2024 16:08:10, Ming Yu wrote:
> > > > > > > > +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length,
> > > > > > > > + u8 rd_idx, u8 rd_len, unsigned char *buf)
> > > > > > >
> > > > > > > why not make buf a void *?
> > > > > >
> > > > > > [Ming] I'll change the type in the next patch.
> > > > > >
> > > > > > >
> > > > > > > > +{
> > > > > > > > + struct usb_device *udev = nct6694->udev;
> > > > > > > > + unsigned char err_status;
> > > > > > > > + int len, packet_len, tx_len, rx_len;
> > > > > > > > + int i, ret;
> > > > > > > > +
> > > > > > > > + mutex_lock(&nct6694->access_lock);
> > > > > > > > +
> > > > > > > > + /* Send command packet to USB device */
> > > > > > > > + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod;
> > > > > > > > + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF;
> > > > > > > > + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF;
> > > > > > > > + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET;
> > > > > > > > + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF;
> > > > > > > > + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF;
> > > > > > > > +
> > > > > > > > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT),
> > > > > > > > + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len,
> > > > > > > > + nct6694->timeout);
> > > > > > > > + if (ret)
> > > > > > > > + goto err;
> > > > > > > > +
> > > > > > > > + /* Receive response packet from USB device */
> > > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > > + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len,
> > > > > > > > + nct6694->timeout);
> > > > > > > > + if (ret)
> > > > > > > > + goto err;
> > > > > > > > +
> > > > > > > > + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX];
> > > > > > > > +
> > > > > > > > + /*
> > > > > > > > + * Segmented reception of messages that exceed the size of USB bulk
> > > > > > > > + * pipe packets.
> > > > > > > > + */
> > > > > > >
> > > > > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > > > > >
> > > > > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > > > > The core will divide packet 256 bytes for high speed USB device, but
> > > > > > it is exceeds
> > > > > > the hardware limitation, so I am dividing it manually.
> > > > >
> > > > > You say the endpoint descriptor is correctly reporting it's max packet
> > > > > size of 128, but the Linux USB will send packets of 256 bytes?
> > > >
> > > > [Ming] The endpoint descriptor is correctly reporting it's max packet
> > > > size of 256, but the Linux USB may send more than 256 (max is 512)
> > > > https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
> > >
> > > AFAIK according to the USB-2.0 spec the maximum packet size for
> > > high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
> > > your device is a non-compliant USB device?
> >
> > We will reduce the endpoint size of other interfaces to ensure that MFD device
> > meets the USB2.0 spec. In other words, I will remove the code for manual
> > unpacking in the next patch.
>
> I was not talking about the driver, but your USB device. According to
> the USB2.0 spec, the packet size is fixed to 512 for high-speed bulk
> transfers. So your device must be able to handle 512 byte transfers or
> it's a non-compliant USB device.
I understand. Therefore, the USB device's firmware will be modified to support
bulk pipe size of 512 bytes to comply with the USB 2.0 spec.
>
> > > > > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > > > > + if (len > nct6694->maxp)
> > > > > > > > + packet_len = nct6694->maxp;
> > > > > > > > + else
> > > > > > > > + packet_len = len;
> > > > > > > > +
> > > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > > > > + if (ret)
> > > > > > > > + goto err;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + for (i = 0; i < rd_len; i++)
> > > > > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > > > > >
> > > > > > > memcpy()?
> > > > > > >
> > > > > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > > > > of the data doesn't make it faster.
> > > > > > >
> > > > > > > On the other hand, receiving directly into the target buffer means the
> > > > > > > target buffer must not live on the stack.
> > > > > >
> > > > > > [Ming] Okay! I'll change it to memcpy().
> > > > >
> > > > > fine!
> > > > >
> > > > > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > > > > by the MFD device. does it need to be changed?
> > > > >
> > > > > My question is: Why do you first receive into the nct6694->rx_buffer and
> > > > > then memcpy() to the buffer provided by the caller, why don't you
> > > > > directly receive into the memory provided by the caller?
> > > >
> > > > [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> > > > using the MFD'd dynamically allocated buffer to submit URBs will better
> > > > manage USB-related operations
> > >
> > > The non-compliant max packet size limitation is unrelated to the
> > > question which RX or TX buffer to use.
> >
> > I think these two USB functions can be easily called using the buffer
> > dynamically
> > allocated by the MFD. However, if they transfer data directly to the
> > target buffer,
> > they must ensure that it is not located on the stack.
>
> You have a high coupling between the MFD driver and the individual
> drivers anyways, so why not directly use the dynamically allocated
> buffer provided by the caller and get rid of the memcpy()?
Okay! I will provide a function to request and free buffer for child devices,
and update the caller's variables to use these two functions in the next patch.
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support
2024-10-25 23:34 ` Nobuhiro Iwamatsu
@ 2024-10-28 8:42 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-28 8:42 UTC (permalink / raw)
To: Nobuhiro Iwamatsu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Nobuhiro,
Thank you for your comments,
Nobuhiro Iwamatsu <iwamatsu@nigauri.org> 於 2024年10月26日 週六 上午7:35寫道:
>
> Hello,
>
> 2024年10月24日(木) 18:04 Ming Yu <a0282524688@gmail.com>:
> >
> > 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 | 276 ++++++++++++++++++++++++++++++++++++++
> > 4 files changed, 288 insertions(+)
> > create mode 100644 drivers/rtc/rtc-nct6694.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 4d5a5eded3b9..8de90bda8b5e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16445,6 +16445,7 @@ F: drivers/i2c/busses/i2c-nct6694.c
> > F: drivers/mfd/nct6694.c
> > F: drivers/net/can/nct6694_canfd.c
> > F: drivers/pwm/pwm-nct6694.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..240c496d95f7 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..622bb9fbe6f6
> > --- /dev/null
> > +++ b/drivers/rtc/rtc-nct6694.c
> > @@ -0,0 +1,276 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 RTC driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/slab.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/rtc.h>
> > +#include <linux/bcd.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/nct6694.h>
>
> Please sort header files alphabetically.
[Ming] Okay! I will sort these headers in the next patch.
>
> > +
> > +#define DRVNAME "nct6694-rtc"
> > +
> > +/* Host interface */
> > +#define REQUEST_RTC_MOD 0x08
> > +
> > +/* Message Channel */
> > +/* Command 00h */
> > +#define REQUEST_RTC_CMD0_LEN 0x07
> > +#define REQUEST_RTC_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> > +#define RTC_SEC_IDX 0x00
> > +#define RTC_MIN_IDX 0x01
> > +#define RTC_HOUR_IDX 0x02
> > +#define RTC_WEEK_IDX 0x03
> > +#define RTC_DAY_IDX 0x04
> > +#define RTC_MONTH_IDX 0x05
> > +#define RTC_YEAR_IDX 0x06
> > +/* Command 01h */
> > +#define REQUEST_RTC_CMD1_LEN 0x05
> > +#define REQUEST_RTC_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
> > +#define RTC_ALRM_EN_IDX 0x03
> > +#define RTC_ALRM_PEND_IDX 0x04
> > +/* Command 02h */
> > +#define REQUEST_RTC_CMD2_LEN 0x02
> > +#define REQUEST_RTC_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */
> > +#define RTC_IRQ_EN_IDX 0x00
> > +#define RTC_IRQ_PEND_IDX 0x01
> > +
> > +#define RTC_IRQ_EN (BIT(0) | BIT(5))
>
> RTC_IRQ_INT_EN | RTC_IRQ_GPO_EN ?
[Ming] Yes, it is used to enable both of them.
>
> > +#define RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */
> > +#define RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */
> > +#define RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */
> > +
> > +struct nct6694_rtc_data {
> > + struct nct6694 *nct6694;
> > + struct rtc_device *rtc;
> > + struct work_struct alarm_work;
> > +};
> > +
> > +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
> > +{
> > + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[REQUEST_RTC_CMD0_LEN];
> > + int ret;
> > +
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
> > + 0, REQUEST_RTC_CMD0_LEN, buf);
> > + if (ret) {
> > + pr_err("%s: Failed to get rtc device!\n", __func__);
> > + return -EIO;
> > + }
> > +
> > + tm->tm_sec = bcd2bin(buf[RTC_SEC_IDX]); /* tm_sec expect 0 ~ 59 */
> > + tm->tm_min = bcd2bin(buf[RTC_MIN_IDX]); /* tm_min expect 0 ~ 59 */
> > + tm->tm_hour = bcd2bin(buf[RTC_HOUR_IDX]); /* tm_hour expect 0 ~ 23 */
> > + tm->tm_wday = bcd2bin(buf[RTC_WEEK_IDX]) - 1; /* tm_wday expect 0 ~ 6 */
> > + tm->tm_mday = bcd2bin(buf[RTC_DAY_IDX]); /* tm_mday expect 1 ~ 31 */
> > + tm->tm_mon = bcd2bin(buf[RTC_MONTH_IDX]) - 1; /* tm_month expect 0 ~ 11 */
> > + tm->tm_year = bcd2bin(buf[RTC_YEAR_IDX]) + 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);
> > + unsigned char buf[REQUEST_RTC_CMD0_LEN];
> > + int ret;
> > +
> > + buf[RTC_SEC_IDX] = bin2bcd(tm->tm_sec);
> > + buf[RTC_MIN_IDX] = bin2bcd(tm->tm_min);
> > + buf[RTC_HOUR_IDX] = bin2bcd(tm->tm_hour);
> > + buf[RTC_WEEK_IDX] = bin2bcd(tm->tm_wday + 1);
> > + buf[RTC_DAY_IDX] = bin2bcd(tm->tm_mday);
> > + buf[RTC_MONTH_IDX] = bin2bcd(tm->tm_mon + 1);
> > + buf[RTC_YEAR_IDX] = bin2bcd(tm->tm_year - 100);
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN,
> > + buf);
> > + if (ret) {
> > + pr_err("%s: Failed to set rtc device!\n", __func__);
> > + return -EIO;
>
> Why do you return -EIO? Please do not overwrite error codes.
[Ming] Understood, thank you.
>
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> > +{
> > + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[REQUEST_RTC_CMD1_LEN];
> > + int ret;
> > +
> > + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
> > + 0, REQUEST_RTC_CMD1_LEN, buf);
> > + if (ret) {
> > + pr_err("%s: Failed to get rtc device!\n", __func__);
> > + return -EIO;
>
> same as above.
>
> > + }
> > +
> > + alrm->time.tm_sec = bcd2bin(buf[RTC_SEC_IDX]);
> > + alrm->time.tm_min = bcd2bin(buf[RTC_MIN_IDX]);
> > + alrm->time.tm_hour = bcd2bin(buf[RTC_HOUR_IDX]);
> > +
> > + alrm->enabled = buf[RTC_ALRM_EN_IDX];
> > + alrm->pending = buf[RTC_ALRM_PEND_IDX];
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> > +{
> > + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[REQUEST_RTC_CMD1_LEN];
> > + int ret;
> > +
> > + buf[RTC_SEC_IDX] = bin2bcd(alrm->time.tm_sec);
> > + buf[RTC_MIN_IDX] = bin2bcd(alrm->time.tm_min);
> > + buf[RTC_HOUR_IDX] = bin2bcd(alrm->time.tm_hour);
> > + buf[RTC_ALRM_EN_IDX] = alrm->enabled ? RTC_IRQ_EN : 0;
> > + buf[RTC_ALRM_PEND_IDX] = 0;
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN,
> > + buf);
> > + if (ret) {
> > + pr_err("%s: Failed to set rtc device!\n", __func__);
> > + return -EIO;
>
> same as above.
>
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
> > +{
> > + struct nct6694_rtc_data *data = dev_get_drvdata(dev);
> > + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
> > + int ret;
> > +
> > + if (enabled)
> > + buf[RTC_IRQ_EN_IDX] |= RTC_IRQ_EN;
> > + else
> > + buf[RTC_IRQ_EN_IDX] &= ~RTC_IRQ_EN;
> > +
> > + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD2_OFFSET, REQUEST_RTC_CMD2_LEN,
> > + buf);
> > + if (ret) {
> > + pr_err("%s: Failed to set rtc device!\n", __func__);
> > + return -EIO;
>
> same as above.
>
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +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 void nct6694_rtc_alarm(struct work_struct *work)
> > +{
> > + struct nct6694_rtc_data *data;
> > + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0};
> > +
> > + data = container_of(work, struct nct6694_rtc_data, alarm_work);
> > +
> > + pr_info("%s: Got RTC alarm!\n", __func__);
> > + buf[RTC_IRQ_EN_IDX] = RTC_IRQ_EN;
> > + buf[RTC_IRQ_PEND_IDX] = RTC_IRQ_STS;
> > + nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD,
> > + REQUEST_RTC_CMD2_OFFSET,
> > + REQUEST_RTC_CMD2_LEN, buf);
> > +}
> > +
> > +static void nct6694_rtc_handler(void *private_data)
> > +{
> > + struct nct6694_rtc_data *data = private_data;
> > + struct nct6694 *nct6694 = data->nct6694;
> > +
> > + queue_work(nct6694->async_workqueue, &data->alarm_work);
> > +}
> > +
> > +static int nct6694_rtc_probe(struct platform_device *pdev)
> > +{
> > + struct nct6694_rtc_data *data;
> > + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > + int ret;
> > +
> > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->rtc = devm_rtc_allocate_device(&pdev->dev);
> > + if (IS_ERR(data->rtc))
> > + return PTR_ERR(data->rtc);
>
> Please use dev_err_probe.
[Ming] Okay! I will change it in the next patch.
>
> > +
> > + 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;
> > +
> > + INIT_WORK(&data->alarm_work, nct6694_rtc_alarm);
> > +
> > + ret = nct6694_register_handler(nct6694, RTC_IRQ_STATUS,
> > + nct6694_rtc_handler, data);
> > + if (ret) {
> > + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> > + __func__, ERR_PTR(ret));
>
> Please use dev_err_probe.
[Ming] Okay! I will change it in the next patch.
>
> > + return ret;
> > + }
> > +
> > + device_set_wakeup_capable(&pdev->dev, 1);
> > +
> > + platform_set_drvdata(pdev, data);
> > +
> > + /* Register rtc device to RTC framework */
> > + ret = devm_rtc_register_device(data->rtc);
> > + if (ret) {
> > + dev_err(&pdev->dev, "Failed to register rtc device!\n");
> > + return ret;
> > + }
>
> You can simplify return devm_rtc_register_device.
[Ming] Okay! I will change it in the next patch.
>
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver nct6694_rtc_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_rtc_probe,
> > +};
> > +
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_rtc_driver);
> > + if (!err) {
> > + if (err)
>
> This looks strange. You can simplify return platform_driver_register.
[Ming] For platform driver registration, I'll change it to
module_platform_driver() in the next patch.
>
> > + platform_driver_unregister(&nct6694_rtc_driver);
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_rtc_driver);
> > +}
> > +module_exit(nct6694_exit);
> > +
> > +MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.34.1
> >
> >
>
> Best regards,
> Nobuhiro
>
> --
> Nobuhiro Iwamatsu
> iwamatsu at {nigauri.org / debian.org / kernel.org}
> GPG ID: 32247FBB40AD1FA6
Best regards,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-25 7:46 ` Bartosz Golaszewski
@ 2024-10-28 8:56 ` Ming Yu
2024-10-30 19:32 ` Bartosz Golaszewski
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-28 8:56 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, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Bartosz,
Thank you for your comments,
Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年10月25日 週五 下午3:47寫道:
>
> On Fri, Oct 25, 2024 at 9:39 AM 游子民 <a0282524688@gmail.com> wrote:
> >
> > Sorry, resending this email in plain text format.
> >
> > Dear Bart,
> >
> > Thank you for your comments.
> >
> > Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年10月24日 週四 下午5:47寫道:
> > >
> > > On Thu, Oct 24, 2024 at 10:59 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 | 489 ++++++++++++++++++++++++++++++++++++
> > > > 4 files changed, 503 insertions(+)
> > > > create mode 100644 drivers/gpio/gpio-nct6694.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index 30157ca95cf3..2c86d5dab3f1 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -16438,6 +16438,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 d93cd4f722b4..aa78ad9ff4ac 100644
> > > > --- a/drivers/gpio/Kconfig
> > > > +++ b/drivers/gpio/Kconfig
> > > > @@ -1450,6 +1450,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 1429e8c0229b..02c94aa28017 100644
> > > > --- a/drivers/gpio/Makefile
> > > > +++ b/drivers/gpio/Makefile
> > > > @@ -121,6 +121,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..42c0e6e76730
> > > > --- /dev/null
> > > > +++ b/drivers/gpio/gpio-nct6694.c
> > > > @@ -0,0 +1,489 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Nuvoton NCT6694 GPIO controller driver based on USB interface.
> > > > + *
> > > > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > > > + */
> > > > +
> > > > +#include <linux/gpio.h>
> > >
> > > Don't include this header. It's documented as obsolete.
> >
> > [Ming] Okay! I'll drop it in the next patch.
> >
> > >
> > > > +#include <linux/gpio/driver.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/interrupt.h>
> > > > +#include <linux/platform_device.h>
> > > > +#include <linux/mfd/core.h>
> > > > +#include <linux/mfd/nct6694.h>
> > > > +
> > >
> > > You only use it once, drop it.
> >
> > [Ming] That line is blank, did you mean #include <linux/gpio.h>?
> >
> > >
> > > > +#define DRVNAME "nct6694-gpio"
>
> I meant this line. Just put the driver name in the driver struct
> definition directly.
>
> > > > +
> > > > +/* Host interface */
> > > > +#define REQUEST_GPIO_MOD 0xFF
> > > > +#define REQUEST_GPIO_LEN 0x01
> > > > +
> > > > +/* Report Channel */
> > > > +#define GPIO_VER_REG 0x90
> > > > +#define GPIO_VALID_REG 0x110
> > > > +#define GPI_DATA_REG 0x120
> > > > +#define GPO_DIR_REG 0x170
> > > > +#define GPO_TYPE_REG 0x180
> > > > +#define GPO_DATA_REG 0x190
> > > > +
> > > > +#define GPI_STS_REG 0x130
> > > > +#define GPI_CLR_REG 0x140
> > > > +#define GPI_FALLING_REG 0x150
> > > > +#define GPI_RISING_REG 0x160
> > > > +
> > >
> > > Please use the NCT6694 prefix for these defines, otherwise it's not
> > > clear whether they come from the driver or from GPIO core.
> > >
> > > []
> >
> > [Ming] Okay! I'll add the prefix to the defines in the next patch.
> >
> > >
> > > > +
> > > > +static const char * const nct6694_gpio_name[] = {
> > > > + "NCT6694-GPIO0",
> > > > + "NCT6694-GPIO1",
> > > > + "NCT6694-GPIO2",
> > > > + "NCT6694-GPIO3",
> > > > + "NCT6694-GPIO4",
> > > > + "NCT6694-GPIO5",
> > > > + "NCT6694-GPIO6",
> > > > + "NCT6694-GPIO7",
> > > > + "NCT6694-GPIO8",
> > > > + "NCT6694-GPIO9",
> > > > + "NCT6694-GPIOA",
> > > > + "NCT6694-GPIOB",
> > > > + "NCT6694-GPIOC",
> > > > + "NCT6694-GPIOD",
> > > > + "NCT6694-GPIOE",
> > > > + "NCT6694-GPIOF",
> > > > +};
> > >
> > > This looks like it corresponds with the MFD cells and makes me wonder:
> > > am I getting that wrong or do you want to register 0xf GPIO chips? Or
> > > a single GPIO chip with 0xf lines? What is the topology?
> >
> > [Ming] Yes, it corresponds to the MFD cells.
> > I would like to register 16 GPIO chips, each with 8 lines.
> > The chip has 128 pins totally, the core can check if the pin is valid through
> > the init_valid_mask() callback.
> >
>
> Ok, that's fine but the GPIO chip names should be in the MFD driver
> only, it doesn't make sense to have them here. It's the MFD core that
> will register the GPIO platform devices.
I understand. I will remove it.
>
> No for line names - as this is a dynamic USB expander, I'd suggest to
> have them in the driver and assign to gc->names.
Could I create an array to map each of the GPIO pins?
>
> > >
> > > > +
> > > > +static int nct6694_gpio_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_gpio_data *data;
> > > > + struct gpio_irq_chip *girq;
> > > > + int ret;
> > > > +
> > > > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > > > + if (!data)
> > > > + return -ENOMEM;
> > > > +
> > > > + data->nct6694 = nct6694;
> > > > + data->group = cell->id;
> > > > +
> > > > + data->gpio.label = nct6694_gpio_name[cell->id];
> > > > + 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 = 8;
> > > > +
> > > > + INIT_WORK(&data->irq_work, nct6694_irq);
> > > > + INIT_WORK(&data->irq_trig_work, nct6694_irq_trig);
> > > > + mutex_init(&data->irq_lock);
> > > > +
> > > > + ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS,
> > > > + nct6694_gpio_handler, data);
> > > > + if (ret) {
> > > > + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n",
> > > > + __func__, ERR_PTR(ret));
> > > > + return ret;
> > > > + }
> > > > +
> > > > + platform_set_drvdata(pdev, data);
> > > > +
> > > > + ret = nct6694_get_irq_trig(data);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + /* 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 = gpiochip_add_data(&data->gpio, data);
> > > > + if (ret) {
> > > > + dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe",
> > > > + __func__, ERR_PTR(ret));
> > > > + return ret;
> > > > + }
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void nct6694_gpio_remove(struct platform_device *pdev)
> > > > +{
> > > > + struct nct6694_gpio_data *data = platform_get_drvdata(pdev);
> > > > +
> > > > + gpiochip_remove(&data->gpio);
> > >
> > > This should be dropped in favor of using devm_gpiochip_add_data().
> > > Especially since you probably want to cancel the irq_work before
> > > removing the chip.
> >
> > [Ming] Okay! I'll change it in the next patch.
> >
> > >
> > > > + cancel_work(&data->irq_work);
> > > > + cancel_work(&data->irq_trig_work);
> > > > +}
> > > > +
> > > > +static struct platform_driver nct6694_gpio_driver = {
> > > > + .driver = {
> > > > + .name = DRVNAME,
> > > > + },
> > > > + .probe = nct6694_gpio_probe,
> > > > + .remove = nct6694_gpio_remove,
> > > > +};
> > > > +
> > > > +static int __init nct6694_init(void)
> > > > +{
> > > > + int err;
> > > > +
> > > > + err = platform_driver_register(&nct6694_gpio_driver);
> > > > + if (!err) {
> > > > + if (err)
> > >
> > > If err is equal to 0, check if it's not equal to zero?
> > >
> > > > + platform_driver_unregister(&nct6694_gpio_driver);
> > >
> > > If platform_driver_register() failed, then the device was never registered.
> > >
> > > > + }
> > > > +
> > > > + return err;
> > > > +}
> > > > +subsys_initcall(nct6694_init);
> > >
> > > Any reason why this must be initialized earlier? It's a USB driver after all.
> >
> > [Ming] For platform driver registration, I'll change it to
> > module_platform_driver()
> > in the next patch.
> >
>
> Thanks,
> Bartosz
>
> > >
> > > > +
> > > > +static void __exit nct6694_exit(void)
> > > > +{
> > > > + platform_driver_unregister(&nct6694_gpio_driver);
> > > > +}
> > > > +module_exit(nct6694_exit);
> > > > +
> > > > +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
> > > > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > > > +MODULE_LICENSE("GPL");
> > > > --
> > > > 2.34.1
> > > >
> > >
> > > Bart
Best regards,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support
2024-10-24 15:32 ` Guenter Roeck
@ 2024-10-28 9:49 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-28 9:49 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,
jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Guenter,
Thank you for your comments,
I will remove the unnecessary logs in the next patch.
Guenter Roeck <linux@roeck-us.net> 於 2024年10月24日 週四 下午11:33寫道:
>
> On 10/24/24 01:59, 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 | 329 +++++++++++++++++++++++++++++++++
> > 4 files changed, 342 insertions(+)
> > create mode 100644 drivers/watchdog/nct6694_wdt.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index eccd5e795daa..63387c0d4ab6 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -16442,6 +16442,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 684b9fe84fff..bc9d63d69204 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 ab6f2b41e38e..453ceacd43ab 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -231,6 +231,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..68e2926ec504
> > --- /dev/null
> > +++ b/drivers/watchdog/nct6694_wdt.c
> > @@ -0,0 +1,329 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 WDT driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/watchdog.h>
> > +#include <linux/slab.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/nct6694.h>
> > +
> > +#define DRVNAME "nct6694-wdt"
> > +
> > +#define WATCHDOG_TIMEOUT 10
> > +#define WATCHDOG_PRETIMEOUT 0
> > +
> > +/* Host interface */
> > +#define REQUEST_WDT_MOD 0x07
> > +
> > +/* Message Channel*/
> > +/* Command 00h */
> > +#define REQUEST_WDT_CMD0_LEN 0x0F
> > +#define REQUEST_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */
> > +#define WDT_PRETIMEOUT_IDX 0x00
> > +#define WDT_PRETIMEOUT_LEN 0x04 /* PRETIMEOUT(3byte) | ACT(1byte) */
> > +#define WDT_TIMEOUT_IDX 0x04
> > +#define WDT_TIMEOUT_LEN 0x04 /* TIMEOUT(3byte) | ACT(1byte) */
> > +#define WDT_COUNTDOWN_IDX 0x0C
> > +#define WDT_COUNTDOWN_LEN 0x03
> > +
> > +#define WDT_PRETIMEOUT_ACT BIT(1)
> > +#define WDT_TIMEOUT_ACT BIT(1)
> > +
> > +/* Command 01h */
> > +#define REQUEST_WDT_CMD1_LEN 0x04
> > +#define REQUEST_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */
> > +#define WDT_CMD_IDX 0x00
> > +#define WDT_CMD_LEN 0x04
> > +
> > +static unsigned int timeout;
> > +module_param(timeout, int, 0);
> > +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
> > +
> > +static unsigned int 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) ")");
> > +
> > +struct nct6694_wdt_data {
> > + struct nct6694 *nct6694;
> > + struct watchdog_device wdev;
> > + unsigned int wdev_idx;
> > +};
> > +
> > +static inline void set_buf32(void *buf, u32 u32_val)
> > +{
> > + u8 *p = (u8 *)buf;
> > +
> > + p[0] = u32_val & 0xFF;
> > + p[1] = (u32_val >> 8) & 0xFF;
> > + p[2] = (u32_val >> 16) & 0xFF;
> > + p[3] = (u32_val >> 24) & 0xFF;
> > +}
> > +
> > +static int nct6694_wdt_start(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > +
> > + pr_debug("%s: WDT(%d) Start\n", __func__, data->wdev_idx);
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_wdt_stop(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'C'};
> > + int ret;
> > +
> > + pr_debug("%s: WDT(%d) Close\n", __func__, data->wdev_idx);
> > + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD1_LEN, buf);
> > + if (ret)
> > + pr_err("%s: Failed to start WDT device!\n", __func__);
>
> Please refrain from logging noise. Besides, the message is wrong:
> the watchdog is stopped here, not started.
>
> Also, all messages should use dev_, not pr_ functions.
[Ming] Okay! I will change it.
>
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_wdt_ping(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'S'};
> > + int ret;
> > +
> > + pr_debug("%s: WDT(%d) Ping\n", __func__, data->wdev_idx);
> > + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD1_LEN, buf);
> > + if (ret)
> > + pr_err("%s: Failed to ping WDT device!\n", __func__);
>
> Same as above and everywhere else.
>
> > +
> > + return ret;
> > +}
> > +
> > +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
> > + unsigned int timeout)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned int timeout_fmt, pretimeout_fmt;
> > + unsigned char buf[REQUEST_WDT_CMD0_LEN];
> > + int ret;
> > +
> > + if (timeout < wdev->pretimeout) {
> > + pr_err("%s: 'timeout' must be greater than 'pre timeout'!\n",
> > + __func__);
> > + return -EINVAL;
>
> the driver is supposed to adjust pretimeout in this case. And please,
> again, refrain from logging noise.
[Ming] Excuse me, is any recommendation about pretimeout value?
> > + }
> > +
> > + timeout_fmt = timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> > + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> > + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> > + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> > +
> > + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD0_LEN, buf);
> > + if (ret) {
> > + pr_err("%s: Don't write the setup command in Start stage!\n",
> > + __func__);
> > + return ret;
> > + }
> > +
> > + wdev->timeout = timeout;
> > +
> > + return 0;
> > +}
> > +
> > +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
> > + unsigned int pretimeout)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned int timeout_fmt, pretimeout_fmt;
> > + unsigned char buf[REQUEST_WDT_CMD0_LEN];
> > + int ret;
> > +
> > + if (pretimeout > wdev->timeout) {
> > + pr_err("%s: 'pre timeout' must be less than 'timeout'!\n",
> > + __func__);
> > + return -EINVAL;
>
> Already checked by the watchdog core.
[Ming] I understand. I will remove it.
>
> > + }
> > + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> > + pretimeout_fmt = pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> > + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> > + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> > +
> > + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD0_LEN, buf);
> > + if (ret) {
> > + pr_err("%s: Don't write the setup command in Start stage!\n", __func__);
>
> Besides it being noise, I don't even understand what this message is
> supposed to mean, and neither would anyone else.
>
> > + return ret;
> > + }
> > +
> > + wdev->pretimeout = pretimeout;
> > + return 0;
> > +}
> > +
> > +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned char buf[WDT_COUNTDOWN_LEN];
> > + unsigned int timeleft_ms;
> > + int ret;
> > +
> > + ret = nct6694_read_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD0_LEN, WDT_COUNTDOWN_IDX,
> > + WDT_COUNTDOWN_LEN, buf);
> > + if (ret)
> > + pr_err("%s: Failed to get WDT device!\n", __func__);
> > +
> > + timeleft_ms = ((buf[2] << 16) | (buf[1] << 8) | buf[0]) & 0xFFFFFF;
>
> If the above command failed this will be a random number.
[Ming] Okay! I will update error handling about return value.
>
> > +
> > + return timeleft_ms / 1000;
> > +}
> > +
> > +static int nct6694_wdt_setup(struct watchdog_device *wdev)
> > +{
> > + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
> > + struct nct6694 *nct6694 = data->nct6694;
> > + unsigned char buf[REQUEST_WDT_CMD0_LEN] = {0};
> > + unsigned int timeout_fmt, pretimeout_fmt;
> > + int ret;
> > +
> > + if (timeout)
> > + wdev->timeout = timeout;
> > +
> Already set.
>
> > + if (pretimeout) {
> > + wdev->pretimeout = pretimeout;
>
> Pretimeout is already set in the probe function. Do it completely there.
>
> > + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24);
> > + } else {
> > + pretimeout_fmt = 0;
> > + }
> > +
> > + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24);
> > + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt));
> > + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt));
> > +
> > + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD,
> > + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx),
> > + REQUEST_WDT_CMD0_LEN, buf);
>
>
> This seems pretty pointless at this time. Why not do it in the watchdog
> start function ?
[Ming] Yes, I will remove it and do it in the start callback in the next patch.
>
> > + if (ret)
> > + return ret;
> > +
> > + pr_info("Setting WDT(%d): timeout = %d, pretimeout = %d\n",
> > + data->wdev_idx, wdev->timeout, wdev->pretimeout);
> > +
> > + return 0;
> > +}
> > +
> > +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 nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > + struct nct6694_wdt_data *data;
> > + struct watchdog_device *wdev;
> > + int ret;
> > +
> > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + data->nct6694 = nct6694;
> > + data->wdev_idx = cell->id;
> > +
> > + wdev = &data->wdev;
> > + wdev->info = &nct6694_wdt_info;
> > + wdev->ops = &nct6694_wdt_ops;
> > + wdev->timeout = WATCHDOG_TIMEOUT;
> > + wdev->pretimeout = WATCHDOG_PRETIMEOUT;
> > + wdev->min_timeout = 1;
> > + wdev->max_timeout = 255;
> > +
> > + platform_set_drvdata(pdev, data);
> > +
> > + /* Register watchdog timer device to WDT framework */
> > + watchdog_set_drvdata(&data->wdev, data);
> > + watchdog_init_timeout(&data->wdev, timeout, &pdev->dev);
> > + watchdog_set_nowayout(&data->wdev, nowayout);
> > + watchdog_stop_on_reboot(&data->wdev);
> > +
> > + ret = devm_watchdog_register_device(&pdev->dev, &data->wdev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "%s: Failed to register watchdog device: %d\n",
> > + __func__, ret);
> > + return ret;
> > + }
> > +
> > + ret = nct6694_wdt_setup(&data->wdev);
>
> This is too late. It needs to be done before registering the watchdog.
>
> > + if (ret) {
> > + dev_err(&pdev->dev, "Failed to setup WDT device!\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver nct6694_wdt_driver = {
> > + .driver = {
> > + .name = DRVNAME,
> > + },
> > + .probe = nct6694_wdt_probe,
> > +};
> > +
> > +static int __init nct6694_init(void)
> > +{
> > + int err;
> > +
> > + err = platform_driver_register(&nct6694_wdt_driver);
> > + if (!err) {
> > + if (err)
> > + platform_driver_unregister(&nct6694_wdt_driver);
> > + }
> > +
> > + return err;
> > +}
> > +subsys_initcall(nct6694_init);
> > +
> > +static void __exit nct6694_exit(void)
> > +{
> > + platform_driver_unregister(&nct6694_wdt_driver);
> > +}
> > +module_exit(nct6694_exit);
> > +
> > +MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
> > +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> > +MODULE_LICENSE("GPL");
>
Best regards,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-28 8:31 ` Ming Yu
@ 2024-10-28 14:06 ` Marc Kleine-Budde
2024-10-29 3:45 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-28 14:06 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 5017 bytes --]
On 28.10.2024 16:31:25, Ming Yu wrote:
> > > > > > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > > > > > >
> > > > > > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > > > > > The core will divide packet 256 bytes for high speed USB device, but
> > > > > > > it is exceeds
> > > > > > > the hardware limitation, so I am dividing it manually.
> > > > > >
> > > > > > You say the endpoint descriptor is correctly reporting it's max packet
> > > > > > size of 128, but the Linux USB will send packets of 256 bytes?
> > > > >
> > > > > [Ming] The endpoint descriptor is correctly reporting it's max packet
> > > > > size of 256, but the Linux USB may send more than 256 (max is 512)
> > > > > https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
> > > >
> > > > AFAIK according to the USB-2.0 spec the maximum packet size for
> > > > high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
> > > > your device is a non-compliant USB device?
> > >
> > > We will reduce the endpoint size of other interfaces to ensure that MFD device
> > > meets the USB2.0 spec. In other words, I will remove the code for manual
> > > unpacking in the next patch.
> >
> > I was not talking about the driver, but your USB device. According to
> > the USB2.0 spec, the packet size is fixed to 512 for high-speed bulk
> > transfers. So your device must be able to handle 512 byte transfers or
> > it's a non-compliant USB device.
>
> I understand. Therefore, the USB device's firmware will be modified to support
> bulk pipe size of 512 bytes to comply with the USB 2.0 spec.
Then you don't need manual segmentation of bulk transfers anymore!
> > > > > > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > > > > > + if (len > nct6694->maxp)
> > > > > > > > > + packet_len = nct6694->maxp;
> > > > > > > > > + else
> > > > > > > > > + packet_len = len;
> > > > > > > > > +
> > > > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > > > > > + if (ret)
> > > > > > > > > + goto err;
> > > > > > > > > + }
> > > > > > > > > +
> > > > > > > > > + for (i = 0; i < rd_len; i++)
> > > > > > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > > > > > >
> > > > > > > > memcpy()?
> > > > > > > >
> > > > > > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > > > > > of the data doesn't make it faster.
> > > > > > > >
> > > > > > > > On the other hand, receiving directly into the target buffer means the
> > > > > > > > target buffer must not live on the stack.
> > > > > > >
> > > > > > > [Ming] Okay! I'll change it to memcpy().
> > > > > >
> > > > > > fine!
> > > > > >
> > > > > > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > > > > > by the MFD device. does it need to be changed?
> > > > > >
> > > > > > My question is: Why do you first receive into the nct6694->rx_buffer and
> > > > > > then memcpy() to the buffer provided by the caller, why don't you
> > > > > > directly receive into the memory provided by the caller?
> > > > >
> > > > > [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> > > > > using the MFD'd dynamically allocated buffer to submit URBs will better
> > > > > manage USB-related operations
> > > >
> > > > The non-compliant max packet size limitation is unrelated to the
> > > > question which RX or TX buffer to use.
> > >
> > > I think these two USB functions can be easily called using the buffer
> > > dynamically
> > > allocated by the MFD. However, if they transfer data directly to the
> > > target buffer,
> > > they must ensure that it is not located on the stack.
> >
> > You have a high coupling between the MFD driver and the individual
> > drivers anyways, so why not directly use the dynamically allocated
> > buffer provided by the caller and get rid of the memcpy()?
>
> Okay! I will provide a function to request and free buffer for child devices,
> and update the caller's variables to use these two functions in the next patch.
I don't see a need to provide dedicated function to allocate and free
the buffers. The caller can allocate them as part of their private data,
or allocate them during probe().
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-28 7:58 ` Ming Yu
@ 2024-10-28 18:54 ` Jonathan Cameron
2024-10-30 3:29 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Jonathan Cameron @ 2024-10-28 18:54 UTC (permalink / raw)
To: Ming Yu
Cc: Guenter Roeck, Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij,
brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem,
edumazet, kuba, pabeni, wim, jdelvare, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
On Mon, 28 Oct 2024 15:58:00 +0800
Ming Yu <a0282524688@gmail.com> wrote:
> Dear Guenter,
>
> The original plan was to use the IIO driver to access the temperature
> and voltage sensors, and the HWMON driver to access the tachometers.
> However, since the device is a hot-plug USB device, as far as I know,
> IIO-HWMON is not applicable. I will merge the IIO driver part into the
> HWMON driver in the next patch.
> In other words, the driver will be used to access TIN, VIN and FIN.
See drivers/mfd/sun4i-gpadc.c
for an example of an mfd using the iio-hwmon bridge.
Jonathan
>
> Best regards
> Ming
>
> Guenter Roeck <linux@roeck-us.net> 於 2024年10月26日 週六 下午10:50寫道:
> >
> > On 10/25/24 08:44, Guenter Roeck wrote:
> > > On 10/25/24 08:22, Ming Yu wrote:
> > > [ ... ]
> > >
> > >>>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> > >>>>> + long val)
> > >>>>> +{
> > >>>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > >>>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> > >>>> [Kalesh] Please try to maintain RCT order for variable declaration
> > >>>
> > >>> Ok, but that is already the case here ?
> > >>
> > >> [Ming] Is there anything that needs to be changed?
> > >>
> > >
> > > I don't think so, If two lines have the same length, the order is up
> > > to the developer to decide.
> > >
> > > Question though is if the buffer needs to be initialized. You should drop
> > > the initialization if it is not necessary. In that case the second line
> > > would be shorter anyway, and the order question would not arise.
> > >
> >
> > Actually, I just noticed that you also submitted an IIO driver which
> > reports the same data again. If a chip has an IIO driver, there should
> > be no HWMON driver since the IIO -> HWMON bridge can then be used if
> > necessary. So please drop this driver.
> >
> > Thanks,
> > Guenter
> >
> >
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-28 14:06 ` Marc Kleine-Budde
@ 2024-10-29 3:45 ` Ming Yu
2024-10-29 8:14 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-29 3:45 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月28日 週一 下午10:06寫道:
>
> On 28.10.2024 16:31:25, Ming Yu wrote:
> > > > > > > > > The Linux USB stack can receive bulk messages longer than the max packet size.
> > > > > > > >
> > > > > > > > [Ming] Since NCT6694's bulk pipe endpoint size is 128 bytes for this MFD device.
> > > > > > > > The core will divide packet 256 bytes for high speed USB device, but
> > > > > > > > it is exceeds
> > > > > > > > the hardware limitation, so I am dividing it manually.
> > > > > > >
> > > > > > > You say the endpoint descriptor is correctly reporting it's max packet
> > > > > > > size of 128, but the Linux USB will send packets of 256 bytes?
> > > > > >
> > > > > > [Ming] The endpoint descriptor is correctly reporting it's max packet
> > > > > > size of 256, but the Linux USB may send more than 256 (max is 512)
> > > > > > https://elixir.bootlin.com/linux/v6.11.5/source/drivers/usb/host/xhci-mem.c#L1446
> > > > >
> > > > > AFAIK according to the USB-2.0 spec the maximum packet size for
> > > > > high-speed bulk transfers is fixed set to 512 bytes. Does this mean that
> > > > > your device is a non-compliant USB device?
> > > >
> > > > We will reduce the endpoint size of other interfaces to ensure that MFD device
> > > > meets the USB2.0 spec. In other words, I will remove the code for manual
> > > > unpacking in the next patch.
> > >
> > > I was not talking about the driver, but your USB device. According to
> > > the USB2.0 spec, the packet size is fixed to 512 for high-speed bulk
> > > transfers. So your device must be able to handle 512 byte transfers or
> > > it's a non-compliant USB device.
> >
> > I understand. Therefore, the USB device's firmware will be modified to support
> > bulk pipe size of 512 bytes to comply with the USB 2.0 spec.
>
> Then you don't need manual segmentation of bulk transfers anymore!
Understood, thank you very much.
>
> > > > > > > > > > + for (i = 0, len = length; len > 0; i++, len -= packet_len) {
> > > > > > > > > > + if (len > nct6694->maxp)
> > > > > > > > > > + packet_len = nct6694->maxp;
> > > > > > > > > > + else
> > > > > > > > > > + packet_len = len;
> > > > > > > > > > +
> > > > > > > > > > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT),
> > > > > > > > > > + nct6694->rx_buffer + nct6694->maxp * i,
> > > > > > > > > > + packet_len, &rx_len, nct6694->timeout);
> > > > > > > > > > + if (ret)
> > > > > > > > > > + goto err;
> > > > > > > > > > + }
> > > > > > > > > > +
> > > > > > > > > > + for (i = 0; i < rd_len; i++)
> > > > > > > > > > + buf[i] = nct6694->rx_buffer[i + rd_idx];
> > > > > > > > >
> > > > > > > > > memcpy()?
> > > > > > > > >
> > > > > > > > > Or why don't you directly receive data into the provided buffer? Copying
> > > > > > > > > of the data doesn't make it faster.
> > > > > > > > >
> > > > > > > > > On the other hand, receiving directly into the target buffer means the
> > > > > > > > > target buffer must not live on the stack.
> > > > > > > >
> > > > > > > > [Ming] Okay! I'll change it to memcpy().
> > > > > > >
> > > > > > > fine!
> > > > > > >
> > > > > > > > This is my perspective: the data is uniformly received by the rx_bffer held
> > > > > > > > by the MFD device. does it need to be changed?
> > > > > > >
> > > > > > > My question is: Why do you first receive into the nct6694->rx_buffer and
> > > > > > > then memcpy() to the buffer provided by the caller, why don't you
> > > > > > > directly receive into the memory provided by the caller?
> > > > > >
> > > > > > [Ming] Due to the bulk pipe maximum packet size limitation, I think consistently
> > > > > > using the MFD'd dynamically allocated buffer to submit URBs will better
> > > > > > manage USB-related operations
> > > > >
> > > > > The non-compliant max packet size limitation is unrelated to the
> > > > > question which RX or TX buffer to use.
> > > >
> > > > I think these two USB functions can be easily called using the buffer
> > > > dynamically
> > > > allocated by the MFD. However, if they transfer data directly to the
> > > > target buffer,
> > > > they must ensure that it is not located on the stack.
> > >
> > > You have a high coupling between the MFD driver and the individual
> > > drivers anyways, so why not directly use the dynamically allocated
> > > buffer provided by the caller and get rid of the memcpy()?
> >
> > Okay! I will provide a function to request and free buffer for child devices,
> > and update the caller's variables to use these two functions in the next patch.
>
> I don't see a need to provide dedicated function to allocate and free
> the buffers. The caller can allocate them as part of their private data,
> or allocate them during probe().
Okay, so each child device may allocate a buffer like this during probe():
priv->xmit_buf = devm_kcalloc(dev, MAX_PACKET_SZ, sizeof(unsigned char),
GFP_KERNEL), right?
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-29 3:45 ` Ming Yu
@ 2024-10-29 8:14 ` Marc Kleine-Budde
2024-11-01 5:35 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-29 8:14 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 1581 bytes --]
On 29.10.2024 11:45:30, Ming Yu wrote:
> > > > You have a high coupling between the MFD driver and the individual
> > > > drivers anyways, so why not directly use the dynamically allocated
> > > > buffer provided by the caller and get rid of the memcpy()?
> > >
> > > Okay! I will provide a function to request and free buffer for child devices,
> > > and update the caller's variables to use these two functions in the next patch.
> >
> > I don't see a need to provide dedicated function to allocate and free
> > the buffers. The caller can allocate them as part of their private data,
> > or allocate them during probe().
>
> Okay, so each child device may allocate a buffer like this during probe():
> priv->xmit_buf = devm_kcalloc(dev, MAX_PACKET_SZ, sizeof(unsigned char),
> GFP_KERNEL), right?
basically yes, probably devm_kzalloc() or embed it into the priv struct
directly with ____cacheline_aligned:
| https://elixir.bootlin.com/linux/v6.11.5/source/drivers/net/can/spi/mcp251xfd/mcp251xfd.h#L498
The size of the driver's RX and TX buffers depend on what they want to
send and expect to receive. The next step would be to create structs the
describe the RX and TX buffers for each driver. If you have a common
header between each driver, create that first.
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-28 18:54 ` Jonathan Cameron
@ 2024-10-30 3:29 ` Ming Yu
2024-10-30 4:26 ` Guenter Roeck
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-30 3:29 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Guenter Roeck, Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij,
brgl, andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem,
edumazet, kuba, pabeni, wim, jdelvare, lars, ukleinek,
alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
netdev, linux-watchdog, linux-hwmon, linux-iio, linux-pwm,
linux-rtc
Dear Jonathan,
Thanks you for your comments,
I tested your suggestion in both the MFD driver and the IIO driver, and
the iio-hwmon bridge worked well.
On the other hand, my requirements involve accessing thermal sensors,
voltage sensors and tachometers, so I should implement it in this HWMON
drive, right?
Best regards
Ming
Jonathan Cameron <jic23@kernel.org> 於 2024年10月29日 週二 上午2:54寫道:
>
> On Mon, 28 Oct 2024 15:58:00 +0800
> Ming Yu <a0282524688@gmail.com> wrote:
>
> > Dear Guenter,
> >
> > The original plan was to use the IIO driver to access the temperature
> > and voltage sensors, and the HWMON driver to access the tachometers.
> > However, since the device is a hot-plug USB device, as far as I know,
> > IIO-HWMON is not applicable. I will merge the IIO driver part into the
> > HWMON driver in the next patch.
> > In other words, the driver will be used to access TIN, VIN and FIN.
> See drivers/mfd/sun4i-gpadc.c
> for an example of an mfd using the iio-hwmon bridge.
>
> Jonathan
>
> >
> > Best regards
> > Ming
> >
> > Guenter Roeck <linux@roeck-us.net> 於 2024年10月26日 週六 下午10:50寫道:
> > >
> > > On 10/25/24 08:44, Guenter Roeck wrote:
> > > > On 10/25/24 08:22, Ming Yu wrote:
> > > > [ ... ]
> > > >
> > > >>>>> +static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
> > > >>>>> + long val)
> > > >>>>> +{
> > > >>>>> + struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
> > > >>>>> + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0};
> > > >>>> [Kalesh] Please try to maintain RCT order for variable declaration
> > > >>>
> > > >>> Ok, but that is already the case here ?
> > > >>
> > > >> [Ming] Is there anything that needs to be changed?
> > > >>
> > > >
> > > > I don't think so, If two lines have the same length, the order is up
> > > > to the developer to decide.
> > > >
> > > > Question though is if the buffer needs to be initialized. You should drop
> > > > the initialization if it is not necessary. In that case the second line
> > > > would be shorter anyway, and the order question would not arise.
> > > >
> > >
> > > Actually, I just noticed that you also submitted an IIO driver which
> > > reports the same data again. If a chip has an IIO driver, there should
> > > be no HWMON driver since the IIO -> HWMON bridge can then be used if
> > > necessary. So please drop this driver.
> > >
> > > Thanks,
> > > Guenter
> > >
> > >
> >
>
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-30 3:29 ` Ming Yu
@ 2024-10-30 4:26 ` Guenter Roeck
2024-11-01 6:11 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Guenter Roeck @ 2024-10-30 4:26 UTC (permalink / raw)
To: Ming Yu, Jonathan Cameron
Cc: Kalesh Anakkur Purayil, tmyu0, lee, linus.walleij, brgl,
andi.shyti, mkl, mailhol.vincent, andrew+netdev, davem, edumazet,
kuba, pabeni, wim, jdelvare, lars, ukleinek, alexandre.belloni,
linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-iio, linux-pwm, linux-rtc
On 10/29/24 20:29, Ming Yu wrote:
> Dear Jonathan,
>
> Thanks you for your comments,
> I tested your suggestion in both the MFD driver and the IIO driver, and
> the iio-hwmon bridge worked well.
> On the other hand, my requirements involve accessing thermal sensors,
> voltage sensors and tachometers, so I should implement it in this HWMON
> drive, right?
>
Duplicate drivers for the same hardware is not acceptable.
I see that so far only pwm and fan control is implemented in the hwmon driver.
There is no public documentation for NCT6694, so it is difficult to evaluate the
chip's capabilities. The summary doesn't even mention fan speed readings, meaning
pretty much everything is guesswork.
Either case, I do see that you also implemented a pwm driver which _does_
duplicate hwmon functionality. Sorry, that is a no-go. Again, we can not have
multiple drivers controlling the same hardware. A pwm controller implemented
in a hwmon device is supposed to be limited to fan control. It looks like
the pwm controller implemented in the NCT6694 is a generic pwm controller.
It is not appropriate to have a hwmon driver for such a pwm controller.
Guenter
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-25 8:33 ` Marc Kleine-Budde
@ 2024-10-30 8:30 ` Ming Yu
2024-10-30 10:12 ` Marc Kleine-Budde
0 siblings, 1 reply; 80+ messages in thread
From: Ming Yu @ 2024-10-30 8:30 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Marc,
I am trying to register interrupt controller for the MFD deivce.
I need to queue work to call handle_nested_irq() in the callback
of the interrupt pipe, right?
Best regards,
Ming
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月25日 週五 下午4:33寫道:
>
> On 25.10.2024 16:22:01, Ming Yu wrote:
> > Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月24日 週四 下午7:57寫道:
> > > On 24.10.2024 16:59:13, Ming Yu wrote:
> > > > This patch series introduces support for Nuvoton NCT6694, a peripheral
> > > > expander based on USB interface. It models the chip as an MFD driver
> > > > (1/9), GPIO driver(2/9), I2C Adapter driver(3/9), CANfd driver(4/9),
> > > > WDT driver(5/9), HWMON driver(6/9), IIO driver(7/9), PWM driver(8/9),
> > > > and RTC driver(9/9).
> > > >
> > > > The MFD driver implements USB device functionality to issue
> > > > custom-define USB bulk pipe packets for NCT6694. Each child device can
> > > > use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
> > > > a command. They can also register a handler function that will be called
> > > > when the USB device receives its interrupt pipe.
> > >
> > > What about implementing a proper IRQ demux handler instead?
>
> > I think the currently planned IRQ process meets expectations.
> > Is there anything that needs improvement?
>
> You can register the IRQs of the MFD device with the Linux kernel. This
> way the devices can request a threaded IRQ handler directly via the
> kernel function, instead of registering the callback.
>
> With a threaded IRQ handler you can directly call the
> nct6694_read_msg(), nct6694_write_msg() without the need to start a
> workqueue from the callback.
>
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-30 8:30 ` Ming Yu
@ 2024-10-30 10:12 ` Marc Kleine-Budde
2024-10-30 14:21 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Marc Kleine-Budde @ 2024-10-30 10:12 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 953 bytes --]
On 30.10.2024 16:30:37, Ming Yu wrote:
> I am trying to register interrupt controller for the MFD deivce.
> I need to queue work to call handle_nested_irq() in the callback
> of the interrupt pipe, right?
I think you can directly demux the IRQ from the interrupt endpoint
callback. But handle_nested_irq() only works from threaded IRQ context,
so you have to use something like generic_handle_domain_irq_safe().
Have a look for how to setup the IRQ domain:
| drivers/net/usb/lan78xx.c
| drivers/net/usb/smsc95xx.c
But the IRQ demux in the lan78xx only handles the PHY IRQ. The ksz
driver does proper IRQ demux:
| net/dsa/microchip/ksz_common.c
regards,
Marc
--
Pengutronix e.K. | Marc Kleine-Budde |
Embedded Linux | https://www.pengutronix.de |
Vertretung Nürnberg | Phone: +49-5121-206917-129 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices
2024-10-30 10:12 ` Marc Kleine-Budde
@ 2024-10-30 14:21 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-10-30 14:21 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Hi Marc,
okay, I will implement it in the next patch,
thank you very much!
Best regards
Ming
Marc Kleine-Budde <mkl@pengutronix.de> 於 2024年10月30日 週三 下午6:12寫道:
>
> On 30.10.2024 16:30:37, Ming Yu wrote:
> > I am trying to register interrupt controller for the MFD deivce.
> > I need to queue work to call handle_nested_irq() in the callback
> > of the interrupt pipe, right?
>
> I think you can directly demux the IRQ from the interrupt endpoint
> callback. But handle_nested_irq() only works from threaded IRQ context,
> so you have to use something like generic_handle_domain_irq_safe().
>
> Have a look for how to setup the IRQ domain:
>
> | drivers/net/usb/lan78xx.c
> | drivers/net/usb/smsc95xx.c
>
> But the IRQ demux in the lan78xx only handles the PHY IRQ. The ksz
> driver does proper IRQ demux:
>
> | net/dsa/microchip/ksz_common.c
>
> regards,
> Marc
>
> --
> Pengutronix e.K. | Marc Kleine-Budde |
> Embedded Linux | https://www.pengutronix.de |
> Vertretung Nürnberg | Phone: +49-5121-206917-129 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-28 8:56 ` Ming Yu
@ 2024-10-30 19:32 ` Bartosz Golaszewski
2024-11-01 6:15 ` Ming Yu
0 siblings, 1 reply; 80+ messages in thread
From: Bartosz Golaszewski @ 2024-10-30 19:32 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, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
On Mon, Oct 28, 2024 at 9:57 AM Ming Yu <a0282524688@gmail.com> wrote:
>
> >
> > No for line names - as this is a dynamic USB expander, I'd suggest to
> > have them in the driver and assign to gc->names.
>
> Could I create an array to map each of the GPIO pins?
>
Please trim the quoted email to only the relevant parts.
I'm not sure what you mean by that. There's a field in struct
gpio_chip which you can use to set the line names from the driver
code.
Bart
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 14:17 ` Marc Kleine-Budde
2024-10-24 14:20 ` Marc Kleine-Budde
@ 2024-11-01 1:37 ` Ming Yu
1 sibling, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 1:37 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
> > MAINTAINERS | 1 +
> > drivers/net/can/Kconfig | 10 +
> > drivers/net/can/Makefile | 1 +
> > drivers/net/can/nct6694_canfd.c | 843 ++++++++++++++++++++++++++++++++
>
> FTBFS:
>
> | make[5]: *** No rule to make target 'drivers/net/can/nct6604_canfd.o', needed by 'drivers/net/can/'. Stop.
>
> [...]
>
> > diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
> > index a71db2cfe990..825c011aead5 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) += nct6604_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..d873a9fd656d
> > --- /dev/null
> > +++ b/drivers/net/can/nct6694_canfd.c
> ^^^^^^^^^^^^^^^
>
> This doesn't compile :(
>
Sorry for the typo, it should be nct6694_canfd.o in the Makefile,
I will modify it in the next patch,
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 14:20 ` Marc Kleine-Budde
@ 2024-11-01 1:44 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 1:44 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
> | CC [M] drivers/net/can/nct6694_canfd.o
> | drivers/net/can/nct6694_canfd.c: In function ‘nct6694_canfd_start_xmit’:
> | drivers/net/can/nct6694_canfd.c:282:22: error: variable ‘echo_byte’ set but not used [-Werror=unused-but-set-variable]
> | 282 | unsigned int echo_byte;
> | | ^~~~~~~~~
> | drivers/net/can/nct6694_canfd.c: In function ‘nct6694_canfd_rx_work’:
> | drivers/net/can/nct6694_canfd.c:677:34: error: variable ‘stats’ set but not used [-Werror=unused-but-set-variable]
> | 677 | struct net_device_stats *stats;
> | | ^~~~~
> | cc1: all warnings being treated as errors
>
> If compiling with C=1, sparse throws the following errors:
>
> | drivers/net/can/nct6694_canfd.c:417:14: warning: cast to restricted __le32
> | drivers/net/can/nct6694_canfd.c:750:9: warning: cast to restricted __le32
> | drivers/net/can/nct6694_canfd.c:777:32: warning: cast to restricted __le32
>
> Marc
>
Okay! I will delete these unnecessary variables and correct the error in
the next patch.
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support
2024-10-24 15:28 ` Marc Kleine-Budde
@ 2024-11-01 5:32 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 5:32 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
> > > +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb,
> > > + struct net_device *ndev)
> > > +{
> > > + struct nct6694_canfd_priv *priv = netdev_priv(ndev);
> > > + struct nct6694 *nct6694 = priv->nct6694;
> > > + struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> > > + struct net_device_stats *stats = &ndev->stats;
> > > + int can_idx = priv->can_idx;
> > > + u32 txid = 0;
> > > + int i;
> > > + unsigned int echo_byte;
> > > + u8 data_buf[REQUEST_CAN_CMD10_LEN] = {0};
> > > +
> > > + if (can_dropped_invalid_skb(ndev, skb))
> > > + return NETDEV_TX_OK;
> > > +
> > > + /*
> > > + * No check for NCT66794 because the TX bit is read-clear
> > > + * and may be read-cleared by other function
> > > + * Just check the result of tx command.
> > > + */
> >
> > Where do you check the result of the TX command?
> >
> > > + /* Check if the TX buffer is full */
> >
> > Where's the check if the TX buffer is full?
> >
Sorry for the missing code. It will be checked in tx_work().
If nct6694_write_msg() for CMD10 returns an error related to CAN_Deliver,
I will add error handling in the next patch.
> > > + netif_stop_queue(ndev);
> > > +
> > > + if (can_idx == 0)
> > > + data_buf[CAN_TAG_IDX] = CAN_TAG_CAN0;
> > > + else
> > > + data_buf[CAN_TAG_IDX] = 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.
> > > + */
> > > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_EFF;
> > > + } else {
> > > + txid = cf->can_id & CAN_SFF_MASK;
> > > + }
> > > +
> > > + set_buf32(&data_buf[CAN_ID_IDX], txid);
> > > +
> > > + data_buf[CAN_DLC_IDX] = cf->len;
> > > +
> > > + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) {
> > > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_FD;
> > > + if (cf->flags & CANFD_BRS)
> > > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_BRS;
> > > + }
> > > +
> > > + if (cf->can_id & CAN_RTR_FLAG)
> > > + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_RTR;
> > > +
> > > + /* set data to buf */
> > > + for (i = 0; i < cf->len; i++)
> > > + data_buf[CAN_DATA_IDX + i] = *(u8 *)(cf->data + i);
> > > +
> > > + can_put_echo_skb(skb, ndev, 0, 0);
> > > +
> > > + memcpy(priv->data_buf, data_buf, REQUEST_CAN_CMD10_LEN);
> > > + queue_work(nct6694->async_workqueue, &priv->tx_work);
> > > +
> > > + stats->tx_bytes += cf->len;
> > > + stats->tx_packets++;
> > > + echo_byte = can_get_echo_skb(ndev, 0, NULL);
> > > +
> > > + netif_wake_queue(ndev);
> >
> > How do you make sure that the tx_work has finished?
> > Once you wake the queue, the xmit function can be called again. If your
> > tx_work has not finished, you'll overwrite the priv->data_buf.
>
> Do you get a CAN TX complete message/IRQ from your device?
>
I will move the code that needs to confirm the completion of TX transmission
to tx_work().
In tx_work(), CMD2 CAN_Event's TX_EVT will first confirm TX_FIFO_EMPTY,
ensuring transmission is complete.
Best regards,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694
2024-10-29 8:14 ` Marc Kleine-Budde
@ 2024-11-01 5:35 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 5:35 UTC (permalink / raw)
To: Marc Kleine-Budde
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
> > Okay, so each child device may allocate a buffer like this during probe():
> > priv->xmit_buf = devm_kcalloc(dev, MAX_PACKET_SZ, sizeof(unsigned char),
> > GFP_KERNEL), right?
>
> basically yes, probably devm_kzalloc() or embed it into the priv struct
> directly with ____cacheline_aligned:
>
> | https://elixir.bootlin.com/linux/v6.11.5/source/drivers/net/can/spi/mcp251xfd/mcp251xfd.h#L498
>
> The size of the driver's RX and TX buffers depend on what they want to
> send and expect to receive. The next step would be to create structs the
> describe the RX and TX buffers for each driver. If you have a common
> header between each driver, create that first.
>
> regards,
> Marc
>
Understood, I'll make these modifications in the next patch.
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support
2024-10-30 4:26 ` Guenter Roeck
@ 2024-11-01 6:11 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 6:11 UTC (permalink / raw)
To: Guenter Roeck
Cc: Jonathan Cameron, Kalesh Anakkur Purayil, tmyu0, lee,
linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, jdelvare, lars,
ukleinek, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
linux-can, netdev, linux-watchdog, linux-hwmon, linux-iio,
linux-pwm, linux-rtc
> Duplicate drivers for the same hardware is not acceptable.
>
> I see that so far only pwm and fan control is implemented in the hwmon driver.
> There is no public documentation for NCT6694, so it is difficult to evaluate the
> chip's capabilities. The summary doesn't even mention fan speed readings, meaning
> pretty much everything is guesswork.
>
> Either case, I do see that you also implemented a pwm driver which _does_
> duplicate hwmon functionality. Sorry, that is a no-go. Again, we can not have
> multiple drivers controlling the same hardware. A pwm controller implemented
> in a hwmon device is supposed to be limited to fan control. It looks like
> the pwm controller implemented in the NCT6694 is a generic pwm controller.
> It is not appropriate to have a hwmon driver for such a pwm controller.
>
> Guenter
>
I understand, I'll drop the pwm controller driver in the next patch,
and implement only the fan
control functionality in HWMON driver.
These functionalities are actually implemented by NCT6694 firmware,
which supports up to
16 voltage sensors, 26 temperature sensors, 10 PWM controllers and 10
tachometers.
Do you think implementing these functionalities in the HWMON driver
complies with regulations?
If the answer is yes, I will drop the IIO and PWM driver and implement
everything in the HWMON
driver in the next patch.
Thanks,
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support
2024-10-30 19:32 ` Bartosz Golaszewski
@ 2024-11-01 6:15 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-01 6:15 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, jic23, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Bartosz Golaszewski <brgl@bgdev.pl> 於 2024年10月31日 週四 上午3:32寫道:
>
> > > No for line names - as this is a dynamic USB expander, I'd suggest to
> > > have them in the driver and assign to gc->names.
> >
> > Could I create an array to map each of the GPIO pins?
> >
>
> Please trim the quoted email to only the relevant parts.
>
> I'm not sure what you mean by that. There's a field in struct
> gpio_chip which you can use to set the line names from the driver
> code.
>
Understood, thank you very much.
Best regards
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support
2024-10-26 14:41 ` Jonathan Cameron
@ 2024-11-05 6:21 ` Ming Yu
0 siblings, 0 replies; 80+ messages in thread
From: Ming Yu @ 2024-11-05 6:21 UTC (permalink / raw)
To: Jonathan Cameron
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, lars, ukleinek, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
Dear Jonathan,
Thank you for your comments,
I will make changes based on the part you mentioned in the future.
Best regards
Ming
^ permalink raw reply [flat|nested] 80+ messages in thread
* Re: [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support
2024-10-24 8:59 ` [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support Ming Yu
@ 2024-11-22 18:05 ` Uwe Kleine-König
0 siblings, 0 replies; 80+ messages in thread
From: Uwe Kleine-König @ 2024-11-22 18:05 UTC (permalink / raw)
To: Ming Yu
Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
jdelvare, jic23, lars, alexandre.belloni, linux-kernel,
linux-gpio, linux-i2c, linux-can, netdev, linux-watchdog,
linux-hwmon, linux-iio, linux-pwm, linux-rtc
[-- Attachment #1: Type: text/plain, Size: 11410 bytes --]
Hello,
On Thu, Oct 24, 2024 at 04:59:21PM +0800, Ming Yu wrote:
> This driver supports PWM functionality for NCT6694 MFD device
> based on USB interface.
>
> Signed-off-by: Ming Yu <tmyu0@nuvoton.com>
> ---
> MAINTAINERS | 1 +
> drivers/pwm/Kconfig | 10 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-nct6694.c | 245 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 257 insertions(+)
> create mode 100644 drivers/pwm/pwm-nct6694.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5c350eac187d..4d5a5eded3b9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16444,6 +16444,7 @@ F: drivers/iio/adc/nct6694_adc.c
> F: drivers/i2c/busses/i2c-nct6694.c
> F: drivers/mfd/nct6694.c
> F: drivers/net/can/nct6694_canfd.c
> +F: drivers/pwm/pwm-nct6694.c
> F: drivers/watchdog/nct6694_wdt.c
> F: include/linux/mfd/nct6694.h
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 0915c1e7df16..00b5eb13f99d 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -471,6 +471,16 @@ config PWM_NTXEC
> controller found in certain e-book readers designed by the original
> design manufacturer Netronix.
>
> +config PWM_NCT6694
> + tristate "Nuvoton NCT6694 PWM support"
> + depends on MFD_NCT6694
> + help
> + If you say yes to this option, support will be included for Nuvoton
> + NCT6694, a USB device to PWM controller.
> +
> + This driver can also be built as a module. If so, the module
> + will be called pwm-nct6694.
> +
> config PWM_OMAP_DMTIMER
> tristate "OMAP Dual-Mode Timer PWM support"
> depends on OF
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9081e0c0e9e0..5c5602b79402 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -42,6 +42,7 @@ obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o
> obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o
> obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
> obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
> +obj-$(CONFIG_PWM_NCT6694) += pwm-nct6694.o
> obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
> obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
> obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
> diff --git a/drivers/pwm/pwm-nct6694.c b/drivers/pwm/pwm-nct6694.c
> new file mode 100644
> index 000000000000..915a2ab50834
> --- /dev/null
> +++ b/drivers/pwm/pwm-nct6694.c
> @@ -0,0 +1,245 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 PWM driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/pwm.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/nct6694.h>
> +
> +#define DRVNAME "nct6694-pwm"
> +
> +#define NR_PWM 10
> +#define MAX_PERIOD_NS 40000 /* PWM Maximum Frequency = 25kHz */
Please use a prefix for your defines, otherwise they make a more general
impression than justified.
> +#define PERIOD_NS_CONST 10200000 /* Period_ns to Freq_reg */
What is Freq_reg?
> +/* Host interface */
> +#define REQUEST_RPT_MOD 0xFF
> +#define REQUEST_HWMON_MOD 0x00
> +#define REQUEST_PWM_MOD 0x01
> +
> +/* Report Channel */
> +#define HWMON_PWM_IDX(x) (0x70 + (x))
> +
> +/* Message Channel -HWMON */
> +/* Command 00h */
> +#define REQUEST_HWMON_CMD0_LEN 0x40
> +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define HWMON_PWM_EN(x) (0x06 + (x))
> +#define HWMON_PWM_PP(x) (0x08 + (x))
> +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x))
> +
> +/* Message Channel -FAN */
> +/* Command 00h */
> +#define REQUEST_PWM_CMD0_LEN 0x08
> +#define REQUEST_PWM_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */
> +#define PWM_CH_EN(x) (x ? 0x00 : 0x01)
> +/* Command 01h */
> +#define REQUEST_PWM_CMD1_LEN 0x20
> +#define REQUEST_PWM_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */
> +#define PWM_MAL_EN(x) (x ? 0x00 : 0x01)
> +#define PWM_MAL_VAL(x) (0x02 + (x))
> +
> +/*
> + * Frequency <-> Period
> + * (10^9 * 255) / (25000 * Freq_reg) = Period_ns
> + * 10200000 / Freq_reg = Period_ns
> + *
> + * | Freq_reg | Freq_Hz | Period_ns |
> + * | 1 (01h | 98.039 | 10200000 |
missing )
> + * | 2 (02h) | 196.078 | 5100000 |
> + * | 3 (03h) | 294.117 | 3400000 |
> + * | ... |
> + * | ... |
> + * | ... |
Better use spaces for indention here.
> + * | 253 (FDh)| 24803.9 | 40316.20 |
> + * | 254 (FEh)| 24901.9 | 40157.48 |
> + * | 255 (FFh)| 25000 | 40000 |
Is this table useful?
I'd just write:
The emitted period P depends on the value F configured in
the freq register:
P = 255 s / (25000 * F)
= 10200000 ns / F
I wonder what happens if F == 0?
> + */
> +
> +struct nct6694_pwm_data {
> + struct nct6694 *nct6694;
> + unsigned char hwmon_cmd0_buf[REQUEST_HWMON_CMD0_LEN];
> + unsigned char pwm_cmd0_buf[REQUEST_PWM_CMD0_LEN];
> + unsigned char pwm_cmd1_buf[REQUEST_PWM_CMD1_LEN];
What are these arrays? register caches?
> +};
> +
> +static inline struct nct6694_pwm_data *to_nct6694_pwm_data(struct pwm_chip *chip)
> +{
> + return pwmchip_get_drvdata(chip);
> +}
> +
> +static int nct6694_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
> + unsigned char ch_enable = data->pwm_cmd0_buf[PWM_CH_EN(pwm->hwpwm / 8)];
> + unsigned char mal_enable = data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)];
> + bool ch_en = ch_enable & BIT(pwm->hwpwm % 8);
> + bool mal_en = mal_enable & BIT(pwm->hwpwm % 8);
What is "mal"?
> +
> + if (!(ch_en && mal_en)) {
> + pr_err("%s: PWM(%d) is running in other mode!\n",
> + __func__, pwm->hwpwm);
> + return -EINVAL;
> + }
No error messages after .probe() please. dev_dbg() is fine however.
> + return 0;
> +}
> +
> +static int nct6694_pwm_get_state(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + struct pwm_state *state)
> +{
> + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
> + unsigned char freq_reg, duty;
> +
> + /* Get pwm device initial state */
> + state->enabled = true;
> +
> + freq_reg = data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)];
> + state->period = PERIOD_NS_CONST / freq_reg;
I doubt you extensively tested your driver with PWM_DEBUG enabled. Hint:
You should probably use DIV_ROUND_UP here.
> + duty = data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)];
> + state->duty_cycle = duty * state->period / 0xFF;
> +
> + return 0;
> +}
> +
> +static int nct6694_pwm_apply(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_state *state)
> +{
> + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip);
> + unsigned char freq_reg, duty;
> + int ret;
> +
> + if (state->period < MAX_PERIOD_NS)
> + return -EINVAL;
> +
> + /* (10^9 * 255) / (25000 * Freq_reg) = Period_ns */
This could be less irritating if the formula included units. See above.
> + freq_reg = (unsigned char)(PERIOD_NS_CONST / state->period);
No need for the cast.
If state->period is bigger than PERIOD_NS_CONST, freq_reg ends up being
zero. That's related to the question above about F == 0.
> + data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)] = freq_reg;
> + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN,
> + data->hwmon_cmd0_buf);
> + if (ret)
> + return -EIO;
return ret;?
> +
> + /* Duty = duty * 0xFF */
I don't understand that.
> + duty = (unsigned char)(state->duty_cycle * 0xFF / state->period);
Please use the actual period implemented and not state->period.
> + data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)] = duty;
> + if (state->enabled)
> + data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] |= BIT(pwm->hwpwm % 8);
> + else
> + data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] &= ~BIT(pwm->hwpwm % 8);
s/ / /
> + ret = nct6694_write_msg(data->nct6694, REQUEST_PWM_MOD,
> + REQUEST_PWM_CMD1_OFFSET, REQUEST_PWM_CMD1_LEN,
> + data->pwm_cmd1_buf);
> + if (ret)
> + return -EIO;
return ret;
> + return 0;
> +}
> +
> +static const struct pwm_ops nct6694_pwm_ops = {
> + .request = nct6694_pwm_request,
> + .apply = nct6694_pwm_apply,
> + .get_state = nct6694_pwm_get_state,
> +};
> +
> +static int nct6694_pwm_init(struct nct6694_pwm_data *data)
> +{
> + struct nct6694 *nct6694 = data->nct6694;
> + int ret;
> +
> + ret = nct6694_read_msg(nct6694, REQUEST_HWMON_MOD,
> + REQUEST_HWMON_CMD0_OFFSET,
> + REQUEST_HWMON_CMD0_LEN, 0,
> + REQUEST_HWMON_CMD0_LEN,
> + data->hwmon_cmd0_buf);
> + if (ret)
> + return ret;
> +
> + ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD,
> + REQUEST_PWM_CMD0_OFFSET,
> + REQUEST_PWM_CMD0_LEN, 0,
> + REQUEST_PWM_CMD0_LEN,
> + data->pwm_cmd0_buf);
> + if (ret)
> + return ret;
> +
> + ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD,
> + REQUEST_PWM_CMD1_OFFSET,
> + REQUEST_PWM_CMD1_LEN, 0,
> + REQUEST_PWM_CMD1_LEN,
> + data->pwm_cmd1_buf);
> + return ret;
> +}
> +
> +static int nct6694_pwm_probe(struct platform_device *pdev)
> +{
> + struct pwm_chip *chip;
> + struct nct6694_pwm_data *data;
> + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> + int ret;
> +
> + chip = devm_pwmchip_alloc(&pdev->dev, NR_PWM, sizeof(*data));
> + if (IS_ERR(chip))
> + return PTR_ERR(chip);
> +
> + data = to_nct6694_pwm_data(chip);
> +
> + data->nct6694 = nct6694;
> + chip->ops = &nct6694_pwm_ops;
> +
> + ret = nct6694_pwm_init(data);
> + if (ret)
> + return -EIO;
return dev_err_probe(dev, ret, "....\n");
> + /* Register pwm device to PWM framework */
> + ret = devm_pwmchip_add(&pdev->dev, chip);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register pwm device!\n");
> + return ret;
> + }
Please use dev_err_probe().
> +
> + return 0;
> +}
> +
> +static struct platform_driver nct6694_pwm_driver = {
> + .driver = {
> + .name = DRVNAME,
DRVNAME is only used once (and a too generic name). Please just put the
string here directly.
> + },
> + .probe = nct6694_pwm_probe,
> +};
I don't like your aligning choices. Why do you intend the = for .probe
but not for .driver? My preferred style is a single space before the =.
While aligning the = is a subjective choice, a relevant downside is that
if later a longer member should get initialized you have to realign all
the otherwise unaffected lines to restore the alignment.
> +static int __init nct6694_init(void)
> +{
> + int err;
> +
> + err = platform_driver_register(&nct6694_pwm_driver);
> + if (!err) {
> + if (err)
Huh?
> + platform_driver_unregister(&nct6694_pwm_driver);
> + }
> +
> + return err;
> +}
> +subsys_initcall(nct6694_init);
Do you really need this to be at subsys init time?
> +static void __exit nct6694_exit(void)
> +{
> + platform_driver_unregister(&nct6694_pwm_driver);
> +}
> +module_exit(nct6694_exit);
> +
> +MODULE_DESCRIPTION("USB-PWM driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 80+ messages in thread
end of thread, other threads:[~2024-11-22 18:05 UTC | newest]
Thread overview: 80+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-24 8:59 [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Ming Yu
2024-10-24 8:59 ` [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Ming Yu
2024-10-24 9:03 ` Marc Kleine-Budde
2024-10-25 8:00 ` Ming Yu
2024-10-24 9:57 ` Marc Kleine-Budde
2024-10-25 8:02 ` Ming Yu
2024-10-24 15:20 ` Marc Kleine-Budde
2024-10-24 15:34 ` Marc Kleine-Budde
2024-10-25 8:14 ` Ming Yu
2024-10-25 8:35 ` Marc Kleine-Budde
2024-10-25 9:02 ` Marc Kleine-Budde
2024-10-25 10:22 ` Ming Yu
2024-10-25 8:08 ` Ming Yu
2024-10-25 10:08 ` Marc Kleine-Budde
2024-10-25 11:03 ` Ming Yu
2024-10-25 12:23 ` Marc Kleine-Budde
2024-10-28 7:33 ` Ming Yu
2024-10-28 7:52 ` Marc Kleine-Budde
2024-10-28 8:31 ` Ming Yu
2024-10-28 14:06 ` Marc Kleine-Budde
2024-10-29 3:45 ` Ming Yu
2024-10-29 8:14 ` Marc Kleine-Budde
2024-11-01 5:35 ` Ming Yu
2024-10-26 14:58 ` Christophe JAILLET
2024-10-28 7:37 ` Ming Yu
2024-10-24 8:59 ` [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support Ming Yu
2024-10-24 9:47 ` Bartosz Golaszewski
[not found] ` <CAOoeyxUUOCSaDLK8=ox3hwDVu=Ej-ds4=FsS8F+9GfiE-8HYvg@mail.gmail.com>
2024-10-25 7:12 ` Bartosz Golaszewski
2024-10-25 7:38 ` 游子民
2024-10-25 7:46 ` Bartosz Golaszewski
2024-10-28 8:56 ` Ming Yu
2024-10-30 19:32 ` Bartosz Golaszewski
2024-11-01 6:15 ` Ming Yu
2024-10-24 8:59 ` [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support Ming Yu
2024-10-24 10:41 ` Andi Shyti
2024-10-25 7:47 ` 游子民
2024-10-24 8:59 ` [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Ming Yu
2024-10-24 12:12 ` Marc Kleine-Budde
2024-10-24 15:28 ` Marc Kleine-Budde
2024-11-01 5:32 ` Ming Yu
2024-10-24 14:17 ` Marc Kleine-Budde
2024-10-24 14:20 ` Marc Kleine-Budde
2024-11-01 1:44 ` Ming Yu
2024-11-01 1:37 ` Ming Yu
2024-10-25 11:18 ` kernel test robot
2024-10-25 23:31 ` kernel test robot
2024-10-24 8:59 ` [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Ming Yu
2024-10-24 15:32 ` Guenter Roeck
2024-10-28 9:49 ` Ming Yu
2024-10-24 16:06 ` Guenter Roeck
2024-10-26 9:19 ` kernel test robot
2024-10-24 8:59 ` [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Ming Yu
2024-10-24 9:20 ` Kalesh Anakkur Purayil
2024-10-24 14:53 ` Guenter Roeck
2024-10-25 15:22 ` Ming Yu
2024-10-25 15:44 ` Guenter Roeck
2024-10-26 14:50 ` Guenter Roeck
2024-10-28 7:58 ` Ming Yu
2024-10-28 18:54 ` Jonathan Cameron
2024-10-30 3:29 ` Ming Yu
2024-10-30 4:26 ` Guenter Roeck
2024-11-01 6:11 ` Ming Yu
2024-10-28 7:42 ` Ming Yu
2024-10-25 15:10 ` Ming Yu
2024-10-24 15:03 ` Guenter Roeck
2024-10-25 15:33 ` Ming Yu
2024-10-24 8:59 ` [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support Ming Yu
2024-10-26 14:41 ` Jonathan Cameron
2024-11-05 6:21 ` Ming Yu
2024-10-24 8:59 ` [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support Ming Yu
2024-11-22 18:05 ` Uwe Kleine-König
2024-10-24 8:59 ` [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support Ming Yu
2024-10-25 23:34 ` Nobuhiro Iwamatsu
2024-10-28 8:42 ` Ming Yu
2024-10-24 11:57 ` [PATCH v1 0/9] Add Nuvoton NCT6694 MFD devices Marc Kleine-Budde
2024-10-25 8:22 ` Ming Yu
2024-10-25 8:33 ` Marc Kleine-Budde
2024-10-30 8:30 ` Ming Yu
2024-10-30 10:12 ` Marc Kleine-Budde
2024-10-30 14:21 ` Ming Yu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).