From patchwork Tue May 20 02:03:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 891518 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 45D3D256C77; Tue, 20 May 2025 02:04:14 +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=1747706658; cv=none; b=AN7IqQ9T84CWHAAmPvdAZe8nfB6bd8zZ4sN3yV7uB2zqmwf4nmbtGPdn2LHOXR6iW3i1MA4LBztgHmPUrsOvLKnQcvNCOUQgUMpFLMEB7DAcTb/qrsmSLLybZmoECY22U2XzGfn9yhQXgb3m4KT14WVafmRqd5t+4MjDQ0/H1WU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706658; c=relaxed/simple; bh=xXUzUeF5el7Sw8LUFQExL4qXtrOmmhFVn1LntTA7RRA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=qBkGtOoawPnkuaxPUeCeyW1xCRozWAJkWAvDAwnp3WyJL9efrppSMtl03xoSR0vd8Y7SwGpam4hEpL8KL+y7XSSiNEwu5YaFtIm2nXLT19w1OgR098wRbyl5YLwJ7dmdLYFny+K9t3sIyF32hgFILh8pcbKZExLqM5uLM2s/0wo= 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=jt2HGcxf; 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="jt2HGcxf" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-b26f01c638fso3481820a12.1; Mon, 19 May 2025 19:04:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747706654; x=1748311454; 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=uwoMJmBzGoy1ISmxdm/zOtPJvClydGSlblLtcL+RFQc=; b=jt2HGcxfAJEOQWIX04SeNv4x4A9EsqLKzweRnL93Q+NlRqeGGQ67incQ15t8gaLBzh J6ZsWJoBsrF6JGAgSexRc2pbbSUxRtLFZ01hiteysuM4jx52R3PCjlOrkN1LPEi0EBgB oVhg7Wf7Hi+18yzBIqQi4Z3PlyT6mr8mg6deyKvJCsy3akqq70cKqXDtjm2WRrEz/UFN spYcHbRqgyJVaaz//rgezfO3Y3o7YP8e8pxqp9clXjUdHhlw2YyrO0/LExqiay119ix5 fEyL0DVxU/G79SpFZIVuKDRLm81vY0O5S0022wA7kt2I4ibWomAHlKANYoQgsIL6cLNp EsWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747706654; x=1748311454; 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=uwoMJmBzGoy1ISmxdm/zOtPJvClydGSlblLtcL+RFQc=; b=S8Z/EmZ5Cdm9NnYnAUnl7Ptq5Y1zcYivttNjahmz1EeEyJn1hNuiVtoECHbEwS4IYc Bew/c9hHHjUwiXoQPOsiiAAntpHi4ZOaglIX1on2eKOzH37lxUcOF83awa/icWs1SyQN TdrR6GaDfG5UjoYMuAVhcW97qt1dLUyHY5TtWROcfdsHXNY+M5uu9h8I4CEE5mUTSCGx HyzAf3tIjshnDWXkqeFomCHnKY9svPT6dbx9vHnE78mzNGziyFp/QxVgCVYf80gO6WT2 LyTYTdEZJum0Ebw0pM2nSqHR1VqXKeMTmD4VmjNqEJ4ERn6YOYY676ouST4F7WFNLkV8 wT2w== X-Forwarded-Encrypted: i=1; AJvYcCUAwhVKf5MVXKQEQoyoTLTa6KX9bxTdbP3ojKGdlwmjubrwU0JYeTMQ2/X8anAwtIu251iNE3L4zfnwZQ==@vger.kernel.org, AJvYcCUUYodjlpzyuktUUgfEUCfDrs0RNF3CzG+y4VYWD/cHn0h3UHK9pJm1TDTaclwkx3Xd6tanuRQ6O6v+YoOYbLE=@vger.kernel.org, AJvYcCV1lDH2Y18aOWih4Hn3c3EoZbdwq6qB+5uYIuM9PSU13Sl0vsl/aaxbgHgikdwdYPcL256jSggAKpY=@vger.kernel.org, AJvYcCVi/1kfFGHrR9gqBuRk4sT+vs5ln+YxtwA8gPdyOe19LGMoMD9S1jiFADNlyRjV8LuCb4gBQFCOk5y+@vger.kernel.org, AJvYcCVp2x1Rf7LEaUNyW/CPRA2Xj9FWjCRfmbM7A47m7gBK/cZHGo2/bwhypd8LZhgh4kNJBkp9ZjnI@vger.kernel.org, AJvYcCWG0+ck7xfi9W5wCYrsde4f4VMezSQ+X63Y4PsyPjzdj0ZSoEW4/p9vMxXAJBMzKT/DGZlAhvKAsDNL@vger.kernel.org, AJvYcCWMoOJxHXOd7fiuDgcap0J50KxD0XLMvaOnT2xbiNCOUmUynQkLpS0BRMY2Tehi3xYUQFxyMy0dRHwFyEI=@vger.kernel.org, AJvYcCXRlEYE2ULDxX2u638dnJzK37yKcs3Y5A58tHpwX657yyFsb1wAKWxxYmaoqn6jReqFOrJ9udpyvX+L@vger.kernel.org X-Gm-Message-State: AOJu0YyF9RSCsTN7nYx3oWPcrF4J7Qk/loZSYr4YchCiS+SuRNsRbXcG EyjO7uauaH6lW9pCiohYgfO43Edy7cAMjcU31Co1XkZrHEuXw/LaatUc X-Gm-Gg: ASbGnctV2clHuP9vvWhQXRZUM7rIU6a2y3jYAlsPInOZ3x5mQGJrcTmQ/4UJtVyzvZl WATZ2DcCnx5T6CCuQ3yawI91C+VtfIx8KSyuwOsE4/02zH1ys9bgjOE2ohJK9+LvL2id8bKKxxO gL/HJI4xKb7W1mvk2O+QK4gYA17w1dTumd5mPKDQACYB+wxx8sK1LXr5Bd+ZnH6frgYX+Y91ZZ7 AKKzL/7EHTFaQitH1q+H1OkLoy9TNJ14MqfbGKqYwFqcSG+jKn0x+TBKm/X2yvDMO3MUCi9cfjg WMBznqufXhQlSmlfmNThP4oBOxQvZoOO0FsXgR/ZujBIQJZGht/z6mCD/A+4Dph4KlJwXNjLCXH JiltSJc/VhPhNOw== X-Google-Smtp-Source: AGHT+IFPouxb128rWTOc7rb2pNgKaCo5AI4r1+tjqxqo4P28Z7uqbbJsOIHzVE1k/gCQiKcgqt+B9g== X-Received: by 2002:a17:903:3c64:b0:232:7531:2229 with SMTP id d9443c01a7336-232753122f3mr30900665ad.1.1747706654408; Mon, 19 May 2025 19:04:14 -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 d9443c01a7336-231d4ac9fc8sm66543855ad.27.2025.05.19.19.04.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 May 2025 19:04:13 -0700 (PDT) From: a0282524688@gmail.com X-Google-Original-From: tmyu0@nuvoton.com To: 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 v11 1/7] mfd: Add core driver for Nuvoton NCT6694 Date: Tue, 20 May 2025 10:03:49 +0800 Message-Id: <20250520020355.3885597-2-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250520020355.3885597-1-tmyu0@nuvoton.com> References: <20250520020355.3885597-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-watchdog@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 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 | 387 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/nct6694.h | 98 +++++++++ 5 files changed, 508 insertions(+) create mode 100644 drivers/mfd/nct6694.c create mode 100644 include/linux/mfd/nct6694.h diff --git a/MAINTAINERS b/MAINTAINERS index d48dd6726fe6..fab0cfb7e884 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17451,6 +17451,12 @@ F: drivers/nubus/ F: include/linux/nubus.h F: include/uapi/linux/nubus.h +NUVOTON NCT6694 MFD DRIVER +M: Ming Yu +S: Supported +F: drivers/mfd/nct6694.c +F: include/linux/mfd/nct6694.h + NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER M: Antonino Daplas L: linux-fbdev@vger.kernel.org diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 22b936310039..cd4d826a7fcb 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1058,6 +1058,21 @@ config MFD_MENF21BMC This driver can also be built as a module. If so the module will be called menf21bmc. +config MFD_NCT6694 + tristate "Nuvoton NCT6694 support" + select MFD_CORE + depends on USB + help + This enables support for the Nuvoton USB device NCT6694, which shares + peripherals. + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, + PWM, and RTC. + This driver provides core APIs to access the NCT6694 hardware + monitoring and control features. + Additional drivers must be enabled to utilize the specific + functionalities of the device. + config MFD_OCELOT tristate "Microsemi Ocelot External Control Support" depends on SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 948cbdf42a18..471dc1f183b8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o +obj-$(CONFIG_MFD_NCT6694) += nct6694.o + obj-$(CONFIG_MFD_CORE) += mfd-core.o ocelot-soc-objs := ocelot-core.o ocelot-spi.o diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c new file mode 100644 index 000000000000..10ab836f8f2c --- /dev/null +++ b/drivers/mfd/nct6694.c @@ -0,0 +1,387 @@ +// 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 + +static const struct mfd_cell nct6694_devs[] = { + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + MFD_CELL_NAME("nct6694-gpio"), + + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + MFD_CELL_NAME("nct6694-i2c"), + + MFD_CELL_NAME("nct6694-canfd"), + MFD_CELL_NAME("nct6694-canfd"), + + MFD_CELL_NAME("nct6694-wdt"), + MFD_CELL_NAME("nct6694-wdt"), + + MFD_CELL_NAME("nct6694-hwmon"), + + MFD_CELL_NAME("nct6694-rtc"), +}; + +static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status) +{ + switch (err_status) { + case NCT6694_NO_ERROR: + return 0; + case NCT6694_NOT_SUPPORT_ERROR: + dev_err(nct6694->dev, "Command is not supported!\n"); + break; + case NCT6694_NO_RESPONSE_ERROR: + dev_warn(nct6694->dev, "Command received no response!\n"); + break; + case NCT6694_TIMEOUT_ERROR: + dev_warn(nct6694->dev, "Command timed out!\n"); + break; + case NCT6694_PENDING: + dev_err(nct6694->dev, "Command is pending!\n"); + break; + default: + return -EINVAL; + } + + return -EIO; +} + +/** + * nct6694_read_msg() - Read message from NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer to store the response data + * + * Sends a command to the NCT6694 device and reads the response. + * The command header is specified in @cmd_hd, and the response + * data is stored in @buf. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg = nct6694->usb_msg; + struct usb_device *udev = nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl = NCT6694_HCTRL_GET; + + /* Send command packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len != le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected received length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_read_msg); + +/** + * nct6694_write_msg() - Write message to NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer containing the data to be sent + * + * Sends a command to the NCT6694 device and writes the data + * from @buf. The command header is specified in @cmd_hd. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg = nct6694->usb_msg; + struct usb_device *udev = nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl = NCT6694_HCTRL_SET; + + /* Send command packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Send data packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf, + le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len != le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_write_msg); + +static void usb_int_callback(struct urb *urb) +{ + struct nct6694 *nct6694 = urb->context; + unsigned int *int_status = urb->transfer_buffer; + int ret; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto resubmit; + } + + while (*int_status) { + int irq = __ffs(*int_status); + + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); + *int_status &= ~BIT(irq); + } + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret)); +} + +static void nct6694_irq_lock(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + + mutex_lock(&nct6694->irq_lock); +} + +static void nct6694_irq_sync_unlock(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + + mutex_unlock(&nct6694->irq_lock); +} + +static void nct6694_irq_enable(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + nct6694->irq_enable |= BIT(hwirq); +} + +static void nct6694_irq_disable(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + nct6694->irq_enable &= ~BIT(hwirq); +} + +static const struct irq_chip nct6694_irq_chip = { + .name = "nct6694-irq", + .flags = IRQCHIP_SKIP_SET_WAKE, + .irq_bus_lock = nct6694_irq_lock, + .irq_bus_sync_unlock = nct6694_irq_sync_unlock, + .irq_enable = nct6694_irq_enable, + .irq_disable = nct6694_irq_disable, +}; + +static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +{ + struct nct6694 *nct6694 = d->host_data; + + irq_set_chip_data(irq, nct6694); + irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq); + + return 0; +} + +static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops nct6694_irq_domain_ops = { + .map = nct6694_irq_domain_map, + .unmap = nct6694_irq_domain_unmap, +}; + +static int nct6694_usb_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_endpoint_descriptor *int_endpoint; + struct usb_host_interface *interface; + struct device *dev = &iface->dev; + struct nct6694 *nct6694; + int pipe, maxp; + int ret; + + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL); + if (!nct6694) + return -ENOMEM; + + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP); + maxp = usb_maxpacket(udev, pipe); + + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL); + if (!nct6694->usb_msg) + return -ENOMEM; + + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL); + if (!nct6694->int_buffer) + return -ENOMEM; + + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!nct6694->int_in_urb) + return -ENOMEM; + + nct6694->domain = irq_domain_add_simple(NULL, NCT6694_NR_IRQS, 0, + &nct6694_irq_domain_ops, + nct6694); + if (!nct6694->domain) { + ret = -ENODEV; + goto err_urb; + } + + nct6694->dev = dev; + nct6694->udev = udev; + + ret = devm_mutex_init(dev, &nct6694->access_lock); + if (ret) + goto err_irq; + + ret = devm_mutex_init(dev, &nct6694->irq_lock); + if (ret) + goto err_irq; + + interface = iface->cur_altsetting; + + int_endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(int_endpoint)) { + ret = -ENODEV; + goto err_irq; + } + + usb_fill_int_urb(nct6694->int_in_urb, udev, pipe, nct6694->int_buffer, maxp, + usb_int_callback, nct6694, int_endpoint->bInterval); + + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL); + if (ret) + goto err_irq; + + usb_set_intfdata(iface, nct6694); + + ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs)); + if (ret) + goto err_mfd; + + return 0; + +err_mfd: + usb_kill_urb(nct6694->int_in_urb); +err_irq: + irq_domain_remove(nct6694->domain); +err_urb: + usb_free_urb(nct6694->int_in_urb); + return ret; +} + +static void nct6694_usb_disconnect(struct usb_interface *iface) +{ + struct nct6694 *nct6694 = usb_get_intfdata(iface); + + mfd_remove_devices(nct6694->dev); + usb_kill_urb(nct6694->int_in_urb); + irq_domain_remove(nct6694->domain); + usb_free_urb(nct6694->int_in_urb); +} + +static const struct usb_device_id nct6694_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00)}, + {} +}; +MODULE_DEVICE_TABLE(usb, nct6694_ids); + +static struct usb_driver nct6694_usb_driver = { + .name = "nct6694", + .id_table = nct6694_ids, + .probe = nct6694_usb_probe, + .disconnect = nct6694_usb_disconnect, +}; +module_usb_driver(nct6694_usb_driver); + +MODULE_DESCRIPTION("Nuvoton NCT6694 core driver"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h new file mode 100644 index 000000000000..5e172609be3f --- /dev/null +++ b/include/linux/mfd/nct6694.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Nuvoton Technology Corp. + * + * Nuvoton NCT6694 USB transaction and data structure. + */ + +#ifndef __MFD_NCT6694_H +#define __MFD_NCT6694_H + +#define NCT6694_VENDOR_ID 0x0416 +#define NCT6694_PRODUCT_ID 0x200B +#define NCT6694_INT_IN_EP 0x81 +#define NCT6694_BULK_IN_EP 0x02 +#define NCT6694_BULK_OUT_EP 0x03 + +#define NCT6694_HCTRL_SET 0x40 +#define NCT6694_HCTRL_GET 0x80 + +#define NCT6694_URB_TIMEOUT 1000 + +enum nct6694_irq_id { + NCT6694_IRQ_GPIO0 = 0, + NCT6694_IRQ_GPIO1, + NCT6694_IRQ_GPIO2, + NCT6694_IRQ_GPIO3, + NCT6694_IRQ_GPIO4, + NCT6694_IRQ_GPIO5, + NCT6694_IRQ_GPIO6, + NCT6694_IRQ_GPIO7, + NCT6694_IRQ_GPIO8, + NCT6694_IRQ_GPIO9, + NCT6694_IRQ_GPIOA, + NCT6694_IRQ_GPIOB, + NCT6694_IRQ_GPIOC, + NCT6694_IRQ_GPIOD, + NCT6694_IRQ_GPIOE, + NCT6694_IRQ_GPIOF, + NCT6694_IRQ_CAN0, + NCT6694_IRQ_CAN1, + NCT6694_IRQ_RTC, + NCT6694_NR_IRQS, +}; + +enum nct6694_response_err_status { + NCT6694_NO_ERROR = 0, + NCT6694_FORMAT_ERROR, + NCT6694_RESERVED1, + NCT6694_RESERVED2, + NCT6694_NOT_SUPPORT_ERROR, + NCT6694_NO_RESPONSE_ERROR, + NCT6694_TIMEOUT_ERROR, + NCT6694_PENDING, +}; + +struct __packed nct6694_cmd_header { + u8 rsv1; + u8 mod; + union __packed { + __le16 offset; + struct __packed { + u8 cmd; + u8 sel; + }; + }; + u8 hctrl; + u8 rsv2; + __le16 len; +}; + +struct __packed nct6694_response_header { + u8 sequence_id; + u8 sts; + u8 reserved[4]; + __le16 len; +}; + +union __packed nct6694_usb_msg { + struct nct6694_cmd_header cmd_header; + struct nct6694_response_header response_header; +}; + +struct nct6694 { + struct device *dev; + struct irq_domain *domain; + struct mutex access_lock; + struct mutex irq_lock; + struct urb *int_in_urb; + struct usb_device *udev; + union nct6694_usb_msg *usb_msg; + unsigned char *int_buffer; + unsigned int irq_enable; +}; + +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); + +#endif From patchwork Tue May 20 02:03:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 891517 Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.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 AEA372580E4; Tue, 20 May 2025 02:04:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706665; cv=none; b=S9WM0rPPOjvGU7L6pjozLgf6QtgBpAT0bz1c87U77LZa+lbQqiTnkKkDxZmHR4GR4zF+NOhcr9+07RzkCwXTyWSihpwOAAdL9zyN2GKNNhsCoUPPu+4q0H3BPy8d2UXaTh3E9VpDe8Fp9GI/0UH+J0Jdk8LqKL0CWLdzz1cqNkM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706665; c=relaxed/simple; bh=SPmU5DocE7bFLj6MhdmL55Pakuc3u38KOa+2BOvt7Ig=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=CAp2ORUVMDRixiLSbRXOAFi7oIPAjhRzrXJBeXWqJ0FOAk8Wm93HMBeGj/6ep0kKfeaHok65rpwyYCAAVgR8xkPgEuI837Vvyq+rkz0+UXdkO8hQQwlptWV5ryRyh2vLBTLBUeo5k5z/s0h706oKQBbfpGft/nMELDrtOftlTGI= 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=CGhwQuTs; arc=none smtp.client-ip=209.85.214.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="CGhwQuTs" Received: by mail-pl1-f178.google.com with SMTP id d9443c01a7336-23211e62204so18047295ad.3; Mon, 19 May 2025 19:04:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747706663; x=1748311463; 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=HipBwcg6Ll7dOYLGfXJwYOXNuh2MRjVNlaNc8CeVn7g=; b=CGhwQuTs10+2FHRlMQcDuVvWWzz02rYQdtTH/XCV7AETxrJaKhB9liGSylfiFv+0iJ MiLbr2qMA2019U82aB0PCyIfILItPSC8QUS2Yo1qJ4RbcITlAHmUsYAvEmsTieje+fKU B77+cr8jLSrLag8bAI2E2uODhXLrG142kdNCR9JvQMMKvbX3PU+SWjRULXRah9oYVBfw fShmJeHwsXFSiBd8qzHKf8Lw21eCWdJz8LHmcbzpv8sDZPyQbvs79a070YWNk/TQYjCa FY/8EHmDAG3GD71tkrGX5uYhAiOAZ7H+AGqotD6vJkFS+Zs0BXOa7Gh32F5NuA6AUudz Ce/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747706663; x=1748311463; 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=HipBwcg6Ll7dOYLGfXJwYOXNuh2MRjVNlaNc8CeVn7g=; b=EMMTpYLyw5NbMSGOJt/QQCHdaEqzbZntiJGh/DOgiCNHAglbRivLTkIEEIXaasJUcE Ik2nHRE1zi8j+1PpvtVsLiQUie3ox8qf7Z2wF0diT2PQ46WgWQDQW8RnOuAWS7z9if9L TEQeBns6Ne2Ij+g8hLkMxCBht0DBhL4X/YHxlXBuALq7gQvbeZKDsApac37Np+QXOBtX MNc7RJFLE9g9vgXzvGLMBXjpX0qE4q8fnJpFGhPTM2+6ilAeFSj2Ir7RBfqXLzfLxgMO YV6X4NvIWv/emqER2Tg3sjWV89Tog8L9Z3GvH2vIsCMWqFeQsLYSaB2h3/M/8nLSlmYD 0tmQ== X-Forwarded-Encrypted: i=1; AJvYcCUI9AOqbHCiarzBYR1dbuq0LVaFz6bjguKAvyvTCii9f8Q8+0lHUjzEpAEgfaBa4rAnCKU9kP+IntY+@vger.kernel.org, AJvYcCUToJxusAuvcO09loABOcIpVfU0a6Q9YhwapqjhBSUSITXE6LvAlFkaYekp5zF4EO5/FQU1xhx7kfwDe7U=@vger.kernel.org, AJvYcCUd/6I8kxeW8H1tO1qo0PVwedqMKTuyHLsRUcpc6KBEYJVoqii8tdI07Tj2KJNR7coUa+n1D1xgOY9j@vger.kernel.org, AJvYcCUgEJICNUvWw3W+NVS7g1EFOZI5oK2khco6ss3jnt86FrcHkYod+FjCl4OZxfDbJ50dNqlQ08d5@vger.kernel.org, AJvYcCW/snsgjSV1DmO+uavishSJ3YrbwJWL3SkZbIOpdTWNi74TGTWPzB0fOJMUw7VpZafVCrBoyfQs4/Q=@vger.kernel.org, AJvYcCW1z4venMIlmvJDK+NrVA86aC+63Idlx3UvezGL04MKbWvscNtRk7gdNvFvqNuzgXIi7Wco2y48EJ2i@vger.kernel.org, AJvYcCX40nO2PkcviDj7q3403v3y0rDu3Fp2KqaI/qtar8OlXGMH7kkDkOb67uEHsx8bPDjVdmQSLaDmEBHFLg==@vger.kernel.org, AJvYcCXit7T7CE4wW7AcWKy7TvNARuHqwbAD5iL1VEBVVE9y7uq1bqTR1vAVd+y//5wVlJvJhzqKYLVkYLHCLPmB5LY=@vger.kernel.org X-Gm-Message-State: AOJu0YyO0GMssraRa4/xopYjt+PsU5gB6eAlPK/kpriuWk3nJlehxrbO b28fMvo1Oo2Kd6gb6nTa+UROveIOR03YvVG1mPO8JYdeWke23Lshr3aT X-Gm-Gg: ASbGnctTK5Bw2q8NWQ1WC1e/NVA6z8J+HMlF2prsaY13aGIeNJCX5GAbgvQ8ZKChtOJ e0+8EU7Isfo9CsZeMrsZ52e4j8SVbrexKfvHtUflgrC0pRqAgd1s9N7fntE85mx3hlcY3v0b/Uh UiD4hMHoH1g8VyNTx4hJLcEVQOcS+aVDDMKJWUuTtXupEorxzF03F6QUF+pTWsym00G4en0U+Fm 7Vo6jBS4QveZohdRrZB3LenZFcQy97J1+wWF2L+XtmKpAj89xTd9wxCZsokTsl/EUR/9crZHU83 mieP6hL/N8VqUjtZ/xFJF2o6dwlYD2vHnh3ZFa2smTqm+TC9dB22nx4DfHHrdDPRphJheDdEdUM poO9/8g6gXYBJgQ== X-Google-Smtp-Source: AGHT+IFSN72+LLHHedvYq7y9mEHr8VXXEIQ+RbqVTO3w57eQiReppl6XkKMeL70Sx4qLBYNYmMYsWQ== X-Received: by 2002:a17:902:f790:b0:223:325c:89f6 with SMTP id d9443c01a7336-231d43d9ba7mr270281505ad.10.1747706662780; Mon, 19 May 2025 19:04: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 d9443c01a7336-231d4ac9fc8sm66543855ad.27.2025.05.19.19.04.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 May 2025 19:04:22 -0700 (PDT) From: a0282524688@gmail.com X-Google-Original-From: tmyu0@nuvoton.com To: 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 v11 3/7] i2c: Add Nuvoton NCT6694 I2C support Date: Tue, 20 May 2025 10:03:51 +0800 Message-Id: <20250520020355.3885597-4-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250520020355.3885597-1-tmyu0@nuvoton.com> References: <20250520020355.3885597-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-watchdog@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 Signed-off-by: Ming Yu --- 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 | 193 +++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 drivers/i2c/busses/i2c-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index 4864a08dcf7e..88a6be6b05a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17455,6 +17455,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 diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 83c88c79afe2..e0938fed74f1 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1347,6 +1347,16 @@ config I2C_LJCA This driver can also be built as a module. If so, the module will be called i2c-ljca. +config I2C_NCT6694 + tristate "Nuvoton NCT6694 I2C adapter support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB to I2C interface. + + This driver can also be built as a module. If so, the module will + be called i2c-nct6694. + config I2C_CP2615 tristate "Silicon Labs CP2615 USB sound card and I2C adapter" depends on USB diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index c1252e2b779e..e7fd9dc15c6c 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -134,6 +134,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o +obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c new file mode 100644 index 000000000000..4f36cd54ad55 --- /dev/null +++ b/drivers/i2c/busses/i2c-nct6694.c @@ -0,0 +1,193 @@ +// 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_DEVS 6 + +static unsigned char br_reg[NCT6694_I2C_MAX_DEVS] = {[0 ... (NCT6694_I2C_MAX_DEVS - 1)] = 0xFF}; + +module_param_array(br_reg, byte, NULL, 0644); +MODULE_PARM_DESC(br_reg, + "I2C Baudrate register per adapter: (0=25K, 1=50K, 2=100K, 3=200K, 4=400K, 5=800K, 6=1M), default=2"); + +enum nct6694_i2c_baudrate { + NCT6694_I2C_BR_25K = 0, + NCT6694_I2C_BR_50K, + NCT6694_I2C_BR_100K, + NCT6694_I2C_BR_200K, + NCT6694_I2C_BR_400K, + NCT6694_I2C_BR_800K, + NCT6694_I2C_BR_1M +}; + +struct __packed nct6694_i2c_deliver { + u8 port; + u8 br; + u8 addr; + u8 w_cnt; + u8 r_cnt; + u8 rsv[11]; + u8 write_data[0x40]; + u8 read_data[0x40]; +}; + +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 DEFINE_IDA(nct6694_i2c_ida); + +static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct nct6694_i2c_data *data = adap->algo_data; + struct nct6694_i2c_deliver *deliver = &data->deliver; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_I2C_MOD, + .cmd = NCT6694_I2C_DELIVER, + .sel = NCT6694_I2C_DELIVER_SEL, + .len = cpu_to_le16(sizeof(*deliver)) + }; + int ret, i; + + for (i = 0; i < num; i++) { + struct i2c_msg *msg_temp = &msgs[i]; + + memset(deliver, 0, sizeof(*deliver)); + + if (msg_temp->len > 64) + return -EPROTO; + + deliver->port = data->port; + deliver->br = data->br; + deliver->addr = i2c_8bit_addr_from_msg(msg_temp); + if (msg_temp->flags & I2C_M_RD) { + deliver->r_cnt = msg_temp->len; + ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver); + if (ret < 0) + return ret; + + memcpy(msg_temp->buf, deliver->read_data, msg_temp->len); + } else { + deliver->w_cnt = msg_temp->len; + memcpy(deliver->write_data, msg_temp->buf, msg_temp->len); + ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver); + if (ret < 0) + return ret; + } + } + + return num; +} + +static u32 nct6694_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm algorithm = { + .master_xfer = nct6694_xfer, + .functionality = nct6694_func, +}; + +static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data) +{ + if (data->port >= NCT6694_I2C_MAX_DEVS) { + dev_err(data->dev, "Invalid I2C port index %d\n", data->port); + return -EINVAL; + } + + if (br_reg[data->port] > NCT6694_I2C_BR_1M) { + dev_warn(data->dev, "Invalid baudrate %d for I2C%d, using 100K\n", + br_reg[data->port], data->port); + br_reg[data->port] = NCT6694_I2C_BR_100K; + } + + data->br = br_reg[data->port]; + + return 0; +} + +static void nct6694_i2c_ida_remove(void *d) +{ + struct nct6694_i2c_data *data = d; + + ida_free(&nct6694_i2c_ida, data->port); +} + +static int nct6694_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6694 *nct6694 = dev_get_drvdata(dev->parent); + struct nct6694_i2c_data *data; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + data->nct6694 = nct6694; + + ret = ida_alloc(&nct6694_i2c_ida, GFP_KERNEL); + if (ret < 0) + return ret; + data->port = ret; + + ret = devm_add_action_or_reset(dev, nct6694_i2c_ida_remove, data); + if (ret) + return ret; + + ret = nct6694_i2c_set_baudrate(data); + if (ret) + return ret; + + sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", data->port); + data->adapter.owner = THIS_MODULE; + data->adapter.algo = &algorithm; + data->adapter.dev.parent = &pdev->dev; + data->adapter.algo_data = data; + + platform_set_drvdata(pdev, data); + + return devm_i2c_add_adapter(dev, &data->adapter); +} + +static struct platform_driver nct6694_i2c_driver = { + .driver = { + .name = "nct6694-i2c", + }, + .probe = nct6694_i2c_probe, +}; + +module_platform_driver(nct6694_i2c_driver); + +MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-i2c"); From patchwork Tue May 20 02:03:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 891516 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.174]) (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 B7D0925A34D; Tue, 20 May 2025 02:04:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706673; cv=none; b=Y7AWigJVBsHM/00Bi08cfsbicwtNulHXokaX3WUF19II9yh/ay/9n8J01APRPRp9qLOgplfYimsfy0lsZopBSNMuWyDsmJU7pijnZIAukt29N2O8HfVt1DgN/v6ZbC19OPPahrjCl0pAZ0VDJ1Aj66ge4cqvOhZ1gQ5tL134IqY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706673; c=relaxed/simple; bh=tefl2AxIAFqeyMWNGmjaFdERIzDjTCzbROc8E2SFUJo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=gRNBL5jDZ9LJVhuoaMN/iLmYeHiFk5A0z2RcMRkBXpwBgC0JnzNWrcF29d1C/B4Hr1kGfmptCrMhY1D6Jd2/tv/Bwi5QNO67O7uUTxxAECQDPR6VNRc2dmg8efpjXK7Tm6KPYGq4eQ1FdCVMlB1WprWNAJvpEYfLE2e8cKbf6tg= 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=aI/tlyK+; arc=none smtp.client-ip=209.85.215.174 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="aI/tlyK+" Received: by mail-pg1-f174.google.com with SMTP id 41be03b00d2f7-7fd35b301bdso5755922a12.2; Mon, 19 May 2025 19:04:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747706671; x=1748311471; 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=x6DU/6/FZLu/99LfVUbODFhTyR+2P/eumktTAFHtMzw=; b=aI/tlyK+S2Z1a2dd1V6brlnEcwQkjjd4/cn0gchgW1l4X4iXE3Jj9EFhNYvXX8IAVl xA1qRp531xSWIKhiIqnJzYGkSszmC0rM/dt1p4Dz0fWjDG7aef3A1+zC4/j8oCyboLH1 gJThcexK4ONB7GNzYsZ5VO4Odme5GBHXgO+t2sbA3g8XHTWJXMf3jZCoJwfIGT1HnYtZ 5xNCHJZS3HIg7NydZUupL51qFiEakpapmFda4FUZ1SNaUYrm8QsXD6yNWAl4DvwgiCHB xAaLu913Qx54rTv+6BigBdmlqSm/+ABtaKjQ8Xt7hHMiTOSF5mLmJ20BVyuW8+olaOzZ mTAw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747706671; x=1748311471; 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=x6DU/6/FZLu/99LfVUbODFhTyR+2P/eumktTAFHtMzw=; b=cVSgYT7K8dzVYrOfFGsQc8kTSYyjJTEUfLXDnOFBs7IEApuRGKPhbBuDl1sFjXkPbW f14dejnW1YHhEAqBGHqcrxaZBNcYrykDoc81cD4vDZD/397VkIlbCLW3my2uYOyU2DTP uE6THDtOZPeYGtnU42XMVsswPglr//3AbgYwhs76QfgFJOtQNygDrKxiQmsYUasqPVRd tomJr0f3xLKX8RC58KVE+raJ0F40H6TsbifoN8X1+cE1QdmMnCQ+SFdRJ8DzfgkG1Th4 9WG5FVg8o3GFClw+h128TUMg6qfdr4zd0XX2XjVWKxQ8O09rrnxPRm0v2ZkXiinjijHG c4fw== X-Forwarded-Encrypted: i=1; AJvYcCV3EbgELeR94/xf5ldp7EaPo+EU6gkwHn5oBVFg3dU3ECeoSqSriDzD8/vTrFAxJ+e6jEMnZRfzS7mvIH3ve5s=@vger.kernel.org, AJvYcCW2CHtiFiHzQABoY2fQcbpL3FBGaKYE5DpDrDr2w80zrYj74e+5jGu1Clj1LNpaifBKmfDg6FhmOh/fag==@vger.kernel.org, AJvYcCWfKLhs9N1dfB4WRp1i6qGTadT5qsV1pRHOtWMqq+9JLiVurDZlsZCVXb9InSITdoq5yazWn4+oVm9V@vger.kernel.org, AJvYcCWq2XDZ5qjLgl0o93fmlV0OzDLf5Ku8GEz+/U+/VaQrPY+V4D3UleykHvXMCAGmlHTf/XlOTzIVAG9J@vger.kernel.org, AJvYcCX03Tsto+BWnjLSvCXFEZ9YbtMuUvXDxJ8DLA9NZZfFUyQI6ibW+vHLmxXddWkybk+gmFHAqmIFsXw=@vger.kernel.org, AJvYcCXQhIgh9IJvkK9ERW1+4s0P/l187cyyQxpkt1l8UmXlWH7YVet2lZAa2cUmhSOo3wUOwd/oUO0LymUGA2k=@vger.kernel.org, AJvYcCXcIYz+7FygzYIh7YJZY5YFFi87ORttprd0EIPfdRXu/dSslGA11ucxKpJM0DoWaLUFmsc5WC8o@vger.kernel.org, AJvYcCXfBLK/E1QLSN/Wjpa1QmzVOwrDMWQK9YESn/Wh3hCcueeGhdqyGrKyCVVSB/+R0jFb4efTWaId5hZG@vger.kernel.org X-Gm-Message-State: AOJu0Ywi6y77Kkdbe7jh70LL0uHPl2r84KJix8c53C2GzymdeVmkzoAs rjNdc1gCZB2A8c83LSDStaAopOCoo7NnaEtZb3UkPFA9NLlayW6xnhBV X-Gm-Gg: ASbGncsS1F79+yHL7rvuMNgFs/ln5y2F+Tr3y5flxNYOESLiCaAUVyyPrNgq++W+YPR eKBjcSxAazCfujWfk0YnCysZDkVQP/qBPHSHdiSt61Oc4vGXgG8+CHswxP7tBDCsxR1PLPklvMI jVtql6AkbZaHd7dzQGpHoMMSZVrWt3G52QEwEnTR87aqnFHecBd0ZMLctDqDzUxEkpXOsEl7aTo SARxvqAX8X4hJd9bgcRP1EtyFCAco5zRjrsICAysaNyTr4pqqFocZoPE87hig5ZiEtCoYtcRwOi 6ttQ3CZ6TPKVz29b8Szoz8Pvikc4KGYqzZMRYHnLXc+j4GdQlIh9tfZaoro7QcWN9nqVWAW7kzE ifambANgN0y0E1w== X-Google-Smtp-Source: AGHT+IGQnI3ZWqLU6mPytsOfHSMgv1/YL6lKqHlR3rNbZqiFbhdQO43HR977KRRd3NGyFLEuZPuiTQ== X-Received: by 2002:a17:903:b8f:b0:220:be86:a421 with SMTP id d9443c01a7336-231de3ae560mr225452765ad.38.1747706670795; Mon, 19 May 2025 19:04: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 d9443c01a7336-231d4ac9fc8sm66543855ad.27.2025.05.19.19.04.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 May 2025 19:04:30 -0700 (PDT) From: a0282524688@gmail.com X-Google-Original-From: tmyu0@nuvoton.com To: 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 v11 5/7] watchdog: Add Nuvoton NCT6694 WDT support Date: Tue, 20 May 2025 10:03:53 +0800 Message-Id: <20250520020355.3885597-6-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250520020355.3885597-1-tmyu0@nuvoton.com> References: <20250520020355.3885597-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-watchdog@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Ming Yu This driver supports Watchdog timer functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- 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 | 320 +++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 drivers/watchdog/nct6694_wdt.c diff --git a/MAINTAINERS b/MAINTAINERS index 57b95c21a626..681e0cfb4a4b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17458,6 +17458,7 @@ F: drivers/gpio/gpio-nct6694.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/usb/nct6694_canfd.c +F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0d8d37f712e8..6d84a509501e 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -760,6 +760,17 @@ config MAX77620_WATCHDOG MAX77620 chips. To compile this driver as a module, choose M here: the module will be called max77620_wdt. +config NCT6694_WATCHDOG + tristate "Nuvoton NCT6694 watchdog support" + depends on MFD_NCT6694 + select WATCHDOG_CORE + help + Say Y here to support Nuvoton NCT6694 watchdog timer + functionality. + + This driver can also be built as a module. If so, the module + will be called nct6694_wdt. + config IMX2_WDT tristate "IMX2+ Watchdog" depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c9482904bf87..7fe51bb06060 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -233,6 +233,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o +obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c new file mode 100644 index 000000000000..195bcbc0f156 --- /dev/null +++ b/drivers/watchdog/nct6694_wdt.c @@ -0,0 +1,320 @@ +// 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] = { + [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_TIMEOUT +}; +module_param_array(timeout, int, NULL, 0644); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); + +static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] = { + [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_PRETIMEOUT +}; +module_param_array(pretimeout, int, NULL, 0644); +MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +enum { + NCT6694_ACTION_NONE = 0, + NCT6694_ACTION_SIRQ, + NCT6694_ACTION_GPO, +}; + +struct __packed nct6694_wdt_setup { + __le32 pretimeout; + __le32 timeout; + u8 owner; + u8 scratch; + u8 control; + u8 status; + __le32 countdown; +}; + +struct __packed nct6694_wdt_cmd { + __le32 wdt_cmd; + __le32 reserved; +}; + +union __packed nct6694_wdt_msg { + struct nct6694_wdt_setup setup; + struct nct6694_wdt_cmd cmd; +}; + +struct nct6694_wdt_data { + struct watchdog_device wdev; + struct device *dev; + struct nct6694 *nct6694; + struct mutex lock; + union nct6694_wdt_msg *msg; + unsigned char wdev_idx; +}; + +static DEFINE_IDA(nct6694_wdt_ida); + +static int nct6694_wdt_setting(struct watchdog_device *wdev, + u32 timeout_val, u8 timeout_act, + u32 pretimeout_val, u8 pretimeout_act) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694_wdt_setup *setup = &data->msg->setup; + const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_WDT_MOD, + .cmd = NCT6694_WDT_SETUP, + .sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx), + .len = cpu_to_le16(sizeof(*setup)) + }; + unsigned int timeout_fmt, pretimeout_fmt; + + guard(mutex)(&data->lock); + + if (pretimeout_val == 0) + pretimeout_act = NCT6694_ACTION_NONE; + + timeout_fmt = (timeout_val * 1000) | (timeout_act << 24); + pretimeout_fmt = (pretimeout_val * 1000) | (pretimeout_act << 24); + + memset(setup, 0, sizeof(*setup)); + setup->timeout = cpu_to_le32(timeout_fmt); + setup->pretimeout = cpu_to_le32(pretimeout_fmt); + + return nct6694_write_msg(data->nct6694, &cmd_hd, setup); +} + +static int nct6694_wdt_start(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + int ret; + + ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO, + wdev->pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + dev_dbg(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n", + data->wdev_idx, wdev->timeout, wdev->pretimeout); + + return ret; +} + +static int nct6694_wdt_stop(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694_wdt_cmd *cmd = &data->msg->cmd; + const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_WDT_MOD, + .cmd = NCT6694_WDT_COMMAND, + .sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx), + .len = cpu_to_le16(sizeof(*cmd)) + }; + + guard(mutex)(&data->lock); + + memcpy(&cmd->wdt_cmd, "WDTC", 4); + cmd->reserved = 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, cmd); +} + +static int nct6694_wdt_ping(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694_wdt_cmd *cmd = &data->msg->cmd; + const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_WDT_MOD, + .cmd = NCT6694_WDT_COMMAND, + .sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx), + .len = cpu_to_le16(sizeof(*cmd)) + }; + + guard(mutex)(&data->lock); + memcpy(&cmd->wdt_cmd, "WDTS", 4); + cmd->reserved = 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, cmd); +} + +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev, + unsigned int new_timeout) +{ + int ret; + + ret = nct6694_wdt_setting(wdev, new_timeout, NCT6694_ACTION_GPO, + wdev->pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + wdev->timeout = new_timeout; + + return 0; +} + +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev, + unsigned int new_pretimeout) +{ + int ret; + + ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO, + new_pretimeout, NCT6694_ACTION_GPO); + if (ret) + return ret; + + wdev->pretimeout = new_pretimeout; + + return 0; +} + +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694_wdt_setup *setup = &data->msg->setup; + const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_WDT_MOD, + .cmd = NCT6694_WDT_SETUP, + .sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx), + .len = cpu_to_le16(sizeof(*setup)) + }; + unsigned int timeleft_ms; + int ret; + + guard(mutex)(&data->lock); + + ret = nct6694_read_msg(data->nct6694, &cmd_hd, setup); + if (ret) + return 0; + + timeleft_ms = le32_to_cpu(setup->countdown); + + return timeleft_ms / 1000; +} + +static const struct watchdog_info nct6694_wdt_info = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, + .identity = DEVICE_NAME, +}; + +static const struct watchdog_ops nct6694_wdt_ops = { + .owner = THIS_MODULE, + .start = nct6694_wdt_start, + .stop = nct6694_wdt_stop, + .set_timeout = nct6694_wdt_set_timeout, + .set_pretimeout = nct6694_wdt_set_pretimeout, + .get_timeleft = nct6694_wdt_get_time, + .ping = nct6694_wdt_ping, +}; + +static void nct6694_wdt_ida_remove(void *d) +{ + struct nct6694_wdt_data *data = d; + + ida_free(&nct6694_wdt_ida, data->wdev_idx); +} + +static int nct6694_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct nct6694_wdt_data *data; + struct watchdog_device *wdev; + int ret, wdev_idx; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg), + GFP_KERNEL); + if (!data->msg) + return -ENOMEM; + + wdev_idx = ida_alloc(&nct6694_wdt_ida, GFP_KERNEL); + if (wdev_idx < 0) + return wdev_idx; + + ret = devm_add_action_or_reset(dev, nct6694_wdt_ida_remove, data); + if (ret) + return ret; + + data->dev = dev; + data->nct6694 = nct6694; + data->wdev_idx = wdev_idx; + + wdev = &data->wdev; + wdev->info = &nct6694_wdt_info; + wdev->ops = &nct6694_wdt_ops; + wdev->timeout = timeout[wdev_idx]; + wdev->pretimeout = pretimeout[wdev_idx]; + if (timeout[wdev_idx] < pretimeout[wdev_idx]) { + dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n"); + wdev->pretimeout = 0; + } + + wdev->min_timeout = 1; + wdev->max_timeout = 255; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + platform_set_drvdata(pdev, data); + + watchdog_set_drvdata(&data->wdev, data); + watchdog_set_nowayout(&data->wdev, nowayout); + watchdog_stop_on_reboot(&data->wdev); + + return devm_watchdog_register_device(dev, &data->wdev); +} + +static struct platform_driver nct6694_wdt_driver = { + .driver = { + .name = DEVICE_NAME, + }, + .probe = 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"); From patchwork Tue May 20 02:03:55 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 891515 Received: from mail-pl1-f175.google.com (mail-pl1-f175.google.com [209.85.214.175]) (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 E88DE25C81A; Tue, 20 May 2025 02:04:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706681; cv=none; b=sCB8SkHyqUxyvyXT/AwgY9C5QCT7z5wZtKmO677MGyGzoETdZX/M7FIKyAWUPcLHxB8tS9fVkzmpvI0sPB2HuCQLzm5+hy67Nx0llc6Wjw+0YA/qN32Ac3C2fpHtlB5TqtLBhP4+uMP8dEdvDpNVkofvQBd3mC7xrLFKMXzLhdw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747706681; c=relaxed/simple; bh=gv3jROYmmy5OuIPSin5GvRHRP65GFul/edwoC/XLnTg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=D2wmE6ntgRXEgjhAe8rtO9Cx0WAygbaJYB+dXqu5oZSGh6TBFZtmuP3aWuFylBp3adlg55TLdlvFAtyDllUHxKsCrjJ6DGmRIpZqo57IL3kIBqt664QngtXkmADIpIX/tPbBiJlMk7DcrNjWhJ315UwJOboxrMZ2HyJ0jomArYU= 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=YHRzASBU; arc=none smtp.client-ip=209.85.214.175 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="YHRzASBU" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-2320d2a7852so19330965ad.3; Mon, 19 May 2025 19:04:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747706679; x=1748311479; 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=FhDHTmLGe4Aw+TV8M70GrLKaReLMCHE0YVjdnwPrHVw=; b=YHRzASBUZmXX1VKoUL+zCuFN6w8xAkDOIaWyEb7Jc6aOmpXhkCeRRHYyEuiBz7hfQG U4DN5tHxwE86IpP2PSOzOIErX12HiUfIihOH3uOJiVXcJhJ74F2mY6djbfOkLoqQ3AMD Z8XflbZIdQDOyhVoBway1xnJ/6N2eYW16Z3coBytuum43grlf+pKFNiW9h5N4c1VueOI OteRpqqnwe7hGwpTlQKYlmh/HJfPKnOxxF3fAwVwnoyPu3Gi53dB8tvvrdT5kZxQHuPC TTB+q7Qb+gXHqkqrJxRs3nsVrMBpY6y6zjPaPC6VmCEa+EFFr1zWqiymOOlxJLCj0U7E 9QZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747706679; x=1748311479; 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=FhDHTmLGe4Aw+TV8M70GrLKaReLMCHE0YVjdnwPrHVw=; b=Nwpjm9EN9/BavfiX3XIQS0dGdnjaoMQCj+X/0wmDlZoUiarXrnbaZ1ioShu/6Eekw0 QVzxj0sX2gBu5dtWeoAXWAS/2U7UtcVQa3cVXSdHgg8XQZ3x3zgtqdqf41Ep22qItGL+ 3MVXAJf2UE4PD5pY208gq+XeN+HzTbSs4mMeidLWJQrsQ5DZ5poTZB6k056Q8asYdW20 CA9ap4YmfePI/kDBMQKE2ZldF8MJ87P9zpejs9FJ3qXWnbTdGOrf5ewPoZmZGn9o8Bl/ ZDpc1QwWuKYG0+KMX0PIYhUMeVzlrAGzFxJlU3TIbFtkHhSlzPMCcWhgTcNInN6u6Rep g2VA== X-Forwarded-Encrypted: i=1; AJvYcCUHxZ8h0XJMFKNreQ++ssahrL7splUJWO/iwn/RaAXcA/ELUfNfrbVVTx8hjY4gkXFKtJgzIMIRHBBf@vger.kernel.org, AJvYcCUhKiXH2Lzii7KDSVFSlWuoeICwY1G7CB9WbEg2dFTS17y3j77vJ+g6fSVp1q5qJQvr5ZE5xWjl@vger.kernel.org, AJvYcCVvtL8iBEZ7iozYc6C06lW39CNxHl+wPVYpLUzxJH1XPsYFDiYh9pHoZIEuJHjSzGnJBRPS3C46T5wSqU8X4yM=@vger.kernel.org, AJvYcCWMqlvp6oBdjHdl3QHq/uv0wo2P9cQ3aBS3te8YDo1/PB+cwuRe3rre879FwEHAV8nfXp9rBkp2VnjM@vger.kernel.org, AJvYcCWzOjThriYW1OLI6bAqam188eNXVOqBfItCiRm9WK1KScrTEZvD4qUGx8xqPruPaCZbl/jiIOucQ3eqHPg=@vger.kernel.org, AJvYcCXL7zzhIucufcCndObLeqdUb7ghfHb64Xk4xhtXuv6ww+k6Hx1IYRhV0rznCPjodIZxuVVEmFhDNZZ4Vw==@vger.kernel.org, AJvYcCXR5C+fUjIKdKLnsaX3fTn5jy2r96blHTFejkT3tzv1AlDxCs5IAS7c+sLocC3mtCQSsF+jNsxNmRNO@vger.kernel.org, AJvYcCXpPCm/+HseTBNcwDZXb17Tfx+H9ad7d7Kq2wqG9dd+85JUvt9Z9SxNjmgV7oZPp96+un12e7tcRCI=@vger.kernel.org X-Gm-Message-State: AOJu0YxoO+iBRZhOx8anue0O9kKhzE+5YKWQODcC3ujM0wCiz1Rh1xlA AcckEwYne45wgaf/0gmRC+YOJsswZjVY1hgGv7OpqZ48lNOHi9bWaZQ5 X-Gm-Gg: ASbGnct6M8waQ2fXIbw+16HmVWisgRMhH23OCG6aaV5B1M1vP0nlYFvlarlumgfbkcf w3MVTO8XlPwsPh5/vitpVPysCB3NGhGH7dluV050pqsoKPSbq2gbliznsNGQWjJp2wwD3HIG8Xg xJOCWg9AlVq6f6NlHZ89nzb6HTvvZcBVjuokqxMFn3herX4OuAtDAD7v1t9wENK1gKf6c3qRj6o 9gl3Eb0CNNwA0mhNWApJffr/ddx2g8OhF0/wnEKU1OB3OHoy9JZiQDXLfVQ7k6S2KH+osniF4sK HArzb1LcW9OhtnZ+bW35Mxct4uymHAVuuT6R1XxUygFF8Nxy4Hd+N6rsI0VDQitGL3UCIjY62Dh gQD9xVEMdkWzLBA== X-Google-Smtp-Source: AGHT+IEwMwmBnwd9oHAp/qdOb2DHfhiwm4y9PhjuuRCIxicbZNMXHe8PfD0N/7gHr8v2Xt+jj4qSgg== X-Received: by 2002:a17:902:cf42:b0:22e:8062:f77a with SMTP id d9443c01a7336-231de3baf69mr185743715ad.52.1747706679031; Mon, 19 May 2025 19:04:39 -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 d9443c01a7336-231d4ac9fc8sm66543855ad.27.2025.05.19.19.04.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 May 2025 19:04:38 -0700 (PDT) From: a0282524688@gmail.com X-Google-Original-From: tmyu0@nuvoton.com To: 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 v11 7/7] rtc: Add Nuvoton NCT6694 RTC support Date: Tue, 20 May 2025 10:03:55 +0800 Message-Id: <20250520020355.3885597-8-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250520020355.3885597-1-tmyu0@nuvoton.com> References: <20250520020355.3885597-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-watchdog@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 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 661646cab68f..91ea3eafc533 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17459,6 +17459,7 @@ F: drivers/hwmon/nct6694-hwmon.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/usb/nct6694_canfd.c +F: drivers/rtc/rtc-nct6694.c F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 838bdc138ffe..d8662b5d1e47 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -416,6 +416,16 @@ config RTC_DRV_NCT3018Y This driver can also be built as a module, if so, the module will be called "rtc-nct3018y". +config RTC_DRV_NCT6694 + tristate "Nuvoton NCT6694 RTC support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to RTC. + + This driver can also be built as a module. If so, the module will + be called rtc-nct6694. + config RTC_DRV_RK808 tristate "Rockchip RK805/RK808/RK809/RK817/RK818 RTC" depends on MFD_RK8XX diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 31473b3276d9..da091d66e2d7 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o obj-$(CONFIG_RTC_DRV_GAMECUBE) += rtc-gamecube.o obj-$(CONFIG_RTC_DRV_NCT3018Y) += rtc-nct3018y.o +obj-$(CONFIG_RTC_DRV_NCT6694) += rtc-nct6694.o obj-$(CONFIG_RTC_DRV_NTXEC) += rtc-ntxec.o obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o obj-$(CONFIG_RTC_DRV_OPAL) += rtc-opal.o diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c new file mode 100644 index 000000000000..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 alarm */ +#define NCT6694_RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */ + +#define NCT6694_RTC_IRQ_EN (NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_EN) +#define NCT6694_RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */ + +struct __packed nct6694_rtc_time { + u8 sec; + u8 min; + u8 hour; + u8 week; + u8 day; + u8 month; + u8 year; +}; + +struct __packed nct6694_rtc_alarm { + u8 sec; + u8 min; + u8 hour; + u8 alarm_en; + u8 alarm_pend; +}; + +struct __packed nct6694_rtc_status { + u8 irq_en; + u8 irq_pend; +}; + +union __packed nct6694_rtc_msg { + struct nct6694_rtc_time time; + struct nct6694_rtc_alarm alarm; + struct nct6694_rtc_status sts; +}; + +struct nct6694_rtc_data { + struct nct6694 *nct6694; + struct rtc_device *rtc; + union nct6694_rtc_msg *msg; + int irq; +}; + +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + struct nct6694_rtc_time *time = &data->msg->time; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_TIME, + .sel = NCT6694_RTC_TIME_SEL, + .len = cpu_to_le16(sizeof(*time)) + }; + int ret; + + ret = nct6694_read_msg(data->nct6694, &cmd_hd, time); + if (ret) + return ret; + + tm->tm_sec = bcd2bin(time->sec); /* tm_sec expect 0 ~ 59 */ + tm->tm_min = bcd2bin(time->min); /* tm_min expect 0 ~ 59 */ + tm->tm_hour = bcd2bin(time->hour); /* tm_hour expect 0 ~ 23 */ + tm->tm_wday = bcd2bin(time->week) - 1; /* tm_wday expect 0 ~ 6 */ + tm->tm_mday = bcd2bin(time->day); /* tm_mday expect 1 ~ 31 */ + tm->tm_mon = bcd2bin(time->month) - 1; /* tm_month expect 0 ~ 11 */ + tm->tm_year = bcd2bin(time->year) + 100; /* tm_year expect since 1900 */ + + return ret; +} + +static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + struct nct6694_rtc_time *time = &data->msg->time; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_TIME, + .sel = NCT6694_RTC_TIME_SEL, + .len = cpu_to_le16(sizeof(*time)) + }; + + time->sec = bin2bcd(tm->tm_sec); + time->min = bin2bcd(tm->tm_min); + time->hour = bin2bcd(tm->tm_hour); + time->week = bin2bcd(tm->tm_wday + 1); + time->day = bin2bcd(tm->tm_mday); + time->month = bin2bcd(tm->tm_mon + 1); + time->year = bin2bcd(tm->tm_year - 100); + + return nct6694_write_msg(data->nct6694, &cmd_hd, time); +} + +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + struct nct6694_rtc_alarm *alarm = &data->msg->alarm; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_ALARM, + .sel = NCT6694_RTC_ALARM_SEL, + .len = cpu_to_le16(sizeof(*alarm)) + }; + int ret; + + ret = nct6694_read_msg(data->nct6694, &cmd_hd, alarm); + if (ret) + return ret; + + alrm->time.tm_sec = bcd2bin(alarm->sec); + alrm->time.tm_min = bcd2bin(alarm->min); + alrm->time.tm_hour = bcd2bin(alarm->hour); + alrm->enabled = alarm->alarm_en; + alrm->pending = alarm->alarm_pend; + + return ret; +} + +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + struct nct6694_rtc_alarm *alarm = &data->msg->alarm; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_ALARM, + .sel = NCT6694_RTC_ALARM_SEL, + .len = cpu_to_le16(sizeof(*alarm)) + }; + + alarm->sec = bin2bcd(alrm->time.tm_sec); + alarm->min = bin2bcd(alrm->time.tm_min); + alarm->hour = bin2bcd(alrm->time.tm_hour); + alarm->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0; + alarm->alarm_pend = 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, alarm); +} + +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + struct nct6694_rtc_status *sts = &data->msg->sts; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_STATUS, + .sel = NCT6694_RTC_STATUS_SEL, + .len = cpu_to_le16(sizeof(*sts)) + }; + + if (enabled) + sts->irq_en |= NCT6694_RTC_IRQ_EN; + else + sts->irq_en &= ~NCT6694_RTC_IRQ_EN; + + sts->irq_pend = 0; + + return nct6694_write_msg(data->nct6694, &cmd_hd, sts); +} + +static const struct rtc_class_ops nct6694_rtc_ops = { + .read_time = nct6694_rtc_read_time, + .set_time = nct6694_rtc_set_time, + .read_alarm = nct6694_rtc_read_alarm, + .set_alarm = nct6694_rtc_set_alarm, + .alarm_irq_enable = nct6694_rtc_alarm_irq_enable, +}; + +static irqreturn_t nct6694_irq(int irq, void *dev_id) +{ + struct nct6694_rtc_data *data = dev_id; + struct nct6694_rtc_status *sts = &data->msg->sts; + static const struct nct6694_cmd_header cmd_hd = { + .mod = NCT6694_RTC_MOD, + .cmd = NCT6694_RTC_STATUS, + .sel = NCT6694_RTC_STATUS_SEL, + .len = cpu_to_le16(sizeof(*sts)) + }; + int ret; + + rtc_lock(data->rtc); + + sts->irq_en = NCT6694_RTC_IRQ_EN; + sts->irq_pend = NCT6694_RTC_IRQ_STS; + ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts); + if (ret) { + rtc_unlock(data->rtc); + return IRQ_NONE; + } + + rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF); + + rtc_unlock(data->rtc); + + return IRQ_HANDLED; +} + +static void nct6694_irq_dispose_mapping(void *d) +{ + struct nct6694_rtc_data *data = d; + + irq_dispose_mapping(data->irq); +} + +static int nct6694_rtc_probe(struct platform_device *pdev) +{ + struct nct6694_rtc_data *data; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg), + GFP_KERNEL); + if (!data->msg) + return -ENOMEM; + + data->irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC); + if (!data->irq) + return -EINVAL; + + ret = devm_add_action_or_reset(&pdev->dev, nct6694_irq_dispose_mapping, + data); + if (ret) + return ret; + + ret = devm_device_init_wakeup(&pdev->dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to init wakeup\n"); + + data->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(data->rtc)) + return PTR_ERR(data->rtc); + + data->nct6694 = nct6694; + data->rtc->ops = &nct6694_rtc_ops; + data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; + data->rtc->range_max = RTC_TIMESTAMP_END_2099; + + platform_set_drvdata(pdev, data); + + ret = devm_request_threaded_irq(&pdev->dev, data->irq, NULL, + nct6694_irq, IRQF_ONESHOT, + "rtc-nct6694", data); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + + return devm_rtc_register_device(data->rtc); +} + +static struct platform_driver nct6694_rtc_driver = { + .driver = { + .name = "nct6694-rtc", + }, + .probe = nct6694_rtc_probe, +}; + +module_platform_driver(nct6694_rtc_driver); + +MODULE_DESCRIPTION("USB-RTC driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nct6694-rtc");