From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pf1-f177.google.com (mail-pf1-f177.google.com [209.85.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E90FD284B4F for ; Fri, 12 Sep 2025 09:20:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668823; cv=none; b=IkRSKCfWvcz25Ahjtb03fRNOlV52+zO2tlX3QU5LHyRQ9N0JeWBK2rum/JQa9h2+HTu8YHhxDkS69mckQ8vXV/Jaz3OI1l0KTocbPamswKBp3xqTk1N54QIlG/wHdKukpBUI4IW4Ig6AxUaYhnMa7/caoGo9TU021rSJRQWjZvc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668823; c=relaxed/simple; bh=dgui9ocAeQl1PNBq2/0l7IXqINwZNCVFSRgvIQF8sCg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DTl95Q6XZsZ9Do7ziWS95SrJlWEsscGh9VqdWWxnTVCXmkQotZcj4W2QBf/J6YTuFuFeFnYh8HFQe5pJtt6pFDRG/GRnAz5cKxSDhM4Bj7Nit/gJRWlgdP1m8GklcX3vG8fMwPqaHPJabVQFA0U2eZp1S40bly+YWMpzgkNpR0Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ckUyL+GF; arc=none smtp.client-ip=209.85.210.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ckUyL+GF" Received: by mail-pf1-f177.google.com with SMTP id d2e1a72fcca58-7728815e639so1328475b3a.1 for ; Fri, 12 Sep 2025 02:20:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668818; x=1758273618; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jq5VYuAITOtXktN+f5HwZ1WMHG5yiGZ+4hzOVfqoO3U=; b=ckUyL+GFl2gony2YxF3R7+a6BOOm2iy675UR1oSZniqt9xKERkmqArkD72zu3donh9 4XJZ8F0nWUAXJT2rhCVlxJQLWsyOPxPCOaMZEIIIAWKiRxxls5bqv1XQagxhXvf1WHpn +2uxvsQ20XD+jtUh8ACgt3wkop3gjdKxbKpHxHv1C6xI76XQXEPFlAonwq6810eSnux7 IRtDlLLj+e1aLqOQSmUMGknn+J0zXi70OssD9EPxZf97QYvM9ZvowJ0HUvny0XSz69By 5tBiN9KlsXxexVlID0e2Ljvqmh8bP5k/cPc68MummuKTcSYrTqSmistlmcCDcWIJ1OH+ xwHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668818; x=1758273618; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jq5VYuAITOtXktN+f5HwZ1WMHG5yiGZ+4hzOVfqoO3U=; b=kqahQIwQOEhBrOPxW8N2SpME6U7qvdIn6S4BFtJAEou7G+/wGsph67pwK5GeDvSQtM reABZe+hKndhl37gRpQ6iMHYq16x5zqrcB0Sgi8Cmr2bvjZAjHxm4yXnOEd0LYKgcVX+ jM0EHKtCz5EhCitjjsNMw5gezPgJoE7Rc25q5T0sVnoZOAi4YCuPH1QmN5aPBZ2r97JJ yftGBc1JyxRnLZr4/VE3DXnzr5URk+kehy16B+DEx4J1NmJJe49nmpdbrerJJC63yqfZ 7P4L73phTyCoNOBhgLZ3MRerJi0veKwGyQXqGmVm/S1hBC6h/RyuvvqR6qnup7d7du5O oM/w== X-Gm-Message-State: AOJu0YzNnD5rpJ70au+I+hiUIaBEvnxCdc62eGUoKxCQqf5pz8rryX4h 6TJTuTEODPUyu8SGjS96cunoJ2zIIa/WFBzIrEzogpq5WQBu+QHCYtEL X-Gm-Gg: ASbGncvhFHI9Yu/Tl5IbserZYjt/4FAsHgfwpMUtRmMbMLVakTaMA6VO5qEmiFRSttq eQBgGNx4H1H39FD0LPJ1qvIWrt1MM5sspoZJmUgxQA2okVwjOQvUKjnOQWilbuf16hCr00LzAKl 1s5IARGT/eaix6WTf9rN0UfsZK8/wvBj0wFEx/msQGuBY5w27JWWvVtti0+0UWdJPJYPDTlLw8z ABVFYWzVeQ92zlEgLH1I4eDhWTrWnvqChBzSEIElhDJw2y/7tmrYIRAFERQT5IbRkt3TpDYVWQs PTyzMIfO3Sl3QBQyEiiNzfLq7AF+cGOffAV9zVOWs/fSkn29x7eHuI1bcNp6+ZsU0giYAnevUUq sudlTFKjkJbXMkjAp1XlwpJsXcDN2NTUjcH8CmNwRq76sCOCP6kyns1CBIGaOEGqATw== X-Google-Smtp-Source: AGHT+IHLhOCDaGvlnpKvroeQKTgKbKcYyC8+hFKkE9C1thsroVKwZhVQQRQ2v2WnSpQJLu+giGedyg== X-Received: by 2002:a05:6a20:430d:b0:250:f80d:b334 with SMTP id adf61e73a8af0-26027c13362mr3127083637.0.1757668818116; Fri, 12 Sep 2025 02:20:18 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:17 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu Subject: [PATCH RESEND v14 1/7] mfd: Add core driver for Nuvoton NCT6694 Date: Fri, 12 Sep 2025 17:19:46 +0800 Message-Id: <20250912091952.1169369-2-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu The Nuvoton NCT6694 provides an USB interface to the host to access its features. Sub-devices can use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue a command. They can also request interrupt that will be called when the USB device receives its interrupt pipe. Signed-off-by: Ming Yu --- Changes since version 13: - Update to guard(spinlock_irqsave)() in nct6694.c Changes since version 12: - Implement IDA in MFD driver to handle per-device IDs - Use spinlock to replace irq mutex lock - Use same email address in the signature Changes since version 11: - Modify the irq_domain_add_simple() to irq_domain_create_simple() - Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA in sub-drivers Changes since version 10: - Add change log for the patch - Fix mfd_cell to MFD_CELL_NAME() - Remove unnecessary blank line Changes since version 9: - Add KernelDoc to exported functions Changes since version 8: - Modify the signed-off-by with my work address - Rename all MFD cell names to "nct6694-xxx" - Fix some comments in nct6694.c and in nct6694.h Changes since version 7: - Add error handling for devm_mutex_init() Changes since version 6: Changes since version 5: - Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC() - Drop unnecessary macros Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Add header and use BIT macro - Modify mutex_init() to devm_mutex_init() Changes since version 2: Changes since version 1: - Implement IRQ domain to handle IRQ demux - Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API - Add command structure - Fix USB functions - Sort each driver's header files alphabetically MAINTAINERS | 6 + drivers/mfd/Kconfig | 15 ++ drivers/mfd/Makefile | 2 + drivers/mfd/nct6694.c | 388 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/nct6694.h | 102 ++++++++++ 5 files changed, 513 insertions(+) create mode 100644 drivers/mfd/nct6694.c create mode 100644 include/linux/mfd/nct6694.h diff --git a/MAINTAINERS b/MAINTAINERS index fe168477caa4..a8a05872d077 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18082,6 +18082,12 @@ F: drivers/nubus/ F: include/linux/nubus.h F: include/uapi/linux/nubus.h =20 +NUVOTON NCT6694 MFD DRIVER +M: Ming Yu +S: Supported +F: drivers/mfd/nct6694.c +F: include/linux/mfd/nct6694.h + NUVOTON NCT7201 IIO DRIVER M: Eason Yang L: linux-iio@vger.kernel.org diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 425c5fba6cb1..f3d157776e93 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1134,6 +1134,21 @@ config MFD_MENF21BMC This driver can also be built as a module. If so the module will be called menf21bmc. =20 +config MFD_NCT6694 + tristate "Nuvoton NCT6694 support" + select MFD_CORE + depends on USB + help + This enables support for the Nuvoton USB device NCT6694, which shares + peripherals. + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, + PWM, and RTC. + This driver provides core APIs to access the NCT6694 hardware + monitoring and control features. + Additional drivers must be enabled to utilize the specific + functionalities of the device. + config MFD_OCELOT tristate "Microsemi Ocelot External Control Support" depends on SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f7bdedd5a66d..1e7738c02b2c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -121,6 +121,8 @@ obj-$(CONFIG_MFD_MC13XXX) +=3D mc13xxx-core.o obj-$(CONFIG_MFD_MC13XXX_SPI) +=3D mc13xxx-spi.o obj-$(CONFIG_MFD_MC13XXX_I2C) +=3D mc13xxx-i2c.o =20 +obj-$(CONFIG_MFD_NCT6694) +=3D nct6694.o + obj-$(CONFIG_MFD_CORE) +=3D mfd-core.o =20 ocelot-soc-objs :=3D ocelot-core.o ocelot-spi.o diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c new file mode 100644 index 000000000000..308b2fda3055 --- /dev/null +++ b/drivers/mfd/nct6694.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Nuvoton Technology Corp. + * + * Nuvoton NCT6694 core driver using USB interface to provide + * access to the NCT6694 hardware monitoring and control features. + * + * The NCT6694 is an integrated controller that provides GPIO, I2C, + * CAN, WDT, HWMON and RTC management. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell nct6694_devs[] =3D { + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + + MFD_CELL_NAME("nct6694-canfd"), + MFD_CELL_NAME("nct6694-canfd"), + + MFD_CELL_NAME("nct6694-wdt"), + MFD_CELL_NAME("nct6694-wdt"), + + MFD_CELL_NAME("nct6694-hwmon"), + + MFD_CELL_NAME("nct6694-rtc"), +}; + +static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned= char err_status) +{ + switch (err_status) { + case NCT6694_NO_ERROR: + return 0; + case NCT6694_NOT_SUPPORT_ERROR: + dev_err(nct6694->dev, "Command is not supported!\n"); + break; + case NCT6694_NO_RESPONSE_ERROR: + dev_warn(nct6694->dev, "Command received no response!\n"); + break; + case NCT6694_TIMEOUT_ERROR: + dev_warn(nct6694->dev, "Command timed out!\n"); + break; + case NCT6694_PENDING: + dev_err(nct6694->dev, "Command is pending!\n"); + break; + default: + return -EINVAL; + } + + return -EIO; +} + +/** + * nct6694_read_msg() - Read message from NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer to store the response data + * + * Sends a command to the NCT6694 device and reads the response. + * The command header is specified in @cmd_hd, and the response + * data is stored in @buf. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_hea= der *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg =3D nct6694->usb_msg; + struct usb_device *udev =3D nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl =3D NCT6694_HCTRL_GET; + + /* Send command packet to USB device */ + ret =3D usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &m= sg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret =3D usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &ms= g->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret =3D usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len !=3D le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected received length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_read_msg); + +/** + * nct6694_write_msg() - Write message to NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer containing the data to be sent + * + * Sends a command to the NCT6694 device and writes the data + * from @buf. The command header is specified in @cmd_hd. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_he= ader *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg =3D nct6694->usb_msg; + struct usb_device *udev =3D nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl =3D NCT6694_HCTRL_SET; + + /* Send command packet to USB device */ + ret =3D usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &m= sg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Send data packet to USB device */ + ret =3D usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), bu= f, + le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret =3D usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &ms= g->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret =3D usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len !=3D le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_write_msg); + +static void usb_int_callback(struct urb *urb) +{ + struct nct6694 *nct6694 =3D urb->context; + __le32 *status_le =3D urb->transfer_buffer; + u32 int_status; + int ret; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto resubmit; + } + + int_status =3D le32_to_cpu(*status_le); + + while (int_status) { + int irq =3D __ffs(int_status); + + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); + int_status &=3D ~BIT(irq); + } + +resubmit: + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(re= t)); +} + +static void nct6694_irq_enable(struct irq_data *data) +{ + struct nct6694 *nct6694 =3D irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq =3D irqd_to_hwirq(data); + + guard(spinlock_irqsave)(&nct6694->irq_lock); + + nct6694->irq_enable |=3D BIT(hwirq); +} + +static void nct6694_irq_disable(struct irq_data *data) +{ + struct nct6694 *nct6694 =3D irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq =3D irqd_to_hwirq(data); + + guard(spinlock_irqsave)(&nct6694->irq_lock); + + nct6694->irq_enable &=3D ~BIT(hwirq); +} + +static const struct irq_chip nct6694_irq_chip =3D { + .name =3D "nct6694-irq", + .flags =3D IRQCHIP_SKIP_SET_WAKE, + .irq_enable =3D nct6694_irq_enable, + .irq_disable =3D nct6694_irq_disable, +}; + +static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, = irq_hw_number_t hw) +{ + struct nct6694 *nct6694 =3D d->host_data; + + irq_set_chip_data(irq, nct6694); + irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq); + + return 0; +} + +static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int ir= q) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops nct6694_irq_domain_ops =3D { + .map =3D nct6694_irq_domain_map, + .unmap =3D nct6694_irq_domain_unmap, +}; + +static int nct6694_usb_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev =3D interface_to_usbdev(iface); + struct usb_endpoint_descriptor *int_endpoint; + struct usb_host_interface *interface; + struct device *dev =3D &iface->dev; + struct nct6694 *nct6694; + int ret; + + nct6694 =3D devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL); + if (!nct6694) + return -ENOMEM; + + nct6694->usb_msg =3D devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP= _KERNEL); + if (!nct6694->usb_msg) + return -ENOMEM; + + nct6694->int_buffer =3D devm_kzalloc(dev, sizeof(*nct6694->int_buffer), G= FP_KERNEL); + if (!nct6694->int_buffer) + return -ENOMEM; + + nct6694->int_in_urb =3D usb_alloc_urb(0, GFP_KERNEL); + if (!nct6694->int_in_urb) + return -ENOMEM; + + nct6694->domain =3D irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0, + &nct6694_irq_domain_ops, + nct6694); + if (!nct6694->domain) { + ret =3D -ENODEV; + goto err_urb; + } + + nct6694->dev =3D dev; + nct6694->udev =3D udev; + + ida_init(&nct6694->gpio_ida); + ida_init(&nct6694->i2c_ida); + ida_init(&nct6694->canfd_ida); + ida_init(&nct6694->wdt_ida); + + spin_lock_init(&nct6694->irq_lock); + + ret =3D devm_mutex_init(dev, &nct6694->access_lock); + if (ret) + goto err_ida; + + interface =3D iface->cur_altsetting; + + int_endpoint =3D &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(int_endpoint)) { + ret =3D -ENODEV; + goto err_ida; + } + + usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_= INT_IN_EP), + nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback, + nct6694, int_endpoint->bInterval); + + ret =3D usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL); + if (ret) + goto err_ida; + + usb_set_intfdata(iface, nct6694); + + ret =3D mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_dev= s)); + if (ret) + goto err_mfd; + + return 0; + +err_mfd: + usb_kill_urb(nct6694->int_in_urb); +err_ida: + ida_destroy(&nct6694->wdt_ida); + ida_destroy(&nct6694->canfd_ida); + ida_destroy(&nct6694->i2c_ida); + ida_destroy(&nct6694->gpio_ida); + irq_domain_remove(nct6694->domain); +err_urb: + usb_free_urb(nct6694->int_in_urb); + return ret; +} + +static void nct6694_usb_disconnect(struct usb_interface *iface) +{ + struct nct6694 *nct6694 =3D usb_get_intfdata(iface); + + mfd_remove_devices(nct6694->dev); + usb_kill_urb(nct6694->int_in_urb); + ida_destroy(&nct6694->wdt_ida); + ida_destroy(&nct6694->canfd_ida); + ida_destroy(&nct6694->i2c_ida); + ida_destroy(&nct6694->gpio_ida); + irq_domain_remove(nct6694->domain); + usb_free_urb(nct6694->int_in_urb); +} + +static const struct usb_device_id nct6694_ids[] =3D { + { USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0x= FF, 0x00, 0x00) }, + { } +}; +MODULE_DEVICE_TABLE(usb, nct6694_ids); + +static struct usb_driver nct6694_usb_driver =3D { + .name =3D "nct6694", + .id_table =3D nct6694_ids, + .probe =3D nct6694_usb_probe, + .disconnect =3D nct6694_usb_disconnect, +}; +module_usb_driver(nct6694_usb_driver); + +MODULE_DESCRIPTION("Nuvoton NCT6694 core driver"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h new file mode 100644 index 000000000000..6eb9be2cd4a0 --- /dev/null +++ b/include/linux/mfd/nct6694.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Nuvoton Technology Corp. + * + * Nuvoton NCT6694 USB transaction and data structure. + */ + +#ifndef __MFD_NCT6694_H +#define __MFD_NCT6694_H + +#define NCT6694_VENDOR_ID 0x0416 +#define NCT6694_PRODUCT_ID 0x200B +#define NCT6694_INT_IN_EP 0x81 +#define NCT6694_BULK_IN_EP 0x02 +#define NCT6694_BULK_OUT_EP 0x03 + +#define NCT6694_HCTRL_SET 0x40 +#define NCT6694_HCTRL_GET 0x80 + +#define NCT6694_URB_TIMEOUT 1000 + +enum nct6694_irq_id { + NCT6694_IRQ_GPIO0 =3D 0, + NCT6694_IRQ_GPIO1, + NCT6694_IRQ_GPIO2, + NCT6694_IRQ_GPIO3, + NCT6694_IRQ_GPIO4, + NCT6694_IRQ_GPIO5, + NCT6694_IRQ_GPIO6, + NCT6694_IRQ_GPIO7, + NCT6694_IRQ_GPIO8, + NCT6694_IRQ_GPIO9, + NCT6694_IRQ_GPIOA, + NCT6694_IRQ_GPIOB, + NCT6694_IRQ_GPIOC, + NCT6694_IRQ_GPIOD, + NCT6694_IRQ_GPIOE, + NCT6694_IRQ_GPIOF, + NCT6694_IRQ_CAN0, + NCT6694_IRQ_CAN1, + NCT6694_IRQ_RTC, + NCT6694_NR_IRQS, +}; + +enum nct6694_response_err_status { + NCT6694_NO_ERROR =3D 0, + NCT6694_FORMAT_ERROR, + NCT6694_RESERVED1, + NCT6694_RESERVED2, + NCT6694_NOT_SUPPORT_ERROR, + NCT6694_NO_RESPONSE_ERROR, + NCT6694_TIMEOUT_ERROR, + NCT6694_PENDING, +}; + +struct __packed nct6694_cmd_header { + u8 rsv1; + u8 mod; + union __packed { + __le16 offset; + struct __packed { + u8 cmd; + u8 sel; + }; + }; + u8 hctrl; + u8 rsv2; + __le16 len; +}; + +struct __packed nct6694_response_header { + u8 sequence_id; + u8 sts; + u8 reserved[4]; + __le16 len; +}; + +union __packed nct6694_usb_msg { + struct nct6694_cmd_header cmd_header; + struct nct6694_response_header response_header; +}; + +struct nct6694 { + struct device *dev; + struct ida gpio_ida; + struct ida i2c_ida; + struct ida canfd_ida; + struct ida wdt_ida; + struct irq_domain *domain; + struct mutex access_lock; + spinlock_t irq_lock; + struct urb *int_in_urb; + struct usb_device *udev; + union nct6694_usb_msg *usb_msg; + __le32 *int_buffer; + unsigned int irq_enable; +}; + +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_hea= der *cmd_hd, void *buf); +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_he= ader *cmd_hd, void *buf); + +#endif --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 83B11287272 for ; Fri, 12 Sep 2025 09:20:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668826; cv=none; b=iQkrIrtJHm4MPP86u/QTEgOmKGkstrKHsT+r0Rqo7fuxxfMjULlujEO77Mb6r4xWVGiw2oc7+dJhHHjQhTb74C9y7PNhSioaD72iXtmRPfbJjBylNbGUaT7g29DteDESRSkmiOjsD0OK5bLpG7NUWRxM3iYqf3SUnDWg0HqnyYA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668826; c=relaxed/simple; bh=CckzT68ji8iJcxev/VN/fqX2xXdc8LI2v8eTYDaStYU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=YvS3bahCzLevM7fgWPIBWZxyn6BLvUtXU6RYQhawchQs35A4AZ70GodUFaSnAImt88MxfzGwfdiwxATfIw14rU1XuMJDa3gsms7/e4Y4am+ICnFj+qSmlWIzZtfJ1QBsb+2lSc6H4/IpWuObRWu7T3FKgUIEhulEK2FQC2QfxTY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=bveI+qBr; arc=none smtp.client-ip=209.85.215.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bveI+qBr" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-b4fb8d3a2dbso1225522a12.3 for ; Fri, 12 Sep 2025 02:20:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668823; x=1758273623; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=U/mgjzituTGi3jWBsHbGLx5LSmIruJB82To9hi5Vl3E=; b=bveI+qBr2y3jH75k6ly2ExAAGQR+9hPTETBa/ayZ1ST93F3KkbmHoKWTUr29VfDXZi sw3gRIuo0rG9pp12NfyD7gFe4PfHsCkxy53+Ye4YEJP0tdFNefY4MAZdpaZLQu0YLQcX ieKNHXeHiw16rw9XHDgASNPuwbYMU3u3Mt5NI4lc5LnjbTLtwc18mwrYTHS5+Vxgzj96 IuEyWtmxiZmjZZzT1AHf1EwcK/i74cywyDvPGpXSJpiPCEvpQErCP+F6yDRBiPG+Nhyz 5ZoBya3Ct2FZbVly/s+hJg71ph70ZjCgSMMK6ofEZho6p9LCp5aJ/tTDuaFWl5RcPOkt T8Cg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668823; x=1758273623; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=U/mgjzituTGi3jWBsHbGLx5LSmIruJB82To9hi5Vl3E=; b=TdyJK5vvl4oLxsHF9Sydx69qiy4cXUzuyMTrkzbBHnaPtdCEWrchFBb4Cti+KhIyo9 J4Q+rBwkZAg1tFaFnswx4dGH6NCu54anGN2wfx16OxtbWHy6HZ7Nq4RziWCAic0OIDsL QoKyq/J/a2lmR6YVo2QWKZ4pAnJcMzWX9JMcpFI19ijiZNUxOU2JfgOQX1L1LLgCq6Av 18xZWTZPAP6DaGPb4WjfKD5JFfYjm/J6VsM4hnDoY3Y9RDXghvBYGTvNWjpAigqs5cyX Ez9ml13wK9QNVoONVUA7elPBRATQIwBSm49SRJ1MuCBkqRH5kowyG0JgeTa5GhKeDe8z yGZw== X-Gm-Message-State: AOJu0YyrcN7TMpioE8UDg4IdYxrYjbhuPCnIIokVRchl2nYndFvDvHK6 TkAoGKlSJRpPwKTQOf0chElkl6zXQks/+yVU1e3Ob1qJvZWW5Ryk64VB X-Gm-Gg: ASbGncsf9VD8IzNvtJb+7sUfzUKoj8Eejwl3hfTrlwsIUs/C9d48wmljEy75yUgpuSX K52Rx4MNpysphHencoVTvX7ZTSQO498ol4RFvNbL6n9a3oxc6gALcoW1ZNKcWBG/7sC34zAs5y2 NBvvBWg7BTNkaZiz6GOQmratMX7E/g2zMPxoZhSzxXZeWojoWOQkwnSc1grZvqw1pGTrL6TKVKn xUCxz0keTLGWZRR6IzvnI6sohhLSh7ZStm5vil9lCEcHiuSL3h8Qu+ew17ajhEV1I8/JR8inneA 0P7oeLAFMtHFPA+FQbnscf21ZGF5E7KfQlg+IL/OyLPz7nrHm5zg5TLKmuxSodZTWTz3MdzZfGx 7IkWiqoavYLk3azJLMJjfvwYG5TxuXo/8o/7xhyZzxHesVu39wkoJniFXbr1L+Oy8euSB3tzvRp D8 X-Google-Smtp-Source: AGHT+IF0Mbitz2j76VXsFK/0/D0DDeTBSK589qvYf7MixKJ3WR3lfIFwbA6xkHFzUNeEUa71uwx76Q== X-Received: by 2002:a17:90b:164a:b0:32d:ea1c:a4fb with SMTP id 98e67ed59e1d1-32dea1ca81amr1597893a91.24.1757668822291; Fri, 12 Sep 2025 02:20:22 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:22 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu , Bartosz Golaszewski Subject: [PATCH RESEND v14 2/7] gpio: Add Nuvoton NCT6694 GPIO support Date: Fri, 12 Sep 2025 17:19:47 +0800 Message-Id: <20250912091952.1169369-3-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports GPIO and IRQ functionality for NCT6694 MFD device based on USB interface. Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Signed-off-by: Ming Yu --- Changes since version 13: Changes since version 12: - Implement IDA in MFD driver to handle per-device IDs - Use same email address in the signature Changes since version 11: - Use platform_device's id to replace IDA Changes since version 10: - Implement IDA to allocate id Changes since version 9: - Add devm_add_action_or_reset() to dispose irq mapping Changes since version 8: - Modify the signed-off-by with my work address - Add irq_dispose_mapping() in the error handling path and in the remove function Changes since version 7: - Add error handling for devm_mutex_init() Changes since version 6: Changes since version 5: - Modify the module name and the driver name consistently Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Add header and use BIT macro - Modify mutex_init() to devm_mutex_init() Changes since version 2: - Add MODULE_ALIAS() - Modify gpio line names be a local variable in gpio-nct6694.c - Drop unnecessary platform_get_drvdata() in gpio-nct6694.c Changes since version 1: - Add each driver's command structure - Fix platform driver registration - Drop unnecessary header - Add gpio line names MAINTAINERS | 1 + drivers/gpio/Kconfig | 12 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-nct6694.c | 499 ++++++++++++++++++++++++++++++++++++ 4 files changed, 513 insertions(+) create mode 100644 drivers/gpio/gpio-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index a8a05872d077..e340d1934394 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18085,6 +18085,7 @@ F: include/uapi/linux/nubus.h NUVOTON NCT6694 MFD DRIVER M: Ming Yu S: Supported +F: drivers/gpio/gpio-nct6694.c F: drivers/mfd/nct6694.c F: include/linux/mfd/nct6694.h =20 diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e43abb322fa6..1e0b1f5190a1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1522,6 +1522,18 @@ config GPIO_MAX77759 This driver can also be built as a module. If so, the module will be called gpio-max77759. =20 +config GPIO_NCT6694 + tristate "Nuvoton NCT6694 GPIO controller support" + depends on MFD_NCT6694 + select GENERIC_IRQ_CHIP + select GPIOLIB_IRQCHIP + help + This driver supports 8 GPIO pins per bank that can all be interrupt + sources. + + This driver can also be built as a module. If so, the module will be + called gpio-nct6694. + config GPIO_PALMAS tristate "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 379f55e9ed1e..f3e837fccdd2 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_GPIO_MT7621) +=3D gpio-mt7621.o obj-$(CONFIG_GPIO_MVEBU) +=3D gpio-mvebu.o obj-$(CONFIG_GPIO_MXC) +=3D gpio-mxc.o obj-$(CONFIG_GPIO_MXS) +=3D gpio-mxs.o +obj-$(CONFIG_GPIO_NCT6694) +=3D gpio-nct6694.o obj-$(CONFIG_GPIO_NOMADIK) +=3D gpio-nomadik.o obj-$(CONFIG_GPIO_NPCM_SGPIO) +=3D gpio-npcm-sgpio.o obj-$(CONFIG_GPIO_OCTEON) +=3D gpio-octeon.o diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c new file mode 100644 index 000000000000..a8607f0d9915 --- /dev/null +++ b/drivers/gpio/gpio-nct6694.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 GPIO controller driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * USB command module type for NCT6694 GPIO controller. + * This defines the module type used for communication with the NCT6694 + * GPIO controller over the USB interface. + */ +#define NCT6694_GPIO_MOD 0xFF + +#define NCT6694_GPIO_VER 0x90 +#define NCT6694_GPIO_VALID 0x110 +#define NCT6694_GPI_DATA 0x120 +#define NCT6694_GPO_DIR 0x170 +#define NCT6694_GPO_TYPE 0x180 +#define NCT6694_GPO_DATA 0x190 + +#define NCT6694_GPI_STS 0x130 +#define NCT6694_GPI_CLR 0x140 +#define NCT6694_GPI_FALLING 0x150 +#define NCT6694_GPI_RISING 0x160 + +#define NCT6694_NR_GPIO 8 + +struct nct6694_gpio_data { + struct nct6694 *nct6694; + struct gpio_chip gpio; + struct mutex lock; + /* Protect irq operation */ + struct mutex irq_lock; + + unsigned char reg_val; + unsigned char irq_trig_falling; + unsigned char irq_trig_rising; + + /* Current gpio group */ + unsigned char group; + int irq; +}; + +static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offs= et) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_DIR + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + return !(BIT(offset) & data->reg_val); +} + +static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int of= fset) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_DIR + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + data->reg_val &=3D ~BIT(offset); + + return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); +} + +static int nct6694_direction_output(struct gpio_chip *gpio, + unsigned int offset, int val) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_DIR + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + /* Set direction to output */ + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + data->reg_val |=3D BIT(offset); + ret =3D nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + /* Then set output level */ + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPO_DATA + data->group); + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + if (val) + data->reg_val |=3D BIT(offset); + else + data->reg_val &=3D ~BIT(offset); + + return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); +} + +static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_DIR + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + if (BIT(offset) & data->reg_val) { + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPO_DATA + data->group); + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + return !!(BIT(offset) & data->reg_val); + } + + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPI_DATA + data->group); + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + return !!(BIT(offset) & data->reg_val); +} + +static int nct6694_set_value(struct gpio_chip *gpio, unsigned int offset, + int val) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_DATA + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + if (val) + data->reg_val |=3D BIT(offset); + else + data->reg_val &=3D ~BIT(offset); + + return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); +} + +static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset, + unsigned long config) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPO_TYPE + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + data->reg_val |=3D BIT(offset); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + data->reg_val &=3D ~BIT(offset); + break; + default: + return -ENOTSUPP; + } + + return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); +} + +static int nct6694_init_valid_mask(struct gpio_chip *gpio, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPIO_VALID + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret < 0) + return ret; + + *valid_mask =3D data->reg_val; + + return ret; +} + +static irqreturn_t nct6694_irq_handler(int irq, void *priv) +{ + struct nct6694_gpio_data *data =3D priv; + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPI_STS + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + unsigned char status; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val); + if (ret) + return IRQ_NONE; + + status =3D data->reg_val; + + while (status) { + int bit =3D __ffs(status); + + data->reg_val =3D BIT(bit); + handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit)); + status &=3D ~BIT(bit); + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPI_CLR + data->group); + nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val); + } + + return IRQ_HANDLED; +} + +static int nct6694_get_irq_trig(struct nct6694_gpio_data *data) +{ + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPI_FALLING + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + int ret; + + guard(mutex)(&data->lock); + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling); + if (ret) + return ret; + + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPI_RISING + data->group); + return nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising); +} + +static void nct6694_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gpio =3D irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq =3D irqd_to_hwirq(d); + + gpiochip_disable_irq(gpio, hwirq); +} + +static void nct6694_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gpio =3D irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq =3D irqd_to_hwirq(d); + + gpiochip_enable_irq(gpio, hwirq); +} + +static int nct6694_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gpio =3D irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + irq_hw_number_t hwirq =3D irqd_to_hwirq(d); + + guard(mutex)(&data->lock); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + data->irq_trig_rising |=3D BIT(hwirq); + break; + + case IRQ_TYPE_EDGE_FALLING: + data->irq_trig_falling |=3D BIT(hwirq); + break; + + case IRQ_TYPE_EDGE_BOTH: + data->irq_trig_rising |=3D BIT(hwirq); + data->irq_trig_falling |=3D BIT(hwirq); + break; + + default: + return -ENOTSUPP; + } + + return 0; +} + +static void nct6694_irq_bus_lock(struct irq_data *d) +{ + struct gpio_chip *gpio =3D irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + + mutex_lock(&data->irq_lock); +} + +static void nct6694_irq_bus_sync_unlock(struct irq_data *d) +{ + struct gpio_chip *gpio =3D irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data =3D gpiochip_get_data(gpio); + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_GPIO_MOD, + .offset =3D cpu_to_le16(NCT6694_GPI_FALLING + data->group), + .len =3D cpu_to_le16(sizeof(data->reg_val)) + }; + + scoped_guard(mutex, &data->lock) { + nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling); + + cmd_hd.offset =3D cpu_to_le16(NCT6694_GPI_RISING + data->group); + nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising); + } + + mutex_unlock(&data->irq_lock); +} + +static const struct irq_chip nct6694_irq_chip =3D { + .name =3D "gpio-nct6694", + .irq_mask =3D nct6694_irq_mask, + .irq_unmask =3D nct6694_irq_unmask, + .irq_set_type =3D nct6694_irq_set_type, + .irq_bus_lock =3D nct6694_irq_bus_lock, + .irq_bus_sync_unlock =3D nct6694_irq_bus_sync_unlock, + .flags =3D IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static void nct6694_irq_dispose_mapping(void *d) +{ + struct nct6694_gpio_data *data =3D d; + + irq_dispose_mapping(data->irq); +} + +static void nct6694_gpio_ida_free(void *d) +{ + struct nct6694_gpio_data *data =3D d; + struct nct6694 *nct6694 =3D data->nct6694; + + ida_free(&nct6694->gpio_ida, data->group); +} + +static int nct6694_gpio_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct nct6694 *nct6694 =3D dev_get_drvdata(dev->parent); + struct nct6694_gpio_data *data; + struct gpio_irq_chip *girq; + int ret, i; + char **names; + + data =3D devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nct6694 =3D nct6694; + + ret =3D ida_alloc(&nct6694->gpio_ida, GFP_KERNEL); + if (ret < 0) + return ret; + data->group =3D ret; + + ret =3D devm_add_action_or_reset(dev, nct6694_gpio_ida_free, data); + if (ret) + return ret; + + names =3D devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *), + GFP_KERNEL); + if (!names) + return -ENOMEM; + + for (i =3D 0; i < NCT6694_NR_GPIO; i++) { + names[i] =3D devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d", + data->group, i); + if (!names[i]) + return -ENOMEM; + } + + data->irq =3D irq_create_mapping(nct6694->domain, + NCT6694_IRQ_GPIO0 + data->group); + if (!data->irq) + return -EINVAL; + + ret =3D devm_add_action_or_reset(dev, nct6694_irq_dispose_mapping, data); + if (ret) + return ret; + + data->gpio.names =3D (const char * const*)names; + data->gpio.label =3D pdev->name; + data->gpio.direction_input =3D nct6694_direction_input; + data->gpio.get =3D nct6694_get_value; + data->gpio.direction_output =3D nct6694_direction_output; + data->gpio.set =3D nct6694_set_value; + data->gpio.get_direction =3D nct6694_get_direction; + data->gpio.set_config =3D nct6694_set_config; + data->gpio.init_valid_mask =3D nct6694_init_valid_mask; + data->gpio.base =3D -1; + data->gpio.can_sleep =3D false; + data->gpio.owner =3D THIS_MODULE; + data->gpio.ngpio =3D NCT6694_NR_GPIO; + + platform_set_drvdata(pdev, data); + + ret =3D devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret =3D devm_mutex_init(dev, &data->irq_lock); + if (ret) + return ret; + + ret =3D nct6694_get_irq_trig(data); + if (ret) { + dev_err_probe(dev, ret, "Failed to get irq trigger type\n"); + return ret; + } + + girq =3D &data->gpio.irq; + gpio_irq_chip_set_chip(girq, &nct6694_irq_chip); + girq->parent_handler =3D NULL; + girq->num_parents =3D 0; + girq->parents =3D NULL; + girq->default_type =3D IRQ_TYPE_NONE; + girq->handler =3D handle_level_irq; + girq->threaded =3D true; + + ret =3D devm_request_threaded_irq(dev, data->irq, NULL, nct6694_irq_handl= er, + IRQF_ONESHOT | IRQF_SHARED, + "gpio-nct6694", data); + if (ret) { + dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; + } + + return devm_gpiochip_add_data(dev, &data->gpio, data); +} + +static struct platform_driver nct6694_gpio_driver =3D { + .driver =3D { + .name =3D "nct6694-gpio", + }, + .probe =3D nct6694_gpio_probe, +}; + +module_platform_driver(nct6694_gpio_driver); + +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-gpio"); --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2522728A3FA for ; Fri, 12 Sep 2025 09:20:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668829; cv=none; b=S4BTPjubSCk7k44cTi9gg6sMERrMhF9by+4NO4Y4MQYGHqIpRbWMXyCPoZRvn8v6HepZ70Y2UEn7cIsNG/DELCocX41pmvb63byqa7xkkgENGoKiUsccvHNrTobcIj65lE749VB6LN7uZ84Ry3PGoSX6+SnGxqx7lHYrSh3YUeQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668829; c=relaxed/simple; bh=/gjiOvtQeC8MWi0aqWNM7oYCQpBdtUwq1g79qJU3mJA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=VhOMzHC4ZyEIjvpPH1Ru8jonRjAPjy5KelfF9by5G2G3j8htGY9ew9GtY4uoFRmlz2j4GQQg9Cagf6udjffGwgGDGLmll41z02eXj3Q5Sb14ntFzq9uLMBh+JSN6siIwd/7/iwxHEb49jv4iplcfXcCyrlHc485USu7/BzxuxIA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jI0jyQ7I; arc=none smtp.client-ip=209.85.210.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jI0jyQ7I" Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-7725fb32e1bso1738884b3a.1 for ; Fri, 12 Sep 2025 02:20:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668826; x=1758273626; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=uPdU6LK77EYO3iP5ZobJ/AY/W1Pzf56x6nwabe4cYsE=; b=jI0jyQ7I/IdK/QmdD9IiJp+L5HJ0oJqLR/28ERb8uhp+bxZPR3hbT8MknbijP8fGQ/ 2bSO5NfiHHwvc9jvh86Cjz6LhZ/FJX8Cpxv4XDo/A9ASV+zci9lYUHOVKuhLfPfK4Adv 0613GaK2lGwsRHz++aH7qfba3JmupaDrlolWvkbGAdHFjg1N0x+l53sUm0ZXagUtbh6C cZqosPDcl0erJMUAgDayxfnSvtBCIF1pj9ysFdIMyBEnamD+bv6xzCc8NOEelyhU7pKB 4lEp2lTk8zkerMVla+huJiRmLZW9jBtmBiXzyKcHEFOnlii8IC3WOyyLbzrW/VIQONz8 q7gg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668826; x=1758273626; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=uPdU6LK77EYO3iP5ZobJ/AY/W1Pzf56x6nwabe4cYsE=; b=DdxuJ6lNsJ+0fJvxEqzgURYfIfgcazZ2OJgg1w5cMg5GYE+YZo6CHsa1Xk0BMvbSY3 y65pbULt1W7SFaOuUhceBDOcAwzZyOiignq2KGj09iFavDOZn2DWNB4uB7hI6dwdTQWe GooSPtHSxYwJ6WVfOKBhb60FIaT3rMFONJO0nQUlVRaGPvq2NSn+iwb1TCy74safktn0 AUtCn9Gsu6/2jMDGFjG2GF3glJuljx0FMqgeQ45+w6t38ZgscirDCAGq6bAL+na/4Qn7 nKPhk5/YcSmF8GWGtjkB65fUiSN0iDDdXfn7hBAhCpkrjSDX8zZpROOTBxK3DKgWPmxg GgQQ== X-Gm-Message-State: AOJu0YwFwDQvy+dcwG9oAf4JIoXxd3tT3lmVFbgTOntAV/JxK632T+CA F2sf5+cNSbWpyJh6QLWIZyfZ3V+wDbup5RC1bDxszgb1ZpBB55CO+vVt X-Gm-Gg: ASbGnctSa/z4P8Gb4Yw3Sua7M1y0ABDNZU3MHa7VHH9xTSnbte0hAolTN7OKyv4tFIA A5Tc70ZSJWSNeAvF2GzfjSK2MhpUF9MfcsXF9aH6lGM86uPLjkgTrEYgbAGt6+AQ0shE5lhUlAu yB6KHPV3nTmOGcdtdESFrW1jhyBq8dDTkSQoCwek/R+hyh8n8waD8nZCVsGtEc3haSxcL6CFMOY K88AOTGsNEqI0iDKcGKKqmZWxiRYz4pcSqxhpJakPg8pdzahmS5ulu7LwaAEQuhCpmQE6O78CpC IE6ppL1lhRTTfMH25iV9vSNR/RV2mbnlCex7EDFQgJt9CnNbi65QhEoEEISxeL2NjmsATgVMjIp 1tGvGLmgfMNl2dfSqtO+lBLagWhKPwfaQS+MvU0+TBzyLXo0/ufQ+TFpq/wX5ntJQBQ== X-Google-Smtp-Source: AGHT+IH/m+VR1DNePVcHWaQCojwRWvwxWRyQzo6LHek9BLXODlHrtK6Z/CgxSWdf3ZkUFzh0nnSbow== X-Received: by 2002:a05:6a20:939e:b0:243:f86b:3868 with SMTP id adf61e73a8af0-2602c144613mr2945322637.48.1757668826456; Fri, 12 Sep 2025 02:20:26 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:26 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu , Wolfram Sang Subject: [PATCH RESEND v14 3/7] i2c: Add Nuvoton NCT6694 I2C support Date: Fri, 12 Sep 2025 17:19:48 +0800 Message-Id: <20250912091952.1169369-4-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports I2C adapter functionality for NCT6694 MFD device based on USB interface. Each I2C controller uses the default baudrate of 100kHz, which can be overridden via module parameters. Acked-by: Andi Shyti Reviewed-by: Wolfram Sang Signed-off-by: Ming Yu --- Changes since version 13: - Add struct i2c_adapter_quirks in i2c-nct6694.c Changes since version 12: - Implement IDA in MFD driver to handle per-device IDs - Use same email address in the signature Changes since version 11: - Use platform_device's id to replace IDA Changes since version 10: - Implement IDA to allocate id Changes since version 9: Changes since version 8: - Modify the signed-off-by with my work address - Add module parameters to configure I2C's baudrate Changes since version 7: Changes since version 6: Changes since version 5: - Modify the module name and the driver name consistently Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function Changes since version 3: - Modify array buffer to structure - Fix defines and comments Changes since version 2: - Add MODULE_ALIAS() Changes since version 1: - Add each driver's command structure - Fix platform driver registration MAINTAINERS | 1 + drivers/i2c/busses/Kconfig | 10 ++ drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-nct6694.c | 196 +++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 drivers/i2c/busses/i2c-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index e340d1934394..c8f912cb0b95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18086,6 +18086,7 @@ NUVOTON NCT6694 MFD DRIVER M: Ming Yu 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 =20 diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 070d014fdc5d..63a2b5a9abc3 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1357,6 +1357,16 @@ config I2C_LJCA This driver can also be built as a module. If so, the module will be called i2c-ljca. =20 +config I2C_NCT6694 + tristate "Nuvoton NCT6694 I2C adapter support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB to I2C interface. + + This driver can also be built as a module. If so, the module will + be called i2c-nct6694. + config I2C_CP2615 tristate "Silicon Labs CP2615 USB sound card and I2C adapter" depends on USB diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 04db855fdfd6..fe8cf6325fc9 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP) +=3D i2c-gxp.o obj-$(CONFIG_I2C_DIOLAN_U2C) +=3D i2c-diolan-u2c.o obj-$(CONFIG_I2C_DLN2) +=3D i2c-dln2.o obj-$(CONFIG_I2C_LJCA) +=3D i2c-ljca.o +obj-$(CONFIG_I2C_NCT6694) +=3D i2c-nct6694.o obj-$(CONFIG_I2C_CP2615) +=3D i2c-cp2615.o obj-$(CONFIG_I2C_PARPORT) +=3D i2c-parport.o obj-$(CONFIG_I2C_PCI1XXXX) +=3D i2c-mchp-pci1xxxx.o diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6= 694.c new file mode 100644 index 000000000000..1413ab6f9462 --- /dev/null +++ b/drivers/i2c/busses/i2c-nct6694.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 I2C adapter driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include + +/* + * USB command module type for NCT6694 I2C controller. + * This defines the module type used for communication with the NCT6694 + * I2C controller over the USB interface. + */ +#define NCT6694_I2C_MOD 0x03 + +/* Command 00h - I2C Deliver */ +#define NCT6694_I2C_DELIVER 0x00 +#define NCT6694_I2C_DELIVER_SEL 0x00 + +#define NCT6694_I2C_MAX_XFER_SIZE 64 +#define NCT6694_I2C_MAX_DEVS 6 + +static unsigned char br_reg[NCT6694_I2C_MAX_DEVS] =3D {[0 ... (NCT6694_I2C= _MAX_DEVS - 1)] =3D 0xFF}; + +module_param_array(br_reg, byte, NULL, 0644); +MODULE_PARM_DESC(br_reg, + "I2C Baudrate register per adapter: (0=3D25K, 1=3D50K, 2=3D100K, 3=3D20= 0K, 4=3D400K, 5=3D800K, 6=3D1M), default=3D2"); + +enum nct6694_i2c_baudrate { + NCT6694_I2C_BR_25K =3D 0, + NCT6694_I2C_BR_50K, + NCT6694_I2C_BR_100K, + NCT6694_I2C_BR_200K, + NCT6694_I2C_BR_400K, + NCT6694_I2C_BR_800K, + NCT6694_I2C_BR_1M +}; + +struct __packed nct6694_i2c_deliver { + u8 port; + u8 br; + u8 addr; + u8 w_cnt; + u8 r_cnt; + u8 rsv[11]; + u8 write_data[NCT6694_I2C_MAX_XFER_SIZE]; + u8 read_data[NCT6694_I2C_MAX_XFER_SIZE]; +}; + +struct nct6694_i2c_data { + struct device *dev; + struct nct6694 *nct6694; + struct i2c_adapter adapter; + struct nct6694_i2c_deliver deliver; + unsigned char port; + unsigned char br; +}; + +static int nct6694_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs= , int num) +{ + struct nct6694_i2c_data *data =3D adap->algo_data; + struct nct6694_i2c_deliver *deliver =3D &data->deliver; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_I2C_MOD, + .cmd =3D NCT6694_I2C_DELIVER, + .sel =3D NCT6694_I2C_DELIVER_SEL, + .len =3D cpu_to_le16(sizeof(*deliver)) + }; + int ret, i; + + for (i =3D 0; i < num; i++) { + struct i2c_msg *msg_temp =3D &msgs[i]; + + memset(deliver, 0, sizeof(*deliver)); + + deliver->port =3D data->port; + deliver->br =3D data->br; + deliver->addr =3D i2c_8bit_addr_from_msg(msg_temp); + if (msg_temp->flags & I2C_M_RD) { + deliver->r_cnt =3D msg_temp->len; + ret =3D nct6694_write_msg(data->nct6694, &cmd_hd, deliver); + if (ret < 0) + return ret; + + memcpy(msg_temp->buf, deliver->read_data, msg_temp->len); + } else { + deliver->w_cnt =3D msg_temp->len; + memcpy(deliver->write_data, msg_temp->buf, msg_temp->len); + ret =3D nct6694_write_msg(data->nct6694, &cmd_hd, deliver); + if (ret < 0) + return ret; + } + } + + return num; +} + +static u32 nct6694_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_adapter_quirks nct6694_i2c_quirks =3D { + .max_read_len =3D NCT6694_I2C_MAX_XFER_SIZE, + .max_write_len =3D NCT6694_I2C_MAX_XFER_SIZE, +}; + +static const struct i2c_algorithm nct6694_i2c_algo =3D { + .xfer =3D nct6694_i2c_xfer, + .functionality =3D nct6694_i2c_func, +}; + +static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data) +{ + if (data->port >=3D NCT6694_I2C_MAX_DEVS) { + dev_err(data->dev, "Invalid I2C port index %d\n", data->port); + return -EINVAL; + } + + if (br_reg[data->port] > NCT6694_I2C_BR_1M) { + dev_warn(data->dev, "Invalid baudrate %d for I2C%d, using 100K\n", + br_reg[data->port], data->port); + br_reg[data->port] =3D NCT6694_I2C_BR_100K; + } + + data->br =3D br_reg[data->port]; + + return 0; +} + +static void nct6694_i2c_ida_free(void *d) +{ + struct nct6694_i2c_data *data =3D d; + struct nct6694 *nct6694 =3D data->nct6694; + + ida_free(&nct6694->i2c_ida, data->port); +} + +static int nct6694_i2c_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct nct6694 *nct6694 =3D dev_get_drvdata(dev->parent); + struct nct6694_i2c_data *data; + int ret; + + data =3D devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev =3D dev; + data->nct6694 =3D nct6694; + + ret =3D ida_alloc(&nct6694->i2c_ida, GFP_KERNEL); + if (ret < 0) + return ret; + data->port =3D ret; + + ret =3D devm_add_action_or_reset(dev, nct6694_i2c_ida_free, data); + if (ret) + return ret; + + ret =3D nct6694_i2c_set_baudrate(data); + if (ret) + return ret; + + sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", data->port); + data->adapter.owner =3D THIS_MODULE; + data->adapter.algo =3D &nct6694_i2c_algo; + data->adapter.quirks =3D &nct6694_i2c_quirks; + data->adapter.dev.parent =3D dev; + data->adapter.algo_data =3D data; + + platform_set_drvdata(pdev, data); + + return devm_i2c_add_adapter(dev, &data->adapter); +} + +static struct platform_driver nct6694_i2c_driver =3D { + .driver =3D { + .name =3D "nct6694-i2c", + }, + .probe =3D nct6694_i2c_probe, +}; + +module_platform_driver(nct6694_i2c_driver); + +MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-i2c"); --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 916022BE03D for ; Fri, 12 Sep 2025 09:20:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668834; cv=none; b=fFMv1mfdgA8RvtOYpvRr02hkAL5qJtnRhTKsd56aIs8pwsKjKhcUoxIk+ikdhFawd91UCiFgXFVkFLrTIZYchrriEZHvrA924752PwHE86CefaJzo2hdXu08qbkVy0y+xgc6mKMqKSyKD1B7fGHfGVzWAnm3Q4RsNJGmGlBOsgI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668834; c=relaxed/simple; bh=HkybItgWsl/oXbonoIb9QHF4/ozjXmoQbseBPzeQG9U=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=gL5INh8WL+ptw0HwWMgmUVy919YPAhlbAaeeF1I3jAb/vDXt0rZRF6vSBamie4ylZ1vPd0qzVq6BQ2Zab4eQOVosG3BbFP7GGJ5VAaI+X/ZyZHtDmEGbrv1G6iG503fBQ/OC1UWdKrb39p7lkrz0fG+T2dX0JlZUDx+WJZICti8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=giIEzZaD; arc=none smtp.client-ip=209.85.210.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="giIEzZaD" Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-77619f3f41aso162551b3a.2 for ; Fri, 12 Sep 2025 02:20:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668831; x=1758273631; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=KRcT8VqkwLwp7YG0RleYXMM2y/9hrkdErwWkWKqV8QQ=; b=giIEzZaDe0jDHthCd69R1YXntfQsquGL1B+iNnKcMev7s4t7oDs+YgaJHSRJQH+0gH simx0EGK2pPOtx0sNgkXoWIUG54ISiyfGeSV3taRs0jzOtBpLXzmtjWec0unBOESWJWE r9s2EmuOoo5ZurF2gJUcucZ0mA6KxoyD/M6rfpYMM3svRHIrKyNXIGncMxElXyJ3sAAG iTuyCWMhDb0b1njaiwwPAGP3byeF5aB973ZMVFXBoNt81S7p7t9FxyP+3/9H46unhbrn 1kRZ05LgpE2gwAwDO8BswIq/0gCtJKUetyXwYe7wRinpEJeG8Izclk7coKCFwvfSejvA 2gJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668831; x=1758273631; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=KRcT8VqkwLwp7YG0RleYXMM2y/9hrkdErwWkWKqV8QQ=; b=VnRUI/aTYnTsw1lRIBiS3euHSP6l6O6SrGBQSz+zphW8Gt5YIa1Q2CIr4hOxCHZT9u 5O1oorlJgxCgbCLsLI6iyk5h/9KWSf3kw1PFtXaIKDq7LyO5CPOCbUoo2U735nMYV+AZ pveRTU8nWnT+suurdsArBxDtLAtXz7MrqtGcgnb7tNNxKtMTLzTHSX2qi+FyCsLae1g2 0ueZ9Wo/6TC8Kk4O3/1GhIwLMoiw9zGZ3U06p982mSOuApHf2Jk6duvnj0zkiEWKQ65j rqNoU/gJx7PA1CF9U7aJf4BXjvbKP9VDeOswDwwYWY09vO0qbKrnFLIyTAMENW5hLa38 BSqw== X-Gm-Message-State: AOJu0YwJ2yupovc1rSPpg7D1rnZiFGkVkIZX02dBLp5CYQfgfiezCnAT y5HkpRI4eq/oz6H/AJzKGTG5V3cWozuIDL3BVz3iuJVCuyKEqQZjbgHa X-Gm-Gg: ASbGncu9g+VxPMeM5fknblpLl+ec1CWcHMnP+DmV51pp5r2+H+MT/Lm4BZ8IYGk6E7Z 9Z+SX1fbJWWT3e7LnkJQwl6jkBaysPLGMiCkSQnUEXPzIfcMb8//9R7H/Egg7/5FRzDw4N0R7uA YQ2iO94jrB7ZTxSLGHfoFZRLbO+MyGvwSFZ05r/OTsbSTLNhZvg+Jqa7wEfZy6o7YyyycH3heX0 JcR06lk2iPedcyIMqSNI3yDyT5U5LfN/qHd+ErQMtEOJkQS7TJxBuaXIEE3LxZTWULDQIz4AuQJ v4BHYZD44BTVO/+yZsY3N4MvnFqw/+og523enJqnhabMkTiJrQ92jcRtAdOzZX2jC+SoCzgw1to 13Y0UD/HJzKpvBOoQxyPwPFcNjjLnLMfdcR9kUOO7DVmXDVM1kH4JW4nXNKQ1ZrbWeRnkneM4VT sX3JEiuD6Jm2g= X-Google-Smtp-Source: AGHT+IGsF9JCrNfFhWtho1Egk7LavWy2gtT62Xfjcy0Cs/7YTskW3JHRrFksbbA2WfHxm6nKngmZbA== X-Received: by 2002:a05:6a20:1594:b0:243:78a:8291 with SMTP id adf61e73a8af0-2602cf10c79mr2987235637.56.1757668830513; Fri, 12 Sep 2025 02:20:30 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:30 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu Subject: [PATCH RESEND v14 4/7] can: Add Nuvoton NCT6694 CANFD support Date: Fri, 12 Sep 2025 17:19:49 +0800 Message-Id: <20250912091952.1169369-5-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports Socket CANFD functionality for NCT6694 MFD device based on USB interface. Reviewed-by: Marc Kleine-Budde Reviewed-by: Vincent Mailhol Signed-off-by: Ming Yu --- Changes since version 13: Changes since version 12: - Implement IDA in MFD driver to handle per-device IDs - Use same email address in the signature Changes since version 11: - Use platform_device's id to replace IDA - Update struct data_bittiming_params related part Changes since version 10: - Implement IDA to allocate id - Add header - Add support to config tdc Changes since version 9: Changes since version 8: - Modify the signed-off-by with my work address - Add irq_dispose_mapping() in the error handling path and in the remove function - Rename all function names nct6694_can_xxx to nct6694_canfd_xxx - Fix nct6694_canfd_handle_state_change() - Fix nct6694_canfd_start() to configure NBTP and DBTP - Add can_set_static_ctrlmode() Changes since version 7: - Add error handling for devm_mutex_init() - Modify the name of the child devices CAN1 and CAN2 to CAN0 and CAN1. - Fix multiline comments to net-dev style Changes since version 6: - Fix nct6694_can_handle_state_change() - Fix warnings - Move the nct6694_can_priv's bec to the end Changes since version 5: - Modify the module name and the driver name consistently - Update private data and drop mutex - Fix nct6694_can_handle_state_change() Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function - Move the nct6694_canfd.c to drivers/net/can/usb/ - Fix the missing rx offload function Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Modify mutex_init() to devm_mutex_init() - Add rx-offload helper Changes since version 2: - Add MODULE_ALIAS() - Rename each command - Modify each function name consistently Changes since version 1: - Add each driver's command structure - Fix platform driver registration - Fix errors and warnings - Fix TX-flow control MAINTAINERS | 1 + drivers/net/can/usb/Kconfig | 11 + drivers/net/can/usb/Makefile | 1 + drivers/net/can/usb/nct6694_canfd.c | 832 ++++++++++++++++++++++++++++ 4 files changed, 845 insertions(+) create mode 100644 drivers/net/can/usb/nct6694_canfd.c diff --git a/MAINTAINERS b/MAINTAINERS index c8f912cb0b95..758c9a67184e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18088,6 +18088,7 @@ S: Supported F: drivers/gpio/gpio-nct6694.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c +F: drivers/net/can/usb/nct6694_canfd.c F: include/linux/mfd/nct6694.h =20 NUVOTON NCT7201 IIO DRIVER diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig index a7547a83120e..cf65a90816b9 100644 --- a/drivers/net/can/usb/Kconfig +++ b/drivers/net/can/usb/Kconfig @@ -134,6 +134,17 @@ config CAN_MCBA_USB This driver supports the CAN BUS Analyzer interface from Microchip (http://www.microchip.com/development-tools/). =20 +config CAN_NCT6694 + tristate "Nuvoton NCT6694 Socket CANfd support" + depends on MFD_NCT6694 + select CAN_RX_OFFLOAD + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to socket CANfd controller. + + This driver can also be built as a module. If so, the module will + be called nct6694_canfd. + config CAN_PEAK_USB tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD" help diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile index 8b11088e9a59..fcafb1ac262e 100644 --- a/drivers/net/can/usb/Makefile +++ b/drivers/net/can/usb/Makefile @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) +=3D f81604.o obj-$(CONFIG_CAN_GS_USB) +=3D gs_usb.o obj-$(CONFIG_CAN_KVASER_USB) +=3D kvaser_usb/ obj-$(CONFIG_CAN_MCBA_USB) +=3D mcba_usb.o +obj-$(CONFIG_CAN_NCT6694) +=3D nct6694_canfd.o obj-$(CONFIG_CAN_PEAK_USB) +=3D peak_usb/ obj-$(CONFIG_CAN_UCAN) +=3D ucan.o diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6= 694_canfd.c new file mode 100644 index 000000000000..8deff16491a1 --- /dev/null +++ b/drivers/net/can/usb/nct6694_canfd.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Nuvoton NCT6694 Socket CANfd driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "nct6694-canfd" + +/* USB command module type for NCT6694 CANfd controller. + * This defines the module type used for communication with the NCT6694 + * CANfd controller over the USB interface. + */ +#define NCT6694_CANFD_MOD 0x05 + +/* Command 00h - CAN Setting and Initialization */ +#define NCT6694_CANFD_SETTING 0x00 +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1 BIT(0) +#define NCT6694_CANFD_SETTING_ACTIVE_CTRL2 BIT(1) +#define NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP BIT(2) +#define NCT6694_CANFD_SETTING_CTRL1_MON BIT(0) +#define NCT6694_CANFD_SETTING_CTRL1_NISO BIT(1) +#define NCT6694_CANFD_SETTING_CTRL1_LBCK BIT(2) +#define NCT6694_CANFD_SETTING_NBTP_NTSEG2 GENMASK(6, 0) +#define NCT6694_CANFD_SETTING_NBTP_NTSEG1 GENMASK(15, 8) +#define NCT6694_CANFD_SETTING_NBTP_NBRP GENMASK(24, 16) +#define NCT6694_CANFD_SETTING_NBTP_NSJW GENMASK(31, 25) +#define NCT6694_CANFD_SETTING_DBTP_DSJW GENMASK(3, 0) +#define NCT6694_CANFD_SETTING_DBTP_DTSEG2 GENMASK(7, 4) +#define NCT6694_CANFD_SETTING_DBTP_DTSEG1 GENMASK(12, 8) +#define NCT6694_CANFD_SETTING_DBTP_DBRP GENMASK(20, 16) +#define NCT6694_CANFD_SETTING_DBTP_TDC BIT(23) + +/* Command 01h - CAN Information */ +#define NCT6694_CANFD_INFORMATION 0x01 +#define NCT6694_CANFD_INFORMATION_SEL 0x00 + +/* Command 02h - CAN Event */ +#define NCT6694_CANFD_EVENT 0x02 +#define NCT6694_CANFD_EVENT_SEL(idx, mask) \ + ((idx ? 0x80 : 0x00) | ((mask) & 0x7F)) + +#define NCT6694_CANFD_EVENT_MASK GENMASK(5, 0) +#define NCT6694_CANFD_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */ +#define NCT6694_CANFD_EVT_RX_DATA_LOST BIT(5) /* Read-clear */ +#define NCT6694_CANFD_EVT_RX_DATA_IN BIT(7) /* Read-clear */ + +/* Command 10h - CAN Deliver */ +#define NCT6694_CANFD_DELIVER 0x10 +#define NCT6694_CANFD_DELIVER_SEL(buf_cnt) \ + ((buf_cnt) & 0xFF) + +/* Command 11h - CAN Receive */ +#define NCT6694_CANFD_RECEIVE 0x11 +#define NCT6694_CANFD_RECEIVE_SEL(idx, buf_cnt) \ + ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F)) + +#define NCT6694_CANFD_FRAME_TAG(idx) (0xC0 | (idx)) +#define NCT6694_CANFD_FRAME_FLAG_EFF BIT(0) +#define NCT6694_CANFD_FRAME_FLAG_RTR BIT(1) +#define NCT6694_CANFD_FRAME_FLAG_FD BIT(2) +#define NCT6694_CANFD_FRAME_FLAG_BRS BIT(3) +#define NCT6694_CANFD_FRAME_FLAG_ERR BIT(4) + +#define NCT6694_NAPI_WEIGHT 32 + +enum nct6694_event_err { + NCT6694_CANFD_EVT_ERR_NO_ERROR =3D 0, + NCT6694_CANFD_EVT_ERR_CRC_ERROR, + NCT6694_CANFD_EVT_ERR_STUFF_ERROR, + NCT6694_CANFD_EVT_ERR_ACK_ERROR, + NCT6694_CANFD_EVT_ERR_FORM_ERROR, + NCT6694_CANFD_EVT_ERR_BIT_ERROR, + NCT6694_CANFD_EVT_ERR_TIMEOUT_ERROR, + NCT6694_CANFD_EVT_ERR_UNKNOWN_ERROR, +}; + +enum nct6694_event_status { + NCT6694_CANFD_EVT_STS_ERROR_ACTIVE =3D 0, + NCT6694_CANFD_EVT_STS_ERROR_PASSIVE, + NCT6694_CANFD_EVT_STS_BUS_OFF, + NCT6694_CANFD_EVT_STS_WARNING, +}; + +struct __packed nct6694_canfd_setting { + __le32 nbr; + __le32 dbr; + u8 active; + u8 reserved[3]; + __le16 ctrl1; + __le16 ctrl2; + __le32 nbtp; + __le32 dbtp; +}; + +struct __packed nct6694_canfd_information { + u8 tx_fifo_cnt; + u8 rx_fifo_cnt; + u8 reserved[2]; + __le32 can_clk; +}; + +struct __packed nct6694_canfd_event { + u8 err; + u8 status; + u8 tx_evt; + u8 rx_evt; + u8 rec; + u8 tec; + u8 reserved[2]; +}; + +struct __packed nct6694_canfd_frame { + u8 tag; + u8 flag; + u8 reserved; + u8 length; + __le32 id; + u8 data[CANFD_MAX_DLEN]; +}; + +struct nct6694_canfd_priv { + struct can_priv can; /* must be the first member */ + struct can_rx_offload offload; + struct net_device *ndev; + struct nct6694 *nct6694; + struct workqueue_struct *wq; + struct work_struct tx_work; + struct nct6694_canfd_frame tx; + struct nct6694_canfd_frame rx; + struct nct6694_canfd_event event[2]; + struct can_berr_counter bec; +}; + +static inline struct nct6694_canfd_priv *rx_offload_to_priv(struct can_rx_= offload *offload) +{ + return container_of(offload, struct nct6694_canfd_priv, offload); +} + +static const struct can_bittiming_const nct6694_canfd_bittiming_nominal_co= nst =3D { + .name =3D DEVICE_NAME, + .tseg1_min =3D 1, + .tseg1_max =3D 256, + .tseg2_min =3D 1, + .tseg2_max =3D 128, + .sjw_max =3D 128, + .brp_min =3D 1, + .brp_max =3D 512, + .brp_inc =3D 1, +}; + +static const struct can_bittiming_const nct6694_canfd_bittiming_data_const= =3D { + .name =3D DEVICE_NAME, + .tseg1_min =3D 1, + .tseg1_max =3D 32, + .tseg2_min =3D 1, + .tseg2_max =3D 16, + .sjw_max =3D 16, + .brp_min =3D 1, + .brp_max =3D 32, + .brp_inc =3D 1, +}; + +static void nct6694_canfd_rx_offload(struct can_rx_offload *offload, + struct sk_buff *skb) +{ + struct nct6694_canfd_priv *priv =3D rx_offload_to_priv(offload); + int ret; + + ret =3D can_rx_offload_queue_tail(offload, skb); + if (ret) + priv->ndev->stats.rx_fifo_errors++; +} + +static void nct6694_canfd_handle_lost_msg(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct net_device_stats *stats =3D &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + + netdev_dbg(ndev, "RX FIFO overflow, message(s) lost.\n"); + + stats->rx_errors++; + stats->rx_over_errors++; + + skb =3D alloc_can_err_skb(ndev, &cf); + if (!skb) + return; + + cf->can_id |=3D CAN_ERR_CRTL; + cf->data[1] =3D CAN_ERR_CRTL_RX_OVERFLOW; + + nct6694_canfd_rx_offload(&priv->offload, skb); +} + +static void nct6694_canfd_handle_rx(struct net_device *ndev, u8 rx_evt) +{ + struct net_device_stats *stats =3D &ndev->stats; + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct nct6694_canfd_frame *frame =3D &priv->rx; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_RECEIVE, + .sel =3D NCT6694_CANFD_RECEIVE_SEL(ndev->dev_port, 1), + .len =3D cpu_to_le16(sizeof(*frame)) + }; + struct sk_buff *skb; + int ret; + + ret =3D nct6694_read_msg(priv->nct6694, &cmd_hd, frame); + if (ret) + return; + + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_FD) { + struct canfd_frame *cfd; + + skb =3D alloc_canfd_skb(priv->ndev, &cfd); + if (!skb) { + stats->rx_dropped++; + return; + } + + cfd->can_id =3D le32_to_cpu(frame->id); + cfd->len =3D canfd_sanitize_len(frame->length); + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF) + cfd->can_id |=3D CAN_EFF_FLAG; + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_BRS) + cfd->flags |=3D CANFD_BRS; + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_ERR) + cfd->flags |=3D CANFD_ESI; + + memcpy(cfd->data, frame->data, cfd->len); + } else { + struct can_frame *cf; + + skb =3D alloc_can_skb(priv->ndev, &cf); + if (!skb) { + stats->rx_dropped++; + return; + } + + cf->can_id =3D le32_to_cpu(frame->id); + cf->len =3D can_cc_dlc2len(frame->length); + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_EFF) + cf->can_id |=3D CAN_EFF_FLAG; + + if (frame->flag & NCT6694_CANFD_FRAME_FLAG_RTR) + cf->can_id |=3D CAN_RTR_FLAG; + else + memcpy(cf->data, frame->data, cf->len); + } + + nct6694_canfd_rx_offload(&priv->offload, skb); +} + +static int nct6694_canfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + + *bec =3D priv->bec; + + return 0; +} + +static void nct6694_canfd_handle_state_change(struct net_device *ndev, u8 = status) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + enum can_state new_state, rx_state, tx_state; + struct can_berr_counter bec; + struct can_frame *cf; + struct sk_buff *skb; + + nct6694_canfd_get_berr_counter(ndev, &bec); + can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state); + + new_state =3D max(tx_state, rx_state); + + /* state hasn't changed */ + if (new_state =3D=3D priv->can.state) + return; + + skb =3D alloc_can_err_skb(ndev, &cf); + + can_change_state(ndev, cf, tx_state, rx_state); + + if (new_state =3D=3D CAN_STATE_BUS_OFF) { + can_bus_off(ndev); + } else if (cf) { + cf->can_id |=3D CAN_ERR_CNT; + cf->data[6] =3D bec.txerr; + cf->data[7] =3D bec.rxerr; + } + + if (skb) + nct6694_canfd_rx_offload(&priv->offload, skb); +} + +static void nct6694_canfd_handle_bus_err(struct net_device *ndev, u8 bus_e= rr) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct can_frame *cf; + struct sk_buff *skb; + + priv->can.can_stats.bus_error++; + + skb =3D alloc_can_err_skb(ndev, &cf); + if (cf) + cf->can_id |=3D CAN_ERR_PROT | CAN_ERR_BUSERROR; + + switch (bus_err) { + case NCT6694_CANFD_EVT_ERR_CRC_ERROR: + netdev_dbg(ndev, "CRC error\n"); + ndev->stats.rx_errors++; + if (cf) + cf->data[3] |=3D CAN_ERR_PROT_LOC_CRC_SEQ; + break; + + case NCT6694_CANFD_EVT_ERR_STUFF_ERROR: + netdev_dbg(ndev, "Stuff error\n"); + ndev->stats.rx_errors++; + if (cf) + cf->data[2] |=3D CAN_ERR_PROT_STUFF; + break; + + case NCT6694_CANFD_EVT_ERR_ACK_ERROR: + netdev_dbg(ndev, "Ack error\n"); + ndev->stats.tx_errors++; + if (cf) { + cf->can_id |=3D CAN_ERR_ACK; + cf->data[2] |=3D CAN_ERR_PROT_TX; + } + break; + + case NCT6694_CANFD_EVT_ERR_FORM_ERROR: + netdev_dbg(ndev, "Form error\n"); + ndev->stats.rx_errors++; + if (cf) + cf->data[2] |=3D CAN_ERR_PROT_FORM; + break; + + case NCT6694_CANFD_EVT_ERR_BIT_ERROR: + netdev_dbg(ndev, "Bit error\n"); + ndev->stats.tx_errors++; + if (cf) + cf->data[2] |=3D CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT; + break; + + default: + break; + } + + if (skb) + nct6694_canfd_rx_offload(&priv->offload, skb); +} + +static void nct6694_canfd_handle_tx(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct net_device_stats *stats =3D &ndev->stats; + + stats->tx_bytes +=3D can_rx_offload_get_echo_skb_queue_tail(&priv->offloa= d, + 0, NULL); + stats->tx_packets++; + netif_wake_queue(ndev); +} + +static irqreturn_t nct6694_canfd_irq(int irq, void *data) +{ + struct net_device *ndev =3D data; + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct nct6694_canfd_event *event =3D &priv->event[ndev->dev_port]; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_EVENT, + .sel =3D NCT6694_CANFD_EVENT_SEL(ndev->dev_port, NCT6694_CANFD_EVENT_MAS= K), + .len =3D cpu_to_le16(sizeof(priv->event)) + }; + irqreturn_t handled =3D IRQ_NONE; + int ret; + + ret =3D nct6694_read_msg(priv->nct6694, &cmd_hd, priv->event); + if (ret < 0) + return handled; + + if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_IN) { + nct6694_canfd_handle_rx(ndev, event->rx_evt); + handled =3D IRQ_HANDLED; + } + + if (event->rx_evt & NCT6694_CANFD_EVT_RX_DATA_LOST) { + nct6694_canfd_handle_lost_msg(ndev); + handled =3D IRQ_HANDLED; + } + + if (event->status) { + nct6694_canfd_handle_state_change(ndev, event->status); + handled =3D IRQ_HANDLED; + } + + if (event->err !=3D NCT6694_CANFD_EVT_ERR_NO_ERROR) { + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + nct6694_canfd_handle_bus_err(ndev, event->err); + handled =3D IRQ_HANDLED; + } + + if (event->tx_evt & NCT6694_CANFD_EVT_TX_FIFO_EMPTY) { + nct6694_canfd_handle_tx(ndev); + handled =3D IRQ_HANDLED; + } + + if (handled) + can_rx_offload_threaded_irq_finish(&priv->offload); + + priv->bec.rxerr =3D event->rec; + priv->bec.txerr =3D event->tec; + + return handled; +} + +static void nct6694_canfd_tx_work(struct work_struct *work) +{ + struct nct6694_canfd_priv *priv =3D container_of(work, + struct nct6694_canfd_priv, + tx_work); + struct nct6694_canfd_frame *frame =3D &priv->tx; + struct net_device *ndev =3D priv->ndev; + struct net_device_stats *stats =3D &ndev->stats; + struct sk_buff *skb =3D priv->can.echo_skb[0]; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_DELIVER, + .sel =3D NCT6694_CANFD_DELIVER_SEL(1), + .len =3D cpu_to_le16(sizeof(*frame)) + }; + u32 txid; + int err; + + memset(frame, 0, sizeof(*frame)); + + frame->tag =3D NCT6694_CANFD_FRAME_TAG(ndev->dev_port); + + if (can_is_canfd_skb(skb)) { + struct canfd_frame *cfd =3D (struct canfd_frame *)skb->data; + + if (cfd->flags & CANFD_BRS) + frame->flag |=3D NCT6694_CANFD_FRAME_FLAG_BRS; + + if (cfd->can_id & CAN_EFF_FLAG) { + txid =3D cfd->can_id & CAN_EFF_MASK; + frame->flag |=3D NCT6694_CANFD_FRAME_FLAG_EFF; + } else { + txid =3D cfd->can_id & CAN_SFF_MASK; + } + frame->flag |=3D NCT6694_CANFD_FRAME_FLAG_FD; + frame->id =3D cpu_to_le32(txid); + frame->length =3D canfd_sanitize_len(cfd->len); + + memcpy(frame->data, cfd->data, frame->length); + } else { + struct can_frame *cf =3D (struct can_frame *)skb->data; + + if (cf->can_id & CAN_EFF_FLAG) { + txid =3D cf->can_id & CAN_EFF_MASK; + frame->flag |=3D NCT6694_CANFD_FRAME_FLAG_EFF; + } else { + txid =3D cf->can_id & CAN_SFF_MASK; + } + + if (cf->can_id & CAN_RTR_FLAG) + frame->flag |=3D NCT6694_CANFD_FRAME_FLAG_RTR; + else + memcpy(frame->data, cf->data, cf->len); + + frame->id =3D cpu_to_le32(txid); + frame->length =3D cf->len; + } + + err =3D nct6694_write_msg(priv->nct6694, &cmd_hd, frame); + if (err) { + can_free_echo_skb(ndev, 0, NULL); + stats->tx_dropped++; + stats->tx_errors++; + netif_wake_queue(ndev); + } +} + +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + + if (can_dev_dropped_skb(ndev, skb)) + return NETDEV_TX_OK; + + netif_stop_queue(ndev); + can_put_echo_skb(skb, ndev, 0, 0); + queue_work(priv->wq, &priv->tx_work); + + return NETDEV_TX_OK; +} + +static int nct6694_canfd_start(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + const struct can_bittiming *n_bt =3D &priv->can.bittiming; + const struct can_bittiming *d_bt =3D &priv->can.fd.data_bittiming; + struct nct6694_canfd_setting *setting __free(kfree) =3D NULL; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_SETTING, + .sel =3D ndev->dev_port, + .len =3D cpu_to_le16(sizeof(*setting)) + }; + u32 en_tdc; + int ret; + + setting =3D kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) + return -ENOMEM; + + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + setting->ctrl1 |=3D cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON); + + if (priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) + setting->ctrl1 |=3D cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_NISO); + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + setting->ctrl1 |=3D cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_LBCK); + + /* Disable clock divider */ + setting->ctrl2 =3D 0; + + setting->nbtp =3D cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NSJW, + n_bt->sjw - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NBRP, + n_bt->brp - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG2, + n_bt->phase_seg2 - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_NBTP_NTSEG1, + n_bt->prop_seg + n_bt->phase_seg1 - 1)); + + if (d_bt->brp <=3D 2) + en_tdc =3D NCT6694_CANFD_SETTING_DBTP_TDC; + else + en_tdc =3D 0; + + setting->dbtp =3D cpu_to_le32(FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DSJW, + d_bt->sjw - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DBRP, + d_bt->brp - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG2, + d_bt->phase_seg2 - 1) | + FIELD_PREP(NCT6694_CANFD_SETTING_DBTP_DTSEG1, + d_bt->prop_seg + d_bt->phase_seg1 - 1) | + en_tdc); + + setting->active =3D NCT6694_CANFD_SETTING_ACTIVE_CTRL1 | + NCT6694_CANFD_SETTING_ACTIVE_CTRL2 | + NCT6694_CANFD_SETTING_ACTIVE_NBTP_DBTP; + + ret =3D nct6694_write_msg(priv->nct6694, &cmd_hd, setting); + if (ret) + return ret; + + priv->can.state =3D CAN_STATE_ERROR_ACTIVE; + + return 0; +} + +static void nct6694_canfd_stop(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + struct nct6694_canfd_setting *setting __free(kfree) =3D NULL; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_SETTING, + .sel =3D ndev->dev_port, + .len =3D cpu_to_le16(sizeof(*setting)) + }; + + /* The NCT6694 cannot be stopped. To ensure safe operation and avoid + * interference, the control mode is set to Listen-Only mode. This + * mode allows the device to monitor bus activity without actively + * participating in communication. + */ + setting =3D kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) + return; + + nct6694_read_msg(priv->nct6694, &cmd_hd, setting); + setting->ctrl1 =3D cpu_to_le16(NCT6694_CANFD_SETTING_CTRL1_MON); + setting->active =3D NCT6694_CANFD_SETTING_ACTIVE_CTRL1; + nct6694_write_msg(priv->nct6694, &cmd_hd, setting); + + priv->can.state =3D CAN_STATE_STOPPED; +} + +static int nct6694_canfd_close(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + + netif_stop_queue(ndev); + nct6694_canfd_stop(ndev); + destroy_workqueue(priv->wq); + free_irq(ndev->irq, ndev); + can_rx_offload_disable(&priv->offload); + close_candev(ndev); + return 0; +} + +static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode m= ode) +{ + int ret; + + switch (mode) { + case CAN_MODE_START: + ret =3D nct6694_canfd_start(ndev); + if (ret) + return ret; + + netif_wake_queue(ndev); + break; + + default: + return -EOPNOTSUPP; + } + + return ret; +} + +static int nct6694_canfd_open(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv =3D netdev_priv(ndev); + int ret; + + ret =3D open_candev(ndev); + if (ret) + return ret; + + can_rx_offload_enable(&priv->offload); + + ret =3D request_threaded_irq(ndev->irq, NULL, + nct6694_canfd_irq, IRQF_ONESHOT, + "nct6694_canfd", ndev); + if (ret) { + netdev_err(ndev, "Failed to request IRQ\n"); + goto can_rx_offload_disable; + } + + priv->wq =3D alloc_ordered_workqueue("%s-nct6694_wq", + WQ_FREEZABLE | WQ_MEM_RECLAIM, + ndev->name); + if (!priv->wq) { + ret =3D -ENOMEM; + goto free_irq; + } + + ret =3D nct6694_canfd_start(ndev); + if (ret) + goto destroy_wq; + + netif_start_queue(ndev); + + return 0; + +destroy_wq: + destroy_workqueue(priv->wq); +free_irq: + free_irq(ndev->irq, ndev); +can_rx_offload_disable: + can_rx_offload_disable(&priv->offload); + close_candev(ndev); + return ret; +} + +static const struct net_device_ops nct6694_canfd_netdev_ops =3D { + .ndo_open =3D nct6694_canfd_open, + .ndo_stop =3D nct6694_canfd_close, + .ndo_start_xmit =3D nct6694_canfd_start_xmit, + .ndo_change_mtu =3D can_change_mtu, +}; + +static const struct ethtool_ops nct6694_canfd_ethtool_ops =3D { + .get_ts_info =3D ethtool_op_get_ts_info, +}; + +static int nct6694_canfd_get_clock(struct nct6694_canfd_priv *priv) +{ + struct nct6694_canfd_information *info __free(kfree) =3D NULL; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_CANFD_MOD, + .cmd =3D NCT6694_CANFD_INFORMATION, + .sel =3D NCT6694_CANFD_INFORMATION_SEL, + .len =3D cpu_to_le16(sizeof(*info)) + }; + int ret; + + info =3D kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret =3D nct6694_read_msg(priv->nct6694, &cmd_hd, info); + if (ret) + return ret; + + return le32_to_cpu(info->can_clk); +} + +static int nct6694_canfd_probe(struct platform_device *pdev) +{ + struct nct6694 *nct6694 =3D dev_get_drvdata(pdev->dev.parent); + struct nct6694_canfd_priv *priv; + struct net_device *ndev; + int port, irq, ret, can_clk; + + port =3D ida_alloc(&nct6694->canfd_ida, GFP_KERNEL); + if (port < 0) + return port; + + irq =3D irq_create_mapping(nct6694->domain, + NCT6694_IRQ_CAN0 + port); + if (!irq) { + ret =3D -EINVAL; + goto free_ida; + } + + ndev =3D alloc_candev(sizeof(struct nct6694_canfd_priv), 1); + if (!ndev) { + ret =3D -ENOMEM; + goto dispose_irq; + } + + ndev->irq =3D irq; + ndev->flags |=3D IFF_ECHO; + ndev->dev_port =3D port; + ndev->netdev_ops =3D &nct6694_canfd_netdev_ops; + ndev->ethtool_ops =3D &nct6694_canfd_ethtool_ops; + + priv =3D netdev_priv(ndev); + priv->nct6694 =3D nct6694; + priv->ndev =3D ndev; + + can_clk =3D nct6694_canfd_get_clock(priv); + if (can_clk < 0) { + ret =3D dev_err_probe(&pdev->dev, can_clk, + "Failed to get clock\n"); + goto free_candev; + } + + INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work); + + priv->can.clock.freq =3D can_clk; + priv->can.bittiming_const =3D &nct6694_canfd_bittiming_nominal_const; + priv->can.fd.data_bittiming_const =3D &nct6694_canfd_bittiming_data_const; + priv->can.do_set_mode =3D nct6694_canfd_set_mode; + priv->can.do_get_berr_counter =3D nct6694_canfd_get_berr_counter; + priv->can.ctrlmode_supported =3D CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_BERR_REPORTING | + CAN_CTRLMODE_FD_NON_ISO; + + ret =3D can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD); + if (ret) + goto free_candev; + + ret =3D can_rx_offload_add_manual(ndev, &priv->offload, + NCT6694_NAPI_WEIGHT); + if (ret) { + dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n"); + goto free_candev; + } + + platform_set_drvdata(pdev, priv); + SET_NETDEV_DEV(priv->ndev, &pdev->dev); + + ret =3D register_candev(priv->ndev); + if (ret) + goto rx_offload_del; + + return 0; + +rx_offload_del: + can_rx_offload_del(&priv->offload); +free_candev: + free_candev(ndev); +dispose_irq: + irq_dispose_mapping(irq); +free_ida: + ida_free(&nct6694->canfd_ida, port); + return ret; +} + +static void nct6694_canfd_remove(struct platform_device *pdev) +{ + struct nct6694_canfd_priv *priv =3D platform_get_drvdata(pdev); + struct nct6694 *nct6694 =3D priv->nct6694; + struct net_device *ndev =3D priv->ndev; + int port =3D ndev->dev_port; + int irq =3D ndev->irq; + + unregister_candev(ndev); + can_rx_offload_del(&priv->offload); + free_candev(ndev); + irq_dispose_mapping(irq); + ida_free(&nct6694->canfd_ida, port); +} + +static struct platform_driver nct6694_canfd_driver =3D { + .driver =3D { + .name =3D DEVICE_NAME, + }, + .probe =3D nct6694_canfd_probe, + .remove =3D nct6694_canfd_remove, +}; + +module_platform_driver(nct6694_canfd_driver); + +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pf1-f178.google.com (mail-pf1-f178.google.com [209.85.210.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8BE232DAFB1 for ; Fri, 12 Sep 2025 09:20:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668838; cv=none; b=oovnr/jqp0LiKe5bgJe6kQh9L9zgNFba+JLVtMNxgH0yOW/BiR2Lk+E7PrC1Qst6b8Ye3vxS9DfDVS18hPp7oibhqEKb6WpE7S3nS6oLQFxh496XR9hXgKfXUPGsO89X8k5qGbeR3RGDLBK4scZJua0qt0fzu+Y5mXw6kHB6Ao0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668838; c=relaxed/simple; bh=XHDjW/wNxNefieirIku9nqenuGi3hugPiqlbiOGBXww=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=T1BAVOienyCXInCDZLFNtxs6DWsrtJh3JsHcx42Eun18tDSsjwIKHZ7SPT607lOjV6hMReKUdjzInVcF4K29YlR+x7fTkHJd0010qCZqVj8Ue/77bg6f9GCyU7H+rDXxDEs1PiLI6v+vM6XLlXuUS5sS/VpLUkIz//8ZheYKEsU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Q71QeADW; arc=none smtp.client-ip=209.85.210.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Q71QeADW" Received: by mail-pf1-f178.google.com with SMTP id d2e1a72fcca58-7761b392d50so113514b3a.0 for ; Fri, 12 Sep 2025 02:20:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668835; x=1758273635; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Sou0t32baZGB/gx7eFQ0Qu1pCOQaf+XXBy5bTuaz/cA=; b=Q71QeADWDL5KPRP7clMcdShl3XIJcuFkhj0sBFF/ErBiQwQxo3QMBv62fHFEE8lbmi hv4/fKp3j/e15wj0+3fDN4ZXTTjvIQ29xDiWA8IBh3ULVx7NiHVjIW3rGCRB603Mx9jv WAEN6QIvEt3jtfZhrOhuHg/jaJxp7F2du3SDEONPi52Iq3xe9UEofSzUCn7JQtE+9Js4 n7xEwF/80XiDDouWX8LOcuv0yKyOWfH8GX1L7nMl4gLSHdMzTReJpQGNdiwhinYB+E7J MLtOFrqOlBhJDs/hn6K7ZmIUhEsrrbBBlEjrXLdoEdzBwvE362j/6KjvKOvpw+X3dD/N +APQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668835; x=1758273635; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Sou0t32baZGB/gx7eFQ0Qu1pCOQaf+XXBy5bTuaz/cA=; b=Rop7UTdumnZoxw2VEiv8PPRSjcEMtdY/msqA6ZN74TLBoCFsYkNki6G1gJDGMMHJwa dFtb4+l8FZoKiUEo62ZYN5ZRHHbd5NADajVzWxldbC8TVJpHZNFYaglsYYG6Ip7ny5JP IfgfWRyJMp2u+mXo4IAMPLgS9rRlWRxsKdiddh0EXZngKhxChS2QQOvRrqqa8mVjCfSO au1eeoxyA847ZKSGYJlTKC1I2y3tHSHnC1RAQ2sq8dqm3ZNGRRuc+J26VYDVFXi2QvkN R9YQV5KVS7KGSi9VNwNBM2ZGHURSRMlHZ3XBd5kSt65hGqT7WSAi+S9y6NJj4n2GCaZf 7f4g== X-Gm-Message-State: AOJu0YzrUrT7nwX/7Yrh38tgOo7y6m4LjoIRnB6GvnbuUinEd9RuYq+Q LVxyr2dxulFJAP93LphS/g0/XXPPOfcgbxbxl5FfHnKPJIhzEzCZTPSd X-Gm-Gg: ASbGncuqscMjaumVFRIbHL9VyBcOD4eFm24FTQsIctPzywEJTNxB9H2Aura9KTnqTF9 fPyleeCYc59UAV+ITmLbijrtYTOpVPrV0fQzVARIBKL4rVCDIOVdaoyn8chBzEMgXHE2rkBQvqa uDbDblc/M+stnG09EtJkJ2O0kAxjaWA1/w/MUiR47e1nlCft0rjJlZRxNgUgrBBk7Lr4jv9PJQS YwTMXXnItV5RT5N2NeMmZuZ6z9OxRZyjnn3BSoWkhDCx30VcXgML3T/1O47llE0uSTyaM8RyEgQ d5C5D4TTEZLbLqGu7svR0D9UluzncYUQAg2U9lzWrH9+xBrA/5td0du2wcrOSchBq7paqgVfhoq qtOAiyH0s+/iW3FgVsonbg45nXv3l9r25SHN0G8VRpZ1RcJXvnKy3y+0TGSmSOlAhvOsIN1UDd4 PU X-Google-Smtp-Source: AGHT+IFarZaqBIOCDVhUTwGs0hcK3RrcME2XeoEtQ9swwbf34sveLAXrrteQfQV6TYp/Cz0QEmm6aA== X-Received: by 2002:a05:6a20:e293:b0:24c:1f78:1803 with SMTP id adf61e73a8af0-2602c04a6a1mr3027944637.38.1757668834551; Fri, 12 Sep 2025 02:20:34 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:34 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu Subject: [PATCH RESEND v14 5/7] watchdog: Add Nuvoton NCT6694 WDT support Date: Fri, 12 Sep 2025 17:19:50 +0800 Message-Id: <20250912091952.1169369-6-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports Watchdog timer functionality for NCT6694 MFD device based on USB interface. Acked-by: Guenter Roeck Signed-off-by: Ming Yu --- Changes since version 13: Changes since version 12: - Implement IDA in MFD driver to handle per-device IDs - Use same email address in the signaturei Changes since version 11: - Use platform_device's id to replace IDA Changes since version 10: - Implement IDA to allocate id - Add module parameters to configure WDT's timeout and pretimeout value Changes since version 9: Changes since version 8: - Modify the signed-off-by with my work address Changes since version 7: - Add error handling for devm_mutex_init() Changes since version 6: - Fix warning Changes since version 5: - Modify the module name and the driver name consistently Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Modify mutex_init() to devm_mutex_init() - Drop watchdog_init_timeout() Changes since version 2: - Add MODULE_ALIAS() - Modify the pretimeout validation procedure Changes since version 1: - Add each driver's command structure - Fix platform driver registration - Fix warnings - Drop unnecessary logs - Modify start() function to setup device MAINTAINERS | 1 + drivers/watchdog/Kconfig | 11 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/nct6694_wdt.c | 307 +++++++++++++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 drivers/watchdog/nct6694_wdt.c diff --git a/MAINTAINERS b/MAINTAINERS index 758c9a67184e..4639d5933c5e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18089,6 +18089,7 @@ F: drivers/gpio/gpio-nct6694.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/usb/nct6694_canfd.c +F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h =20 NUVOTON NCT7201 IIO DRIVER diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0c25b2ed44eb..05008d937e40 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -760,6 +760,17 @@ config MAX77620_WATCHDOG MAX77620 chips. To compile this driver as a module, choose M here: the module will be called max77620_wdt. =20 +config NCT6694_WATCHDOG + tristate "Nuvoton NCT6694 watchdog support" + depends on MFD_NCT6694 + select WATCHDOG_CORE + help + Say Y here to support Nuvoton NCT6694 watchdog timer + functionality. + + This driver can also be built as a module. If so, the module + will be called nct6694_wdt. + config IMX2_WDT tristate "IMX2+ Watchdog" depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index bbd4d62d2cc3..b680e4d3c1bc 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -235,6 +235,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) +=3D wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) +=3D wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) +=3D max63xx_wdt.o obj-$(CONFIG_MAX77620_WATCHDOG) +=3D max77620_wdt.o +obj-$(CONFIG_NCT6694_WATCHDOG) +=3D nct6694_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) +=3D ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) +=3D softdog.o obj-$(CONFIG_MENF21BMC_WATCHDOG) +=3D menf21bmc_wdt.o diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c new file mode 100644 index 000000000000..bc3689bd4b6b --- /dev/null +++ b/drivers/watchdog/nct6694_wdt.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 WDT driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "nct6694-wdt" + +#define NCT6694_DEFAULT_TIMEOUT 10 +#define NCT6694_DEFAULT_PRETIMEOUT 0 + +#define NCT6694_WDT_MAX_DEVS 2 + +/* + * USB command module type for NCT6694 WDT controller. + * This defines the module type used for communication with the NCT6694 + * WDT controller over the USB interface. + */ +#define NCT6694_WDT_MOD 0x07 + +/* Command 00h - WDT Setup */ +#define NCT6694_WDT_SETUP 0x00 +#define NCT6694_WDT_SETUP_SEL(idx) (idx ? 0x01 : 0x00) + +/* Command 01h - WDT Command */ +#define NCT6694_WDT_COMMAND 0x01 +#define NCT6694_WDT_COMMAND_SEL(idx) (idx ? 0x01 : 0x00) + +static unsigned int timeout[NCT6694_WDT_MAX_DEVS] =3D { + [0 ... (NCT6694_WDT_MAX_DEVS - 1)] =3D NCT6694_DEFAULT_TIMEOUT +}; +module_param_array(timeout, int, NULL, 0644); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); + +static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] =3D { + [0 ... (NCT6694_WDT_MAX_DEVS - 1)] =3D NCT6694_DEFAULT_PRETIMEOUT +}; +module_param_array(pretimeout, int, NULL, 0644); +MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds"); + +static bool nowayout =3D WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (defau= lt=3D" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +enum { + NCT6694_ACTION_NONE =3D 0, + NCT6694_ACTION_SIRQ, + NCT6694_ACTION_GPO, +}; + +struct __packed nct6694_wdt_setup { + __le32 pretimeout; + __le32 timeout; + u8 owner; + u8 scratch; + u8 control; + u8 status; + __le32 countdown; +}; + +struct __packed nct6694_wdt_cmd { + __le32 wdt_cmd; + __le32 reserved; +}; + +union __packed nct6694_wdt_msg { + struct nct6694_wdt_setup setup; + struct nct6694_wdt_cmd cmd; +}; + +struct nct6694_wdt_data { + struct watchdog_device wdev; + struct device *dev; + struct nct6694 *nct6694; + union nct6694_wdt_msg *msg; + unsigned char wdev_idx; +}; + +static int nct6694_wdt_setting(struct watchdog_device *wdev, + u32 timeout_val, u8 timeout_act, + u32 pretimeout_val, u8 pretimeout_act) +{ + struct nct6694_wdt_data *data =3D watchdog_get_drvdata(wdev); + struct nct6694_wdt_setup *setup =3D &data->msg->setup; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_WDT_MOD, + .cmd =3D NCT6694_WDT_SETUP, + .sel =3D NCT6694_WDT_SETUP_SEL(data->wdev_idx), + .len =3D cpu_to_le16(sizeof(*setup)) + }; + unsigned int timeout_fmt, pretimeout_fmt; + + if (pretimeout_val =3D=3D 0) + pretimeout_act =3D NCT6694_ACTION_NONE; + + timeout_fmt =3D (timeout_val * 1000) | (timeout_act << 24); + pretimeout_fmt =3D (pretimeout_val * 1000) | (pretimeout_act << 24); + + memset(setup, 0, sizeof(*setup)); + setup->timeout =3D cpu_to_le32(timeout_fmt); + setup->pretimeout =3D cpu_to_le32(pretimeout_fmt); + + return nct6694_write_msg(data->nct6694, &cmd_hd, setup); +} + +static int nct6694_wdt_start(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data =3D watchdog_get_drvdata(wdev); + int ret; + + ret =3D nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO, + wdev->pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + dev_dbg(data->dev, "Setting WDT(%d): timeout =3D %d, pretimeout =3D %d\n", + data->wdev_idx, wdev->timeout, wdev->pretimeout); + + return ret; +} + +static int nct6694_wdt_stop(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data =3D watchdog_get_drvdata(wdev); + struct nct6694_wdt_cmd *cmd =3D &data->msg->cmd; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_WDT_MOD, + .cmd =3D NCT6694_WDT_COMMAND, + .sel =3D NCT6694_WDT_COMMAND_SEL(data->wdev_idx), + .len =3D cpu_to_le16(sizeof(*cmd)) + }; + + memcpy(&cmd->wdt_cmd, "WDTC", 4); + cmd->reserved =3D 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, cmd); +} + +static int nct6694_wdt_ping(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data =3D watchdog_get_drvdata(wdev); + struct nct6694_wdt_cmd *cmd =3D &data->msg->cmd; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_WDT_MOD, + .cmd =3D NCT6694_WDT_COMMAND, + .sel =3D NCT6694_WDT_COMMAND_SEL(data->wdev_idx), + .len =3D cpu_to_le16(sizeof(*cmd)) + }; + + memcpy(&cmd->wdt_cmd, "WDTS", 4); + cmd->reserved =3D 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, cmd); +} + +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev, + unsigned int new_timeout) +{ + int ret; + + ret =3D nct6694_wdt_setting(wdev, new_timeout, NCT6694_ACTION_GPO, + wdev->pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + wdev->timeout =3D new_timeout; + + return 0; +} + +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev, + unsigned int new_pretimeout) +{ + int ret; + + ret =3D nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO, + new_pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + wdev->pretimeout =3D new_pretimeout; + + return 0; +} + +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data =3D watchdog_get_drvdata(wdev); + struct nct6694_wdt_setup *setup =3D &data->msg->setup; + const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_WDT_MOD, + .cmd =3D NCT6694_WDT_SETUP, + .sel =3D NCT6694_WDT_SETUP_SEL(data->wdev_idx), + .len =3D cpu_to_le16(sizeof(*setup)) + }; + unsigned int timeleft_ms; + int ret; + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, setup); + if (ret) + return 0; + + timeleft_ms =3D le32_to_cpu(setup->countdown); + + return timeleft_ms / 1000; +} + +static const struct watchdog_info nct6694_wdt_info =3D { + .options =3D WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, + .identity =3D DEVICE_NAME, +}; + +static const struct watchdog_ops nct6694_wdt_ops =3D { + .owner =3D THIS_MODULE, + .start =3D nct6694_wdt_start, + .stop =3D nct6694_wdt_stop, + .set_timeout =3D nct6694_wdt_set_timeout, + .set_pretimeout =3D nct6694_wdt_set_pretimeout, + .get_timeleft =3D nct6694_wdt_get_time, + .ping =3D nct6694_wdt_ping, +}; + +static void nct6694_wdt_ida_free(void *d) +{ + struct nct6694_wdt_data *data =3D d; + struct nct6694 *nct6694 =3D data->nct6694; + + ida_free(&nct6694->wdt_ida, data->wdev_idx); +} + +static int nct6694_wdt_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct nct6694 *nct6694 =3D dev_get_drvdata(dev->parent); + struct nct6694_wdt_data *data; + struct watchdog_device *wdev; + int ret; + + data =3D devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->msg =3D devm_kzalloc(dev, sizeof(union nct6694_wdt_msg), + GFP_KERNEL); + if (!data->msg) + return -ENOMEM; + + data->dev =3D dev; + data->nct6694 =3D nct6694; + + ret =3D ida_alloc(&nct6694->wdt_ida, GFP_KERNEL); + if (ret < 0) + return ret; + data->wdev_idx =3D ret; + + ret =3D devm_add_action_or_reset(dev, nct6694_wdt_ida_free, data); + if (ret) + return ret; + + wdev =3D &data->wdev; + wdev->info =3D &nct6694_wdt_info; + wdev->ops =3D &nct6694_wdt_ops; + wdev->timeout =3D timeout[data->wdev_idx]; + wdev->pretimeout =3D pretimeout[data->wdev_idx]; + if (timeout[data->wdev_idx] < pretimeout[data->wdev_idx]) { + dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n"); + wdev->pretimeout =3D 0; + } + + wdev->min_timeout =3D 1; + wdev->max_timeout =3D 255; + + platform_set_drvdata(pdev, data); + + watchdog_set_drvdata(&data->wdev, data); + watchdog_set_nowayout(&data->wdev, nowayout); + watchdog_stop_on_reboot(&data->wdev); + + return devm_watchdog_register_device(dev, &data->wdev); +} + +static struct platform_driver nct6694_wdt_driver =3D { + .driver =3D { + .name =3D DEVICE_NAME, + }, + .probe =3D nct6694_wdt_probe, +}; + +module_platform_driver(nct6694_wdt_driver); + +MODULE_DESCRIPTION("USB-WDT driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-wdt"); --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 62FEF2DCF55 for ; Fri, 12 Sep 2025 09:20:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668844; cv=none; b=csFo3tyXnQwvqkMzwe3J0gF+8GDlp3DtHnmF4dbmT9rAt86n5DYSBaRx5GcrP8HwTZI5GuPXKI2o8DNKC2zQi1zkzY+/OsQ03S40e5qA6eQbznX3pYmrqHN+J6V50kDWbBUwEkS8ki5Ecp/cqD1o3kVJqZc6yOmYgqpmY5s30bI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668844; c=relaxed/simple; bh=BcdbO04+6XlX06T3x3N5ak9Umf/0mTwibjGYRx4+cZI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=BGlnQ1Jh3+Z3etcJPKubutTO3MlMRTV/NbKEY7zE23i85mbx77KTdqWacq3IgPDdyqY1PPzeRi4E9OEr8BAsG2hB8vGOZzT2+pPaop/sI4Wfh6T9edqA9ueNr4IIdj2lx47wbVFt971sWggr6u44RxKb9l92WJyEmM7fj9a2uWA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=LsBYiomh; arc=none smtp.client-ip=209.85.210.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="LsBYiomh" Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-7724cacc32bso1421075b3a.0 for ; Fri, 12 Sep 2025 02:20:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668839; x=1758273639; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bKwsSmqa4n2KxosM4t23OoTGRweStl/nH9+WGWRW/Hk=; b=LsBYiomhKhPDoSmRmSW760sFZkzH3tOhrwq3jCIJNaCswlQpyVxJWwN9R644nkxLb4 VVTbfI+pvqmZ0RMmfOQoV1GTMUPbQ1gNAXWXQWojoFun5Yw5uHQUwEzm3gKbH6Yqe3VB HAI1PIUaDodk48W4zFqaUjbzaxlGQrdT3FxtYTYWswyyfprnGiQ/1gEH1Dcqqu5lwtAZ 0XbZcd9hRGk4zZtgBYTLl94NnHE6rdRoqSlrJMKnNQ7JSyUkNeSq1xu7vUy3bIUnTkt1 vG1H+VqoJSRplE+thvqkbVtc5gMg71P6awXkYrGvWZ98tIcurbtYwYKsND+4q+RTDKxd ucqQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668839; x=1758273639; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=bKwsSmqa4n2KxosM4t23OoTGRweStl/nH9+WGWRW/Hk=; b=Mc6+8ioz3+qISdWjdOb7vsuibkWTLt6iY6tTjLO4Q5Pa8keYmXgynzBxIRBxa1RJ0d POfOKbx851ia+7KjzscG64+rchlDYFGHySKyGt7nc5mBlH+2JwKIr1GA4W9xVqHQpWVU x2+qUzM4czirvxwkFik/61BSYMRJ+z1kBKGQ2590697dtP0qwxpSWUF9hJAIOwSwyZ4j 0AQK1iV29HJd0kO8To2yatOud8ABLq77nYsjDHZxuHKxhcPnntqWTYNtTan3UPHuV61U +e31BoWn05PGrdMqktsqIVHEYDQulinDRVTGHUkCdljADB3VgsKHX1+Y1b7MQFYbWFBr 1eow== X-Gm-Message-State: AOJu0Yw2PxhHx4Ycx80+cG3xxI+k6Wly3ckn5pDzjUqlIFwLCaqgIT64 qbeX6e0eGB/iqFi9a6Tpv+A/N22Tz7smC7BMuEhn673PKcjs7L6mg/Qg X-Gm-Gg: ASbGnctGTB73UDnz5iAIGgZpCq2kv15M8vlQIScueL8A4UF17BcP0aoumJK6fH8geSj zaHa1cSgq3OWbg9flR3f3k/HqNzSm9qEE07pCc/73P+nY+fluNnkC9nrA9qVuin6AWR5m7ubcF9 2/NVSYr5Li57zQstQ35tXFG9d7jXXbP/xh5bx9sEv7k3YjSBBfKKhY1TYoxrL5wmiHYTUym4gj2 6L8rjIvkjM0QHzh1F9iB+0UEeGgxkHNXw/uFzQrCLK3ZaPG36frZjDOoZsMS+Ldvfz16/S9ngCb PGMWz8thfrRrMov9Q/V/o4Z06HMGJkMWXpNM867jROfOdN6S6qSpsA5ZSJTVagtOjSJ2sNd1CJq SBQ2lZOssI/iPSrXYB1CFYu5nhjcvfCn93j+qzKUvb64rPi8Td9SOWsO7zYqa5k/t/oGcIjq90B GG X-Google-Smtp-Source: AGHT+IHvpU0r7bXjBYfFJu1uMfNiYVCqkgf6LEBGhK1+qPsZ8JpTxjVs4C8Htj8kAcO2ZksuWth0Xg== X-Received: by 2002:a05:6a20:2451:b0:244:58:c147 with SMTP id adf61e73a8af0-2602aa89dddmr2885571637.21.1757668838591; Fri, 12 Sep 2025 02:20:38 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:38 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu Subject: [PATCH RESEND v14 6/7] hwmon: Add Nuvoton NCT6694 HWMON support Date: Fri, 12 Sep 2025 17:19:51 +0800 Message-Id: <20250912091952.1169369-7-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports Hardware monitor functionality for NCT6694 MFD device based on USB interface. Reviewed-by: Guenter Roeck Signed-off-by: Ming Yu --- Changes since version 13: Changes since version 12: - Use same email address in the signature Changes since version 11: - Fix the typo in the header Changes since version 10: Changes since version 9: Changes since version 8: - Modify the signed-off-by with my work address Changes since version 7: - Add error handling for devm_mutex_init() Changes since version 6: - Fix temp_hyst's data type to signed variable Changes since version 5: - Modify the module name and the driver name consistently Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function - Fix warngings Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Modify mutex_init() to devm_mutex_init() - Modify the division method to DIV_ROUND_CLOSEST() Changes since version 2: - Add MODULE_ALIAS() - Fix warnings Changes since version 1: - Add each driver's command structure - Fix platform driver registration - Add voltage sensors functionality - Add temperature sensors functionality - Fix overwrite error return values - Add write value limitation for each write() function MAINTAINERS | 1 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6694-hwmon.c | 949 ++++++++++++++++++++++++++++++++++ 4 files changed, 961 insertions(+) create mode 100644 drivers/hwmon/nct6694-hwmon.c diff --git a/MAINTAINERS b/MAINTAINERS index 4639d5933c5e..bbacc9d48a83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18086,6 +18086,7 @@ NUVOTON NCT6694 MFD DRIVER M: Ming Yu S: Supported F: drivers/gpio/gpio-nct6694.c +F: drivers/hwmon/nct6694-hwmon.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/usb/nct6694_canfd.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9d28fcf7cd2a..19f660d9a0c5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1698,6 +1698,16 @@ config SENSORS_NCT6683 This driver can also be built as a module. If so, the module will be called nct6683. =20 +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 cd8bc4752b4d..9bce91611dc3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -174,6 +174,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) +=3D mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) +=3D menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) +=3D mr75203.o obj-$(CONFIG_SENSORS_NCT6683) +=3D nct6683.o +obj-$(CONFIG_SENSORS_NCT6694) +=3D nct6694-hwmon.o obj-$(CONFIG_SENSORS_NCT6775_CORE) +=3D nct6775-core.o nct6775-objs :=3D nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) +=3D nct6775.o diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c new file mode 100644 index 000000000000..6dcf22ca5018 --- /dev/null +++ b/drivers/hwmon/nct6694-hwmon.c @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 HWMON driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * USB command module type for NCT6694 report channel + * This defines the module type used for communication with the NCT6694 + * report channel over the USB interface. + */ +#define NCT6694_RPT_MOD 0xFF + +/* Report channel */ +/* + * The report channel is used to report the status of the hardware monitor + * devices, such as voltage, temperature, fan speed, and PWM. + */ +#define NCT6694_VIN_IDX(x) (0x00 + (x)) +#define NCT6694_TIN_IDX(x) \ + ({ typeof(x) (_x) =3D (x); \ + ((_x) < 10) ? (0x10 + ((_x) * 2)) : \ + (0x30 + (((_x) - 10) * 2)); }) +#define NCT6694_FIN_IDX(x) (0x50 + ((x) * 2)) +#define NCT6694_PWM_IDX(x) (0x70 + (x)) +#define NCT6694_VIN_STS(x) (0x68 + (x)) +#define NCT6694_TIN_STS(x) (0x6A + (x)) +#define NCT6694_FIN_STS(x) (0x6E + (x)) + +/* + * USB command module type for NCT6694 HWMON controller. + * This defines the module type used for communication with the NCT6694 + * HWMON controller over the USB interface. + */ +#define NCT6694_HWMON_MOD 0x00 + +/* Command 00h - Hardware Monitor Control */ +#define NCT6694_HWMON_CONTROL 0x00 +#define NCT6694_HWMON_CONTROL_SEL 0x00 + +/* Command 02h - Alarm Control */ +#define NCT6694_HWMON_ALARM 0x02 +#define NCT6694_HWMON_ALARM_SEL 0x00 + +/* + * USB command module type for NCT6694 PWM controller. + * This defines the module type used for communication with the NCT6694 + * PWM controller over the USB interface. + */ +#define NCT6694_PWM_MOD 0x01 + +/* PWM Command - Manual Control */ +#define NCT6694_PWM_CONTROL 0x01 +#define NCT6694_PWM_CONTROL_SEL 0x00 + +#define NCT6694_FREQ_FROM_REG(reg) ((reg) * 25000 / 255) +#define NCT6694_FREQ_TO_REG(val) \ + (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000)) + +#define NCT6694_LSB_REG_MASK GENMASK(7, 5) +#define NCT6694_TIN_HYST_MASK GENMASK(7, 5) + +enum nct6694_hwmon_temp_mode { + NCT6694_HWMON_TWOTIME_IRQ =3D 0, + NCT6694_HWMON_ONETIME_IRQ, + NCT6694_HWMON_REALTIME_IRQ, + NCT6694_HWMON_COMPARE_IRQ, +}; + +struct __packed nct6694_hwmon_control { + u8 vin_en[2]; + u8 tin_en[2]; + u8 fin_en[2]; + u8 pwm_en[2]; + u8 reserved1[40]; + u8 pwm_freq[10]; + u8 reserved2[6]; +}; + +struct __packed nct6694_hwmon_alarm { + u8 smi_ctrl; + u8 reserved1[15]; + struct { + u8 hl; + u8 ll; + } vin_limit[16]; + struct { + u8 hyst; + s8 hl; + } tin_cfg[32]; + __be16 fin_ll[10]; + u8 reserved2[4]; +}; + +struct __packed nct6694_pwm_control { + u8 mal_en[2]; + u8 mal_val[10]; + u8 reserved[12]; +}; + +union __packed nct6694_hwmon_rpt { + u8 vin; + struct { + u8 msb; + u8 lsb; + } tin; + __be16 fin; + u8 pwm; + u8 status; +}; + +union __packed nct6694_hwmon_msg { + struct nct6694_hwmon_alarm hwmon_alarm; + struct nct6694_pwm_control pwm_ctrl; +}; + +struct nct6694_hwmon_data { + struct nct6694 *nct6694; + struct mutex lock; + struct nct6694_hwmon_control hwmon_en; + union nct6694_hwmon_rpt *rpt; + union nct6694_hwmon_msg *msg; +}; + +static inline long in_from_reg(u8 reg) +{ + return reg * 16; +} + +static inline u8 in_to_reg(long val) +{ + return DIV_ROUND_CLOSEST(val, 16); +} + +static inline long temp_from_reg(s8 reg) +{ + return reg * 1000; +} + +static inline s8 temp_to_reg(long val) +{ + return DIV_ROUND_CLOSEST(val, 1000); +} + +#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE | \ + HWMON_I_MAX | HWMON_I_MIN | \ + HWMON_I_ALARM) +#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \ + HWMON_T_MAX | HWMON_T_MAX_HYST | \ + HWMON_T_MAX_ALARM) +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \ + HWMON_F_MIN | HWMON_F_MIN_ALARM) +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \ + HWMON_PWM_FREQ) +static const struct hwmon_channel_info *nct6694_info[] =3D { + HWMON_CHANNEL_INFO(in, + NCT6694_HWMON_IN_CONFIG, /* VIN0 */ + NCT6694_HWMON_IN_CONFIG, /* VIN1 */ + NCT6694_HWMON_IN_CONFIG, /* VIN2 */ + NCT6694_HWMON_IN_CONFIG, /* VIN3 */ + NCT6694_HWMON_IN_CONFIG, /* VIN5 */ + NCT6694_HWMON_IN_CONFIG, /* VIN6 */ + NCT6694_HWMON_IN_CONFIG, /* VIN7 */ + NCT6694_HWMON_IN_CONFIG, /* VIN14 */ + NCT6694_HWMON_IN_CONFIG, /* VIN15 */ + NCT6694_HWMON_IN_CONFIG, /* VIN16 */ + NCT6694_HWMON_IN_CONFIG, /* VBAT */ + NCT6694_HWMON_IN_CONFIG, /* VSB */ + NCT6694_HWMON_IN_CONFIG, /* AVSB */ + NCT6694_HWMON_IN_CONFIG, /* VCC */ + NCT6694_HWMON_IN_CONFIG, /* VHIF */ + NCT6694_HWMON_IN_CONFIG), /* VTT */ + + HWMON_CHANNEL_INFO(temp, + NCT6694_HWMON_TEMP_CONFIG, /* THR1 */ + NCT6694_HWMON_TEMP_CONFIG, /* THR2 */ + NCT6694_HWMON_TEMP_CONFIG, /* THR14 */ + NCT6694_HWMON_TEMP_CONFIG, /* THR15 */ + NCT6694_HWMON_TEMP_CONFIG, /* THR16 */ + NCT6694_HWMON_TEMP_CONFIG, /* TDP0 */ + NCT6694_HWMON_TEMP_CONFIG, /* TDP1 */ + NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */ + NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */ + NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */ + NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */ + NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */ + + HWMON_CHANNEL_INFO(fan, + NCT6694_HWMON_FAN_CONFIG, /* FIN0 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN1 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN2 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN3 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN4 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN5 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN6 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN7 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN8 */ + NCT6694_HWMON_FAN_CONFIG), /* FIN9 */ + + HWMON_CHANNEL_INFO(pwm, + NCT6694_HWMON_PWM_CONFIG, /* PWM0 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM1 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM2 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM3 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM4 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM5 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM6 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM7 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM8 */ + NCT6694_HWMON_PWM_CONFIG), /* PWM9 */ + NULL +}; + +static int nct6694_in_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + unsigned char vin_en; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_in_enable: + vin_en =3D data->hwmon_en.vin_en[(channel / 8)]; + *val =3D !!(vin_en & BIT(channel % 8)); + + return 0; + case hwmon_in_input: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_VIN_IDX(channel)), + .len =3D cpu_to_le16(sizeof(data->rpt->vin)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->vin); + if (ret) + return ret; + + *val =3D in_from_reg(data->rpt->vin); + + return 0; + case hwmon_in_max: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + *val =3D in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl); + + return 0; + case hwmon_in_min: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + *val =3D in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll); + + return 0; + case hwmon_in_alarm: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_VIN_STS(channel / 8)), + .len =3D cpu_to_le16(sizeof(data->rpt->status)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->status); + if (ret) + return ret; + + *val =3D !!(data->rpt->status & BIT(channel % 8)); + + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_temp_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + unsigned char temp_en, temp_hyst; + signed char temp_max; + int ret, temp_raw; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_temp_enable: + temp_en =3D data->hwmon_en.tin_en[channel / 8]; + *val =3D !!(temp_en & BIT(channel % 8)); + + return 0; + case hwmon_temp_input: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_TIN_IDX(channel)), + .len =3D cpu_to_le16(sizeof(data->rpt->tin)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->tin); + if (ret) + return ret; + + temp_raw =3D data->rpt->tin.msb << 3; + temp_raw |=3D FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb); + + /* Real temperature(milli degrees Celsius) =3D temp_raw * 1000 * 0.125 */ + *val =3D sign_extend32(temp_raw, 10) * 125; + + return 0; + case hwmon_temp_max: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + *val =3D temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl); + + return 0; + case hwmon_temp_max_hyst: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + temp_max =3D data->msg->hwmon_alarm.tin_cfg[channel].hl; + temp_hyst =3D FIELD_GET(NCT6694_TIN_HYST_MASK, + data->msg->hwmon_alarm.tin_cfg[channel].hyst); + *val =3D temp_from_reg(temp_max - temp_hyst); + + return 0; + case hwmon_temp_max_alarm: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_TIN_STS(channel / 8)), + .len =3D cpu_to_le16(sizeof(data->rpt->status)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->status); + if (ret) + return ret; + + *val =3D !!(data->rpt->status & BIT(channel % 8)); + + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_fan_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + unsigned char fanin_en; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_fan_enable: + fanin_en =3D data->hwmon_en.fin_en[channel / 8]; + *val =3D !!(fanin_en & BIT(channel % 8)); + + return 0; + case hwmon_fan_input: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_FIN_IDX(channel)), + .len =3D cpu_to_le16(sizeof(data->rpt->fin)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->fin); + if (ret) + return ret; + + *val =3D be16_to_cpu(data->rpt->fin); + + return 0; + case hwmon_fan_min: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + *val =3D be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]); + + return 0; + case hwmon_fan_min_alarm: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_FIN_STS(channel / 8)), + .len =3D cpu_to_le16(sizeof(data->rpt->status)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->status); + if (ret) + return ret; + + *val =3D !!(data->rpt->status & BIT(channel % 8)); + + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_pwm_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + unsigned char pwm_en; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_pwm_enable: + pwm_en =3D data->hwmon_en.pwm_en[channel / 8]; + *val =3D !!(pwm_en & BIT(channel % 8)); + + return 0; + case hwmon_pwm_input: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_RPT_MOD, + .offset =3D cpu_to_le16(NCT6694_PWM_IDX(channel)), + .len =3D cpu_to_le16(sizeof(data->rpt->pwm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->rpt->pwm); + if (ret) + return ret; + + *val =3D data->rpt->pwm; + + return 0; + case hwmon_pwm_freq: + *val =3D NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]); + + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_in_write(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_in_enable: + if (val =3D=3D 0) + data->hwmon_en.vin_en[channel / 8] &=3D ~BIT(channel % 8); + else if (val =3D=3D 1) + data->hwmon_en.vin_en[channel / 8] |=3D BIT(channel % 8); + else + return -EINVAL; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + case hwmon_in_max: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + val =3D clamp_val(val, 0, 2032); + data->msg->hwmon_alarm.vin_limit[channel].hl =3D in_to_reg(val); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + case hwmon_in_min: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + val =3D clamp_val(val, 0, 2032); + data->msg->hwmon_alarm.vin_limit[channel].ll =3D in_to_reg(val); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_temp_write(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + unsigned char temp_hyst; + signed char temp_max; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_temp_enable: + if (val =3D=3D 0) + data->hwmon_en.tin_en[channel / 8] &=3D ~BIT(channel % 8); + else if (val =3D=3D 1) + data->hwmon_en.tin_en[channel / 8] |=3D BIT(channel % 8); + else + return -EINVAL; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + case hwmon_temp_max: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + val =3D clamp_val(val, -127000, 127000); + data->msg->hwmon_alarm.tin_cfg[channel].hl =3D temp_to_reg(val); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + case hwmon_temp_max_hyst: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + + val =3D clamp_val(val, -127000, 127000); + temp_max =3D data->msg->hwmon_alarm.tin_cfg[channel].hl; + temp_hyst =3D temp_max - temp_to_reg(val); + temp_hyst =3D clamp_val(temp_hyst, 0, 7); + data->msg->hwmon_alarm.tin_cfg[channel].hyst =3D + (data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK)= | + FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_fan_write(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_fan_enable: + if (val =3D=3D 0) + data->hwmon_en.fin_en[channel / 8] &=3D ~BIT(channel % 8); + else if (val =3D=3D 1) + data->hwmon_en.fin_en[channel / 8] |=3D BIT(channel % 8); + else + return -EINVAL; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + case hwmon_fan_min: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + val =3D clamp_val(val, 1, 65535); + data->msg->hwmon_alarm.fin_ll[channel] =3D cpu_to_be16(val); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_pwm_write(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct6694_hwmon_data *data =3D dev_get_drvdata(dev); + struct nct6694_cmd_header cmd_hd; + int ret; + + guard(mutex)(&data->lock); + + switch (attr) { + case hwmon_pwm_enable: + if (val =3D=3D 0) + data->hwmon_en.pwm_en[channel / 8] &=3D ~BIT(channel % 8); + else if (val =3D=3D 1) + data->hwmon_en.pwm_en[channel / 8] |=3D BIT(channel % 8); + else + return -EINVAL; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + case hwmon_pwm_input: + if (val < 0 || val > 255) + return -EINVAL; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_PWM_MOD, + .cmd =3D NCT6694_PWM_CONTROL, + .sel =3D NCT6694_PWM_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->pwm_ctrl)) + }; + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->pwm_ctrl); + if (ret) + return ret; + + data->msg->pwm_ctrl.mal_val[channel] =3D val; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->pwm_ctrl); + case hwmon_pwm_freq: + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + + data->hwmon_en.pwm_freq[channel] =3D NCT6694_FREQ_TO_REG(val); + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + /* in mV */ + return nct6694_in_read(dev, attr, channel, val); + case hwmon_temp: + /* in mC */ + return nct6694_temp_read(dev, attr, channel, val); + case hwmon_fan: + /* in RPM */ + return nct6694_fan_read(dev, attr, channel, val); + case hwmon_pwm: + /* in value 0~255 */ + return nct6694_pwm_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_in: + return nct6694_in_write(dev, attr, channel, val); + case hwmon_temp: + return nct6694_temp_write(dev, attr, channel, val); + case hwmon_fan: + return nct6694_fan_write(dev, attr, channel, val); + case hwmon_pwm: + return nct6694_pwm_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct6694_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_enable: + case hwmon_in_max: + case hwmon_in_min: + return 0644; + case hwmon_in_alarm: + case hwmon_in_input: + return 0444; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + case hwmon_temp_max: + case hwmon_temp_max_hyst: + return 0644; + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + default: + return 0; + } + case hwmon_fan: + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_min: + return 0644; + case hwmon_fan_input: + case hwmon_fan_min_alarm: + return 0444; + default: + return 0; + } + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + case hwmon_pwm_freq: + case hwmon_pwm_input: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_ops nct6694_hwmon_ops =3D { + .is_visible =3D nct6694_is_visible, + .read =3D nct6694_read, + .write =3D nct6694_write, +}; + +static const struct hwmon_chip_info nct6694_chip_info =3D { + .ops =3D &nct6694_hwmon_ops, + .info =3D nct6694_info, +}; + +static int nct6694_hwmon_init(struct nct6694_hwmon_data *data) +{ + struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_CONTROL, + .sel =3D NCT6694_HWMON_CONTROL_SEL, + .len =3D cpu_to_le16(sizeof(data->hwmon_en)) + }; + int ret; + + /* + * Record each Hardware Monitor Channel enable status + * and PWM frequency register + */ + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->hwmon_en); + if (ret) + return ret; + + cmd_hd =3D (struct nct6694_cmd_header) { + .mod =3D NCT6694_HWMON_MOD, + .cmd =3D NCT6694_HWMON_ALARM, + .sel =3D NCT6694_HWMON_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(data->msg->hwmon_alarm)) + }; + + /* Select hwmon device alarm mode */ + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); + if (ret) + return ret; + + data->msg->hwmon_alarm.smi_ctrl =3D NCT6694_HWMON_REALTIME_IRQ; + + return nct6694_write_msg(data->nct6694, &cmd_hd, + &data->msg->hwmon_alarm); +} + +static int nct6694_hwmon_probe(struct platform_device *pdev) +{ + struct nct6694_hwmon_data *data; + struct nct6694 *nct6694 =3D dev_get_drvdata(pdev->dev.parent); + struct device *hwmon_dev; + int ret; + + data =3D devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->rpt =3D devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt), + GFP_KERNEL); + if (!data->rpt) + return -ENOMEM; + + data->msg =3D devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg), + GFP_KERNEL); + if (!data->msg) + return -ENOMEM; + + data->nct6694 =3D nct6694; + ret =3D devm_mutex_init(&pdev->dev, &data->lock); + if (ret) + return ret; + + ret =3D nct6694_hwmon_init(data); + if (ret) + return ret; + + /* Register hwmon device to HWMON framework */ + hwmon_dev =3D devm_hwmon_device_register_with_info(&pdev->dev, + "nct6694", data, + &nct6694_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver nct6694_hwmon_driver =3D { + .driver =3D { + .name =3D "nct6694-hwmon", + }, + .probe =3D nct6694_hwmon_probe, +}; + +module_platform_driver(nct6694_hwmon_driver); + +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-hwmon"); --=20 2.34.1 From nobody Thu Oct 2 19:03:41 2025 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 974AB2DF132 for ; Fri, 12 Sep 2025 09:20:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668846; cv=none; b=X/tDhIU3v64yi17OEo3LGKArJTovk/8Aw+busABzTDLHMDRYkKW9PaqTjhQUpTK9WLA+yamSZtX2UwyH08ceA3JL2kB5y8IQVf+gQTXvQ5/0OxvbHn7+evpMmjt/ownsi7luhgxlMeUWc6ZrbDiENZn5FebU99VmE2y9m772bB8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757668846; c=relaxed/simple; bh=490XaeJOSXOPOipAwlaja6D50/cNV+4mssGhLZMnMJo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=TNZrE/ylNGdMVd1QPoKzKp/keM/fbvOxFY9DEMg87eOCUl5mi271IwDGqNW7udPbMYXG+csyy/YftX0Xp0gQio+RZAVOySWxNyrnUGqhsZJwOWj7cSrk38wnPPoEPoynNYJ9F5OJML5viLhZMTp2Bz8fUvudqSpgm0A32HvsnOM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=UOHOzuUb; arc=none smtp.client-ip=209.85.216.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UOHOzuUb" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-32de096bf8aso1210326a91.2 for ; Fri, 12 Sep 2025 02:20:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757668843; x=1758273643; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=GgDT8yas3IcACX8dpF1Z+jTHl5S85Xg0OLjTZ81TkYA=; b=UOHOzuUb45N4osHau19ITbQhlJ5LHhfyenMkBxeFC25t5i2kVamZ2Qb4JcYIxCyPhv dGebg6l+Hm6h6+5HqKuVvVpcc/1mpsNLPQzs//xh+yjbg40bCui2LLsZO5M7kXXstEd1 LgusTq4e+TvagY27d26bi5V7y3jdYPW1QJ26+ZOrAnLI9Jvdupgdir+DiZzLYw+rvSml bHe7sHly3uti++I+SGw7mous5xla/OmGiOtXlhl0AtumVvuTA4RBZhgToCXyfpFB0YWc 14LpcBX+2H9yYSu939PY3IZpHURkPphtbfbVG+Z8Z5BCJX6lcZlr9+7154KE48p+TEhT xoJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757668843; x=1758273643; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=GgDT8yas3IcACX8dpF1Z+jTHl5S85Xg0OLjTZ81TkYA=; b=Qx5sTooPdyg/XuJEBJ9cAs4odklaEwf4o4mO2ADhLLgZqyZuKkH0nT4IRoqwIjOxlM YgHdXxkgm3pTTcda2yTqgXgjxQzJEUZx6UVqi381BJH4SSL8/pY5kp+iVRnKjpMbmMDn 7HDc2gTBlZQJWIQ1SGCQfwYbi3aiwh+JcIRNSlIiZr1QDkNYOMyRpYFOW2GfhRoMjDMn rbchEawjfjz+4fXyS6ogNk/hqtwKnVHxB0eseHcmUgseeGFpjb1CmQpWJMz0YAgcYCXV SJ9QdZbKAYaetRR3BVts7xIWZ7x1NAqPZo70NIP0MEaSL/FQeEXnWxTpGIQkp8MVRuNr LRog== X-Gm-Message-State: AOJu0YwQLBVYoEDm9DeMwthCLof2z8NMBMOrvpL6Vg8hAL46ck6S08M6 EvjdYR+NEJPnR1zYJt5++bsl1HArW/4hvPDB6Mux+jBAIoX+epUxKmHn X-Gm-Gg: ASbGncu542drCh26mF5Xq9TW2RsjCgW0GhQIbI+8RbeO5HSiKPOfzMoQRxFeCqpQIR1 p5+pIpNOE5nT2C9n54rfMfMPNqkWz3O8Ksr/9ptx5TTtX9kFpId9frL4Z/q+t6ZQNBak12jWVX/ sNxqp+EfLLCuo/rat8VMrciePiAQ8nc47iJFkVEKZbNA7M7HDmQ/27qLiN2T7SundkUfYVJ/+iv /qi2YQGiD8rXzICjNjIYPYbRqYB7lcvC+HlSwL/xkVvq/0PaX0n1ACQKJUjjLjjDGXrN4p4Y2jT 6iags9Tl/ljDFB5C9K2X5wZ9B8APAnpUNtBNftn0oX4oDNMSO7cPg7GbIxQ5VHIh/1FZVCXmfb5 SNvPqt1z9/2UWQ5fBpHoOQeQ4t6bGNInRBQdcCNg8J9QY6GRff43YIgVqkaYv3Ir3sQ== X-Google-Smtp-Source: AGHT+IExdBehpPUbHsaoXn2hWCJuv0LpOESNjTgw/5wU/Er5DJI4ga/ArIntuYfQJ3gORg8zNqnSQw== X-Received: by 2002:a17:90b:5110:b0:325:7845:fc52 with SMTP id 98e67ed59e1d1-32de5144bafmr2400546a91.25.1757668842632; Fri, 12 Sep 2025 02:20:42 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-b54a36dc461sm4066564a12.23.2025.09.12.02.20.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Sep 2025 02:20:42 -0700 (PDT) From: a0282524688@gmail.com To: tmyu0@nuvoton.com, lee@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, andi.shyti@kernel.org, mkl@pengutronix.de, mailhol.vincent@wanadoo.fr, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, wim@linux-watchdog.org, linux@roeck-us.net, jdelvare@suse.com, alexandre.belloni@bootlin.com Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-i2c@vger.kernel.org, linux-can@vger.kernel.org, netdev@vger.kernel.org, linux-watchdog@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-rtc@vger.kernel.org, linux-usb@vger.kernel.org, Ming Yu Subject: [PATCH RESEND v14 7/7] rtc: Add Nuvoton NCT6694 RTC support Date: Fri, 12 Sep 2025 17:19:52 +0800 Message-Id: <20250912091952.1169369-8-a0282524688@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250912091952.1169369-1-a0282524688@gmail.com> References: <20250912091952.1169369-1-a0282524688@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Ming Yu This driver supports RTC functionality for NCT6694 MFD device based on USB interface. Acked-by: Alexandre Belloni Signed-off-by: Ming Yu --- Changes since version 13: Changes since version 12: - Use same email address in the signature Changes since version 11: Changes since version 10: Changes since version 9: - Add devm_add_action_or_reset() to dispose irq mapping Changes since version 8: - Modify the signed-off-by with my work address - Add irq_dispose_mapping() in the error handling path and in the remove function Changes since version 7: Changes since version 6: Changes since version 5: - Modify the module name and the driver name consistently Changes since version 4: - Modify arguments in read/write function to a pointer to cmd_header - Modify all callers that call the read/write function Changes since version 3: - Modify array buffer to structure - Fix defines and comments - Drop private mutex and use rtc core lock - Modify device_set_wakeup_capable() to device_init_wakeup() Changes since version 2: - Add MODULE_ALIAS() Changes since version 1: - Add each driver's command structure - Fix platform driver registration - Drop unnecessary logs - Fix overwrite error return values - Modify to use dev_err_probe API MAINTAINERS | 1 + drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-nct6694.c | 297 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 309 insertions(+) create mode 100644 drivers/rtc/rtc-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index bbacc9d48a83..442f24a408a6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18090,6 +18090,7 @@ F: drivers/hwmon/nct6694-hwmon.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/usb/nct6694_canfd.c +F: drivers/rtc/rtc-nct6694.c F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h =20 diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 64f6e9756aff..4a8dc8d0a4b7 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -416,6 +416,16 @@ config RTC_DRV_NCT3018Y This driver can also be built as a module, if so, the module will be called "rtc-nct3018y". =20 +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 789bddfea99d..610a9ee5fd33 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_RTC_DRV_MXC) +=3D rtc-mxc.o obj-$(CONFIG_RTC_DRV_MXC_V2) +=3D rtc-mxc_v2.o obj-$(CONFIG_RTC_DRV_GAMECUBE) +=3D rtc-gamecube.o obj-$(CONFIG_RTC_DRV_NCT3018Y) +=3D rtc-nct3018y.o +obj-$(CONFIG_RTC_DRV_NCT6694) +=3D rtc-nct6694.o obj-$(CONFIG_RTC_DRV_NTXEC) +=3D rtc-ntxec.o obj-$(CONFIG_RTC_DRV_OMAP) +=3D rtc-omap.o obj-$(CONFIG_RTC_DRV_OPAL) +=3D rtc-opal.o diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c new file mode 100644 index 000000000000..35401a0d9cf5 --- /dev/null +++ b/drivers/rtc/rtc-nct6694.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 RTC driver based on USB interface. + * + * Copyright (C) 2025 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * USB command module type for NCT6694 RTC controller. + * This defines the module type used for communication with the NCT6694 + * RTC controller over the USB interface. + */ +#define NCT6694_RTC_MOD 0x08 + +/* Command 00h - RTC Time */ +#define NCT6694_RTC_TIME 0x0000 +#define NCT6694_RTC_TIME_SEL 0x00 + +/* Command 01h - RTC Alarm */ +#define NCT6694_RTC_ALARM 0x01 +#define NCT6694_RTC_ALARM_SEL 0x00 + +/* Command 02h - RTC Status */ +#define NCT6694_RTC_STATUS 0x02 +#define NCT6694_RTC_STATUS_SEL 0x00 + +#define NCT6694_RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC al= arm */ +#define NCT6694_RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC = alarm */ + +#define NCT6694_RTC_IRQ_EN (NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_E= N) +#define NCT6694_RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */ + +struct __packed nct6694_rtc_time { + u8 sec; + u8 min; + u8 hour; + u8 week; + u8 day; + u8 month; + u8 year; +}; + +struct __packed nct6694_rtc_alarm { + u8 sec; + u8 min; + u8 hour; + u8 alarm_en; + u8 alarm_pend; +}; + +struct __packed nct6694_rtc_status { + u8 irq_en; + u8 irq_pend; +}; + +union __packed nct6694_rtc_msg { + struct nct6694_rtc_time time; + struct nct6694_rtc_alarm alarm; + struct nct6694_rtc_status sts; +}; + +struct nct6694_rtc_data { + struct nct6694 *nct6694; + struct rtc_device *rtc; + union nct6694_rtc_msg *msg; + int irq; +}; + +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data =3D dev_get_drvdata(dev); + struct nct6694_rtc_time *time =3D &data->msg->time; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_TIME, + .sel =3D NCT6694_RTC_TIME_SEL, + .len =3D cpu_to_le16(sizeof(*time)) + }; + int ret; + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, time); + if (ret) + return ret; + + tm->tm_sec =3D bcd2bin(time->sec); /* tm_sec expect 0 ~ 59 */ + tm->tm_min =3D bcd2bin(time->min); /* tm_min expect 0 ~ 59 */ + tm->tm_hour =3D bcd2bin(time->hour); /* tm_hour expect 0 ~ 23 */ + tm->tm_wday =3D bcd2bin(time->week) - 1; /* tm_wday expect 0 ~ 6 */ + tm->tm_mday =3D bcd2bin(time->day); /* tm_mday expect 1 ~ 31 */ + tm->tm_mon =3D bcd2bin(time->month) - 1; /* tm_month expect 0 ~ 11 */ + tm->tm_year =3D bcd2bin(time->year) + 100; /* tm_year expect since 1900 */ + + return ret; +} + +static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data =3D dev_get_drvdata(dev); + struct nct6694_rtc_time *time =3D &data->msg->time; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_TIME, + .sel =3D NCT6694_RTC_TIME_SEL, + .len =3D cpu_to_le16(sizeof(*time)) + }; + + time->sec =3D bin2bcd(tm->tm_sec); + time->min =3D bin2bcd(tm->tm_min); + time->hour =3D bin2bcd(tm->tm_hour); + time->week =3D bin2bcd(tm->tm_wday + 1); + time->day =3D bin2bcd(tm->tm_mday); + time->month =3D bin2bcd(tm->tm_mon + 1); + time->year =3D bin2bcd(tm->tm_year - 100); + + return nct6694_write_msg(data->nct6694, &cmd_hd, time); +} + +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *a= lrm) +{ + struct nct6694_rtc_data *data =3D dev_get_drvdata(dev); + struct nct6694_rtc_alarm *alarm =3D &data->msg->alarm; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_ALARM, + .sel =3D NCT6694_RTC_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(*alarm)) + }; + int ret; + + ret =3D nct6694_read_msg(data->nct6694, &cmd_hd, alarm); + if (ret) + return ret; + + alrm->time.tm_sec =3D bcd2bin(alarm->sec); + alrm->time.tm_min =3D bcd2bin(alarm->min); + alrm->time.tm_hour =3D bcd2bin(alarm->hour); + alrm->enabled =3D alarm->alarm_en; + alrm->pending =3D alarm->alarm_pend; + + return ret; +} + +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *al= rm) +{ + struct nct6694_rtc_data *data =3D dev_get_drvdata(dev); + struct nct6694_rtc_alarm *alarm =3D &data->msg->alarm; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_ALARM, + .sel =3D NCT6694_RTC_ALARM_SEL, + .len =3D cpu_to_le16(sizeof(*alarm)) + }; + + alarm->sec =3D bin2bcd(alrm->time.tm_sec); + alarm->min =3D bin2bcd(alrm->time.tm_min); + alarm->hour =3D bin2bcd(alrm->time.tm_hour); + alarm->alarm_en =3D alrm->enabled ? NCT6694_RTC_IRQ_EN : 0; + alarm->alarm_pend =3D 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, alarm); +} + +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int e= nabled) +{ + struct nct6694_rtc_data *data =3D dev_get_drvdata(dev); + struct nct6694_rtc_status *sts =3D &data->msg->sts; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_STATUS, + .sel =3D NCT6694_RTC_STATUS_SEL, + .len =3D cpu_to_le16(sizeof(*sts)) + }; + + if (enabled) + sts->irq_en |=3D NCT6694_RTC_IRQ_EN; + else + sts->irq_en &=3D ~NCT6694_RTC_IRQ_EN; + + sts->irq_pend =3D 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, sts); +} + +static const struct rtc_class_ops nct6694_rtc_ops =3D { + .read_time =3D nct6694_rtc_read_time, + .set_time =3D nct6694_rtc_set_time, + .read_alarm =3D nct6694_rtc_read_alarm, + .set_alarm =3D nct6694_rtc_set_alarm, + .alarm_irq_enable =3D nct6694_rtc_alarm_irq_enable, +}; + +static irqreturn_t nct6694_irq(int irq, void *dev_id) +{ + struct nct6694_rtc_data *data =3D dev_id; + struct nct6694_rtc_status *sts =3D &data->msg->sts; + static const struct nct6694_cmd_header cmd_hd =3D { + .mod =3D NCT6694_RTC_MOD, + .cmd =3D NCT6694_RTC_STATUS, + .sel =3D NCT6694_RTC_STATUS_SEL, + .len =3D cpu_to_le16(sizeof(*sts)) + }; + int ret; + + rtc_lock(data->rtc); + + sts->irq_en =3D NCT6694_RTC_IRQ_EN; + sts->irq_pend =3D NCT6694_RTC_IRQ_STS; + ret =3D nct6694_write_msg(data->nct6694, &cmd_hd, sts); + if (ret) { + rtc_unlock(data->rtc); + return IRQ_NONE; + } + + rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF); + + rtc_unlock(data->rtc); + + return IRQ_HANDLED; +} + +static void nct6694_irq_dispose_mapping(void *d) +{ + struct nct6694_rtc_data *data =3D d; + + irq_dispose_mapping(data->irq); +} + +static int nct6694_rtc_probe(struct platform_device *pdev) +{ + struct nct6694_rtc_data *data; + struct nct6694 *nct6694 =3D dev_get_drvdata(pdev->dev.parent); + int ret; + + data =3D devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->msg =3D devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg), + GFP_KERNEL); + if (!data->msg) + return -ENOMEM; + + data->irq =3D irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC); + if (!data->irq) + return -EINVAL; + + ret =3D devm_add_action_or_reset(&pdev->dev, nct6694_irq_dispose_mapping, + data); + if (ret) + return ret; + + ret =3D devm_device_init_wakeup(&pdev->dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to init wakeup\n"); + + data->rtc =3D devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(data->rtc)) + return PTR_ERR(data->rtc); + + data->nct6694 =3D nct6694; + data->rtc->ops =3D &nct6694_rtc_ops; + data->rtc->range_min =3D RTC_TIMESTAMP_BEGIN_2000; + data->rtc->range_max =3D RTC_TIMESTAMP_END_2099; + + platform_set_drvdata(pdev, data); + + ret =3D devm_request_threaded_irq(&pdev->dev, data->irq, NULL, + nct6694_irq, IRQF_ONESHOT, + "rtc-nct6694", data); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + + return devm_rtc_register_device(data->rtc); +} + +static struct platform_driver nct6694_rtc_driver =3D { + .driver =3D { + .name =3D "nct6694-rtc", + }, + .probe =3D nct6694_rtc_probe, +}; + +module_platform_driver(nct6694_rtc_driver); + +MODULE_DESCRIPTION("USB-RTC driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-rtc"); --=20 2.34.1