From patchwork Thu Oct 24 08:59:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848557 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (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 3E8851B0F19; Thu, 24 Oct 2024 08:59:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760393; cv=none; b=FAHW1gg6jUpBlbsYGrrf6sYiKrYUGu+YnzZBFwbjVdJGmlWtG1HQJFnnjIF3ObzWnnqp5QHH7/Fkl3G333Osy/pm100ck6UCPUDb3nFHFMtMHm3dIGpyJk8BpCIKldhe0mx2q7X/FxBGdhARXDbAPz2B1mnaho0/kXRTalKyQsU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760393; c=relaxed/simple; bh=IuTA3S4y8FkEEn7g5DNk+66BHDM5lGS44rGjEIu5vEQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=h13qjc5TSzoOhBDsCD3PWQoQSXH6+nn7KpTn17kQECjL0sDr10nc/PjZFD5a55BUbgK6Jqi0b1GFraWZSiEpVz4xt6o2hdGar7dwoq1JAbpI6XPyHbkaoyZ69CK/3kCxDnXs54+7W1eF2I+AbkatYsfFIQGPVj51PUxYBCRBEmM= 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=P90yStaE; arc=none smtp.client-ip=209.85.214.181 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="P90yStaE" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-20cd76c513cso5035385ad.3; Thu, 24 Oct 2024 01:59:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760389; x=1730365189; 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=2n3jv44FOaurqNOt74iqXLzFIjz7mqyBdb/BAo9EhVU=; b=P90yStaEqVIsb0GnyUn9FBDku9ZIstoMAp2U6SUwfB0+xWjikM8+kQ3VENNsggloQn WKodKuHHbXQzK3ccsFcarK2L++Wms9CgxjHFihAQSmUt5PGJuumaPgruVN+ScTdOOmOc JoT+TNZ5y9d/SROJ6PXgYhbG1P9NF3vOjJt9OAMF1x1aEfC4dcP1WVG3UwCGsQRvs9PK zsnsp08wDCr4A9FzjxCn/U2tcN7QFPFdf98b885BPDpG49mT60Q05k0cNgKjD+28P2xW kaFVYcfXhQy9Th8PR4SeL91j5bjjKCZKBH9Ec0LdMrGo5kBIBlECKEz6dEanN926ApCI wfUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760389; x=1730365189; 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=2n3jv44FOaurqNOt74iqXLzFIjz7mqyBdb/BAo9EhVU=; b=ozUU0pgQKcoYAjp1RDKjhPNnmFpNfUr3gLbmbTofue/YF40pwFHfIB4sBUtC1em87o qnKIk1YLjsD/LwDrrwS3OMFPwvMAIrSb4XyuD172/3tjkzg+uQGDiAesYFKSzn9AfTnd 7fa1xN2uZnbBOGt0uWyuh/upgbbi2gCBbTi6w49q2biiFBf5CdyktCsx7xm1bXQe30D7 NTjpsqu54bH3GMgfkoI95siujq2NxfizJx7kascoAEcqdp4Ceyijk91SjxdWNMxvBVhK XM57o4vdvuDFYLwjjRM9uM/MrIOVfrCwkEtaQeix4Wq0gjAXkHZ00ds0//VOKKagJFQh QgTQ== X-Forwarded-Encrypted: i=1; AJvYcCUKhxUykn/L1zb05Wn43vGjkiIEA9Y6i2aRZ9trr4XlnAqCiYkMO2WxybUX8SufTEYbozLvhXBsGaJYGw4=@vger.kernel.org, AJvYcCUkTCSCnmQOcBQitBuOQxFHH7unsYxiQuP1okpiOxXqTgHYHUD+DNYxp1PkiOpxQxyT6mXV2syLMew=@vger.kernel.org, AJvYcCUmEcv0DeCUVf4zpM8sQnYdPGjlkRImMIk19q+6yU/Y+vdSSAJA2Nnqz7SEVZ4HMP+Ls29q0UUZAlbuNVuVQSk=@vger.kernel.org, AJvYcCVCKADK62cbruh31rf52lYvFLZz5vZZuD95mg6Ry7si+jfPrWeI0dxy2yTLSNxVNt9kKwXapYua3dqO@vger.kernel.org, AJvYcCVZCMIj6AL0wJ8XfrOCDbpWlYZUGU9Jzc4xUnIk94IcbEOWDiftyOLg23vJ9+yWoKKz72ehF7oZRHPc@vger.kernel.org, AJvYcCVuUWVNUbkT+HCtJ/o6lpqWryJf1psH6U09v0/JaVqgPwDIy/OheAUpEd9aH9Ci6a+BaYcFziLQj4T2@vger.kernel.org, AJvYcCWn/1WJsgL8Cuu9Cju3tbJ+RIy5Sb9mR7sLZerIGA9f1kT6Vo1+Aw6R/jABbCyZbXhYxCU5yUf6Zf3amA==@vger.kernel.org, AJvYcCWreIvRLA5UCR2+UBKn+ZdiJNQPyvgr7WSeaZ9t3+YZI22rH0gpiTXfZP6uIQbi6h84usaJ+XAe@vger.kernel.org, AJvYcCXqxwus1f9eD510XXd6dOdGc77P5iLFnxXCzMjR4/cYvjeLtXSjhvLj8AIddKP+SoXLmC5ZPPN7/k83@vger.kernel.org X-Gm-Message-State: AOJu0YzrxI6ft0PTWX3B33bvTLQ3JNh88kr/LVuX8X4rZYGVwSgAsPw/ gZ7xAaJW04TARASNuHbxf4C3ym4gtFs+0Lf1fK+i6BELBUqeXcMm X-Google-Smtp-Source: AGHT+IEJzwqQH2kaT93X08v5aqyeGmqbrSsY3vryGf217SMBOL08qYdnPZj3mt0qKUUlGvzky73sMw== X-Received: by 2002:a17:903:184:b0:20c:da98:d752 with SMTP id d9443c01a7336-20fa9df25f4mr75245485ad.16.1729760389386; Thu, 24 Oct 2024 01:59:49 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.01.59.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 01:59:49 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 1/9] mfd: Add core driver for Nuvoton NCT6694 Date: Thu, 24 Oct 2024 16:59:14 +0800 Message-Id: <20241024085922.133071-2-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, PWM, and RTC. This driver implements USB device functionality and shares the chip's peripherals as a child device. Each child device can use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue a command. They can also register a handler function that will be called when the USB device receives its interrupt pipe. Signed-off-by: Ming Yu --- MAINTAINERS | 7 + drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 2 + drivers/mfd/nct6694.c | 394 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/nct6694.h | 168 +++++++++++++++ 5 files changed, 581 insertions(+) create mode 100644 drivers/mfd/nct6694.c create mode 100644 include/linux/mfd/nct6694.h diff --git a/MAINTAINERS b/MAINTAINERS index e9659a5a7fb3..30157ca95cf3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16434,6 +16434,13 @@ F: drivers/nubus/ F: include/linux/nubus.h F: include/uapi/linux/nubus.h +NUVOTON NCT6694 MFD DRIVER +M: Ming Yu +L: linux-kernel@vger.kernel.org +S: Supported +F: drivers/mfd/nct6694.c +F: include/linux/mfd/nct6694.h + NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER M: Antonino Daplas L: linux-fbdev@vger.kernel.org diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f9325bcce1b9..da2600958697 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -546,6 +546,16 @@ config MFD_MX25_TSADC i.MX25 processors. They consist of a conversion queue for general purpose ADC and a queue for Touchscreens. +config MFD_NCT6694 + tristate "Nuvoton NCT6694 support" + select MFD_CORE + depends on USB + help + This adds support for Nuvoton USB device NCT6694 sharing peripherals + This includes the USB devcie driver and core APIs. + Additional drivers must be enabled in order to use the functionality + of the device. + config MFD_HI6421_PMIC tristate "HiSilicon Hi6421 PMU/Codec IC" depends on OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 2a9f91e81af8..2cf816d67d03 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -116,6 +116,8 @@ obj-$(CONFIG_TWL6040_CORE) += twl6040.o obj-$(CONFIG_MFD_MX25_TSADC) += fsl-imx25-tsadc.o +obj-$(CONFIG_MFD_NCT6694) += nct6694.o + obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c new file mode 100644 index 000000000000..9838c7be0b98 --- /dev/null +++ b/drivers/mfd/nct6694.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 MFD driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-usb_mfd" + +#define MFD_DEV_SIMPLE(_name) \ +{ \ + .name = NCT6694_DEV_##_name, \ +} \ + +#define MFD_DEV_WITH_ID(_name, _id) \ +{ \ + .name = NCT6694_DEV_##_name, \ + .id = _id, \ +} + +/* MFD device resources */ +static const struct mfd_cell nct6694_dev[] = { + MFD_DEV_WITH_ID(GPIO, 0x0), + MFD_DEV_WITH_ID(GPIO, 0x1), + MFD_DEV_WITH_ID(GPIO, 0x2), + MFD_DEV_WITH_ID(GPIO, 0x3), + MFD_DEV_WITH_ID(GPIO, 0x4), + MFD_DEV_WITH_ID(GPIO, 0x5), + MFD_DEV_WITH_ID(GPIO, 0x6), + MFD_DEV_WITH_ID(GPIO, 0x7), + MFD_DEV_WITH_ID(GPIO, 0x8), + MFD_DEV_WITH_ID(GPIO, 0x9), + MFD_DEV_WITH_ID(GPIO, 0xA), + MFD_DEV_WITH_ID(GPIO, 0xB), + MFD_DEV_WITH_ID(GPIO, 0xC), + MFD_DEV_WITH_ID(GPIO, 0xD), + MFD_DEV_WITH_ID(GPIO, 0xE), + MFD_DEV_WITH_ID(GPIO, 0xF), + + MFD_DEV_WITH_ID(I2C, 0x0), + MFD_DEV_WITH_ID(I2C, 0x1), + MFD_DEV_WITH_ID(I2C, 0x2), + MFD_DEV_WITH_ID(I2C, 0x3), + MFD_DEV_WITH_ID(I2C, 0x4), + MFD_DEV_WITH_ID(I2C, 0x5), + + MFD_DEV_WITH_ID(CAN, 0x0), + MFD_DEV_WITH_ID(CAN, 0x1), + + MFD_DEV_WITH_ID(WDT, 0x0), + MFD_DEV_WITH_ID(WDT, 0x1), + + MFD_DEV_SIMPLE(IIO), + MFD_DEV_SIMPLE(HWMON), + MFD_DEV_SIMPLE(PWM), + MFD_DEV_SIMPLE(RTC), +}; + +int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit, + void (*handler)(void *), void *private_data) +{ + struct nct6694_handler_entry *entry; + unsigned long flags; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->irq_bit = irq_bit; + entry->handler = handler; + entry->private_data = private_data; + + spin_lock_irqsave(&nct6694->lock, flags); + list_add_tail(&entry->list, &nct6694->handler_list); + spin_unlock_irqrestore(&nct6694->lock, flags); + + return 0; +} +EXPORT_SYMBOL(nct6694_register_handler); + +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, u16 length, + u8 rd_idx, u8 rd_len, unsigned char *buf) +{ + struct usb_device *udev = nct6694->udev; + unsigned char err_status; + int len, packet_len, tx_len, rx_len; + int i, ret; + + mutex_lock(&nct6694->access_lock); + + /* Send command packet to USB device */ + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod; + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF; + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF; + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_GET; + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF; + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF; + + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT), + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len, + nct6694->timeout); + if (ret) + goto err; + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT), + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len, + nct6694->timeout); + if (ret) + goto err; + + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX]; + + /* + * Segmented reception of messages that exceed the size of USB bulk + * pipe packets. + */ + for (i = 0, len = length; len > 0; i++, len -= packet_len) { + if (len > nct6694->maxp) + packet_len = nct6694->maxp; + else + packet_len = len; + + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT), + nct6694->rx_buffer + nct6694->maxp * i, + packet_len, &rx_len, nct6694->timeout); + if (ret) + goto err; + } + + for (i = 0; i < rd_len; i++) + buf[i] = nct6694->rx_buffer[i + rd_idx]; + + if (err_status) { + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status); + ret = -EIO; + } + +err: + mutex_unlock(&nct6694->access_lock); + return ret; +} +EXPORT_SYMBOL(nct6694_read_msg); + +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset, + u16 length, unsigned char *buf) +{ + struct usb_device *udev = nct6694->udev; + unsigned char err_status; + int len, packet_len, tx_len, rx_len; + int i, ret; + + mutex_lock(&nct6694->access_lock); + + /* Send command packet to USB device */ + nct6694->cmd_buffer[REQUEST_MOD_IDX] = mod; + nct6694->cmd_buffer[REQUEST_CMD_IDX] = offset & 0xFF; + nct6694->cmd_buffer[REQUEST_SEL_IDX] = (offset >> 8) & 0xFF; + nct6694->cmd_buffer[REQUEST_HCTRL_IDX] = HCTRL_SET; + nct6694->cmd_buffer[REQUEST_LEN_L_IDX] = length & 0xFF; + nct6694->cmd_buffer[REQUEST_LEN_H_IDX] = (length >> 8) & 0xFF; + + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT), + nct6694->cmd_buffer, CMD_PACKET_SZ, &tx_len, + nct6694->timeout); + if (ret) + goto err; + + /* + * Segmented transmission of messages that exceed the size of USB bulk + * pipe packets. + */ + for (i = 0, len = length; len > 0; i++, len -= packet_len) { + if (len > nct6694->maxp) + packet_len = nct6694->maxp; + else + packet_len = len; + + memcpy(nct6694->tx_buffer + nct6694->maxp * i, + buf + nct6694->maxp * i, packet_len); + + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT), + nct6694->tx_buffer + nct6694->maxp * i, + packet_len, &tx_len, nct6694->timeout); + if (ret) + goto err; + } + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT), + nct6694->rx_buffer, CMD_PACKET_SZ, &rx_len, + nct6694->timeout); + if (ret) + goto err; + + err_status = nct6694->rx_buffer[RESPONSE_STS_IDX]; + + /* + * Segmented reception of messages that exceed the size of USB bulk + * pipe packets. + */ + for (i = 0, len = length; len > 0; i++, len -= packet_len) { + if (len > nct6694->maxp) + packet_len = nct6694->maxp; + else + packet_len = len; + + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, BULK_IN_ENDPOINT), + nct6694->rx_buffer + nct6694->maxp * i, + packet_len, &rx_len, nct6694->timeout); + if (ret) + goto err; + } + + memcpy(buf, nct6694->rx_buffer, length); + + if (err_status) { + pr_debug("%s: MSG CH status = %2Xh\n", __func__, err_status); + ret = -EIO; + } + +err: + mutex_unlock(&nct6694->access_lock); + return ret; +} +EXPORT_SYMBOL(nct6694_write_msg); + +static void usb_int_callback(struct urb *urb) +{ + unsigned char *int_status = urb->transfer_buffer; + struct nct6694 *nct6694 = urb->context; + struct nct6694_handler_entry *entry; + unsigned long flags; + int ret; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto resubmit; + } + + spin_lock_irqsave(&nct6694->lock, flags); + + list_for_each_entry(entry, &nct6694->handler_list, list) { + if (int_status[INT_IN_IRQ_IDX] & entry->irq_bit) + entry->handler(entry->private_data); + } + + spin_unlock_irqrestore(&nct6694->lock, flags); + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + pr_debug("%s: Failed to resubmit urb, status %d", + __func__, ret); +} + +static int nct6694_usb_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(iface); + struct device *dev = &udev->dev; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *int_endpoint; + struct nct6694 *nct6694; + int pipe, maxp, bulk_pipe; + int ret = EINVAL; + + interface = iface->cur_altsetting; + /* Binding interface class : 0xFF */ + if (interface->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC || + interface->desc.bInterfaceSubClass != 0x00 || + interface->desc.bInterfaceProtocol != 0x00) + return -ENODEV; + + int_endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(int_endpoint)) + return -ENODEV; + + nct6694 = devm_kzalloc(&udev->dev, sizeof(*nct6694), GFP_KERNEL); + if (!nct6694) + return -ENOMEM; + + pipe = usb_rcvintpipe(udev, INT_IN_ENDPOINT); + maxp = usb_maxpacket(udev, pipe); + + nct6694->cmd_buffer = devm_kcalloc(dev, CMD_PACKET_SZ, + sizeof(unsigned char), GFP_KERNEL); + if (!nct6694->cmd_buffer) + return -ENOMEM; + nct6694->rx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ, + sizeof(unsigned char), GFP_KERNEL); + if (!nct6694->rx_buffer) + return -ENOMEM; + nct6694->tx_buffer = devm_kcalloc(dev, MAX_PACKET_SZ, + sizeof(unsigned char), GFP_KERNEL); + if (!nct6694->tx_buffer) + return -ENOMEM; + nct6694->int_buffer = devm_kcalloc(dev, MAX_PACKET_SZ, + sizeof(unsigned char), GFP_KERNEL); + if (!nct6694->int_buffer) + return -ENOMEM; + + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!nct6694->int_in_urb) { + dev_err(&udev->dev, "Failed to allocate INT-in urb!\n"); + return -ENOMEM; + } + + /* Bulk pipe maximum packet for each transaction */ + bulk_pipe = usb_sndbulkpipe(udev, BULK_OUT_ENDPOINT); + nct6694->maxp = usb_maxpacket(udev, bulk_pipe); + + mutex_init(&nct6694->access_lock); + nct6694->udev = udev; + nct6694->timeout = URB_TIMEOUT; /* Wait until urb complete */ + + INIT_LIST_HEAD(&nct6694->handler_list); + spin_lock_init(&nct6694->lock); + + usb_fill_int_urb(nct6694->int_in_urb, udev, pipe, + nct6694->int_buffer, maxp, usb_int_callback, + nct6694, int_endpoint->bInterval); + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL); + if (ret) + goto err_urb; + + dev_set_drvdata(&udev->dev, nct6694); + usb_set_intfdata(iface, nct6694); + + ret = mfd_add_hotplug_devices(&udev->dev, nct6694_dev, + ARRAY_SIZE(nct6694_dev)); + if (ret) { + dev_err(&udev->dev, "Failed to add mfd's child device\n"); + goto err_mfd; + } + + nct6694->async_workqueue = alloc_ordered_workqueue("asyn_workqueue", 0); + + dev_info(&udev->dev, "Probed device: (%04X:%04X)\n", + id->idVendor, id->idProduct); + return 0; + +err_mfd: + usb_kill_urb(nct6694->int_in_urb); +err_urb: + usb_free_urb(nct6694->int_in_urb); + return ret; +} + +static void nct6694_usb_disconnect(struct usb_interface *iface) +{ + struct usb_device *udev = interface_to_usbdev(iface); + struct nct6694 *nct6694 = usb_get_intfdata(iface); + + mfd_remove_devices(&udev->dev); + flush_workqueue(nct6694->async_workqueue); + destroy_workqueue(nct6694->async_workqueue); + usb_set_intfdata(iface, NULL); + usb_kill_urb(nct6694->int_in_urb); + usb_free_urb(nct6694->int_in_urb); +} + +static const struct usb_device_id nct6694_ids[] = { + { USB_DEVICE(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID)}, + {}, +}; +MODULE_DEVICE_TABLE(usb, nct6694_ids); + +static struct usb_driver nct6694_usb_driver = { + .name = DRVNAME, + .id_table = nct6694_ids, + .probe = nct6694_usb_probe, + .disconnect = nct6694_usb_disconnect, +}; + +module_usb_driver(nct6694_usb_driver); + +MODULE_DESCRIPTION("USB-MFD driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h new file mode 100644 index 000000000000..0797564363be --- /dev/null +++ b/include/linux/mfd/nct6694.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Nuvoton NCT6694 USB transaction and data structure. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#ifndef __MFD_NCT6694_H +#define __MFD_NCT6694_H + +#define NCT6694_DEV_GPIO "nct6694-gpio" +#define NCT6694_DEV_I2C "nct6694-i2c" +#define NCT6694_DEV_CAN "nct6694-can" +#define NCT6694_DEV_WDT "nct6694-wdt" +#define NCT6694_DEV_IIO "nct6694-iio" +#define NCT6694_DEV_HWMON "nct6694-hwmon" +#define NCT6694_DEV_PWM "nct6694-pwm" +#define NCT6694_DEV_RTC "nct6694-rtc" + +#define NCT6694_VENDOR_ID 0x0416 +#define NCT6694_PRODUCT_ID 0x200B +#define INT_IN_ENDPOINT 0x81 +#define BULK_IN_ENDPOINT 0x82 +#define BULK_OUT_ENDPOINT 0x03 +#define MAX_PACKET_SZ 0x100 + +#define CMD_PACKET_SZ 0x8 +#define HCTRL_SET 0x40 +#define HCTRL_GET 0x80 + +#define REQUEST_MOD_IDX 0x01 +#define REQUEST_CMD_IDX 0x02 +#define REQUEST_SEL_IDX 0x03 +#define REQUEST_HCTRL_IDX 0x04 +#define REQUEST_LEN_L_IDX 0x06 +#define REQUEST_LEN_H_IDX 0x07 + +#define RESPONSE_STS_IDX 0x01 + +#define INT_IN_IRQ_IDX 0x00 +#define GPIO_IRQ_STATUS BIT(0) +#define CAN_IRQ_STATUS BIT(2) +#define RTC_IRQ_STATUS BIT(3) + +#define URB_TIMEOUT 1000 + +/* + * struct nct6694 - Nuvoton NCT6694 structure + * + * @udev: Pointer to the USB device + * @int_in_urb: Interrupt pipe urb + * @access_lock: USB transaction lock + * @handler_list: List of registered handlers + * @async_workqueue: Workqueue of processing asynchronous work + * @tx_buffer: USB write message buffer + * @rx_buffer: USB read message buffer + * @cmd_buffer: USB send command message buffer + * @int_buffer: USB receive interrupt message buffer + * @lock: Handlers lock + * @timeout: URB timeout + * @maxp: Maximum packet of bulk pipe + */ +struct nct6694 { + struct usb_device *udev; + struct urb *int_in_urb; + struct list_head handler_list; + struct workqueue_struct *async_workqueue; + + /* Make sure that every USB transaction is not interrupted */ + struct mutex access_lock; + + unsigned char *tx_buffer; + unsigned char *rx_buffer; + unsigned char *cmd_buffer; + unsigned char *int_buffer; + + /* Prevent races within handlers */ + spinlock_t lock; + + /* time in msec to wait for the urb to the complete */ + long timeout; + + /* Bulk pipe maximum packet for each transaction */ + int maxp; +}; + +/* + * struct nct6694_handler_entry - Stores the interrupt handling information + * for each registered peripheral + * + * @irq_bit: The bit in irq_status[INT_IN_IRQ_IDX] representing interrupt + * @handler: Function pointer to the interrupt handler of the peripheral + * @private_data: Private data specific to the peripheral driver + * @list: Node used to link to the handler_list + */ +struct nct6694_handler_entry { + int irq_bit; + void (*handler)(void *private_data); + void *private_data; + struct list_head list; +}; + +/* + * nct6694_register_handler - Register a handler with private data for + * interrupt pipe irq event + * + * @nct6694 - Nuvoton NCT6694 structure + * @irq_bit - The irq for which to register a handler + * @handler - The handler function + * @private_data - Private data for which to register a handler + * + * This function is called when peripherals need to register a handler + * for receiving interrupt pipe. + * + * Don't use the wait_for_completion function in handler function, as + * it is in interrupt context. + */ +int nct6694_register_handler(struct nct6694 *nct6694, int irq_bit, + void (*handler)(void *), + void *private_data); + +/* + * nct6694_read_msg - Receive data from NCT6694 USB device + * + * @nct6694 - Nuvoton NCT6694 structure + * @mod - Module byte + * @offset - Offset byte or (Select byte | Command byte) + * @length - Length byte + * @rd_idx - Read data from rx buffer at index + * @rd_len - Read length from rx buffer + * @buf - Read data from rx buffer + * + * USB Transaction format: + * + * OUT |RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H| + * OUT |SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H| + * IN |-------D------A------D------A-------| + * IN ...... + * IN |-------D------A------D------A-------| + */ +int nct6694_read_msg(struct nct6694 *nct6694, u8 mod, u16 offset, + u16 length, u8 rd_idx, u8 rd_len, + unsigned char *buf); + +/* + * nct6694_read_msg - Transmit data to NCT6694 USB device + * + * @nct6694 - Nuvoton NCT6694 structure + * @mod - Module byte + * @offset - Offset byte or (Select byte | Command byte) + * @length - Length byte + * @buf - Write data to tx buffer + * + * USB Transaction format: + * + * OUT |RSV|MOD|CMD|SEL|HCTL|RSV|LEN_L|LEN_H| + * OUT |-------D------A------D------A-------| + * OUT ...... + * OUT |-------D------A------D------A-------| + * IN |SEQ|STS|RSV|RSV|RSV|RSV||LEN_L|LEN_H| + * IN |-------D------A------D------A-------| + * IN ...... + * IN |-------D------A------D------A-------| + */ +int nct6694_write_msg(struct nct6694 *nct6694, u8 mod, u16 offset, + u16 length, unsigned char *buf); + +#endif From patchwork Thu Oct 24 08:59:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848558 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (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 D25E11B6CEB; Thu, 24 Oct 2024 08:59:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760397; cv=none; b=P80qR9tco6gXvpk/bU0NN/I8VQV7egVkgMl7C0K068nYM2G7miWQArwdY7wLJyoWCd+HDTBplw5CsmM0U1zi1psD8DKca88++Xlw1W4boW/DDyZs95XuyXbDZNTW1T2kTkL0OiTeQKGass+9Asxv8ONXYdoVY09Vz80z+LRsaOo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760397; c=relaxed/simple; bh=g1kUzY41TajXRq9Eu1+zOAOjqHoRGtzKHARbRX7ZUro=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pmlccz+HEHHz+Rpk8xHIZk+OkXct23pEszPaaEQAiOjWN2Kl1somcfG71HhDC1WF5a4LRQRZZhHjczx5jyezYwlnsL2yjbaBhXofQwU/Q5hxNT+ISZiJtQGTCHLasZTTVZo/FiLPSt7RCkeLpVjQ5NF7N2T7o935BmDkGITOcc4= 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=SjC5c1GW; arc=none smtp.client-ip=209.85.214.182 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="SjC5c1GW" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-20cb89a4e4cso4331495ad.3; Thu, 24 Oct 2024 01:59:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760394; x=1730365194; 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=0ACqx5Cut3267Yh5BbDV0H19Ly+tNCZIFEFe/P03oXA=; b=SjC5c1GWHQnoyOF57TsSelp2V8w/CG4NvqAPISYjBlpGX+IYEYQgE+yc+aGobMtlAz NXK4X/TSnTEZ4pEZvPZkkduyd0Ql7qZ4KxxLc3kaHFpJ9nmx/exYX1kpqqdkXI50qof3 3AkoSjPu9OJkC6X4Ax06xHhRKOjswGrnBTt/jqvcQIh+mXAcLGQJ2h9ulvkYTPxATNtx rSSUIxpklC17FJ2mHikIg/99BzHub7Id+MLk4jmGsyP5JLvABuF7TTEtDOtntMBzVKyZ ZzszzvEqn/t9x0IQM14IelirNdgJeqSqVj7tN6VE3uWhmsPIa6hXT5hy937t0EcLsDdb qWDw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760394; x=1730365194; 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=0ACqx5Cut3267Yh5BbDV0H19Ly+tNCZIFEFe/P03oXA=; b=C3AgWGHVHbYRi1a3QbKd2ZdvXZTpvHaFOqEXDWYgruIkjrhIkhLZm47GfV8BbxdaQK mvrRZjq9KBZZA0d1PjW/bYdd9j0bJeD2EOUea0HyXDHVTX4TGpZ7jYaWUEocYUzT7CJk 94Hy7lueLB1CNQ73QuBo8QRnlGyIX3S19hjtGbB88/68SCEo+6SYNboLM2AI6lWINZgj PiWWgOrtAgzyrg7GnyAOtP+Ldtae+2rv4OJjajHTIO/qj4jl8IaK7Y2uh2RNR1GGSugQ 48seSPAx3kxJoYYhMtl3H99m+VWV0EH0MHqTGmQ107qSTrkxbJkA/eYn86pO9t3NQaOV nODA== X-Forwarded-Encrypted: i=1; AJvYcCU6C1Gz5PnZEEAKyUN/DDuPkbFqyW7mDW/S/odha6LPCVhgNQiCRiyRbOdjWTkCWyYVxwRXDmYUs/LXNqU=@vger.kernel.org, AJvYcCUxyF77XlnR97ryvVYCvOPXWuBsiSnGumu04M95tplXpHnTyJ1AYAGDBaFOXgvIusF6IFy8tX9b6NTu4g==@vger.kernel.org, AJvYcCVHs1eFBBMDUdMLajtLcOENI2aTcAsC2n2Qoz1t4BtpCoRAa9qs6dcnaaILXYhbUCPu3ZOxbdxRreYi@vger.kernel.org, AJvYcCVNV+KcFRsauTklIFPB+0/BMyRmKUz8n6J8qWT4NKn8zf4jsnlOBtrQ/FYNjWomapDXTv+CxPqrgSWTfo3gCLA=@vger.kernel.org, AJvYcCW/FhqRwEymMtWNaggl1QTJELrTbscuMZtGEsQKR5U8t+nargE2UF8a9OlTvBoFozghbGb8/ePJMgg=@vger.kernel.org, AJvYcCXFAl8WYq0BK5YqMjd5Zzo/xT3V+g0KVhceSZ4O+GdQ8DyTPRsSO+vCMNTTImy+HiUNhOvFQEsR@vger.kernel.org, AJvYcCXX8ZeQsNCGrJNzNdOJ8bZb4vLc/Y8ZLdyY8acFpfk5nTHiey8lUl8tk8ChFfZtaWfsmRiw1A58v2lt@vger.kernel.org, AJvYcCXpdEfADfuu1o4tgNz1GIJ78qmbXQHLLUJ//wRibF+4GjouFiQ9fsgp7uVbXmCwUqjfLg0eglfAzwHQ@vger.kernel.org, AJvYcCXr0X2C1J2LBRrZ44hjmZKT9FheEuq+oUYRBVqCyxcNXaZRoo93MTO0KYmk6W8gNqetAL0UjGa/MJwX@vger.kernel.org X-Gm-Message-State: AOJu0YxCzhoyK/HYR4JLgHtA/u+pyh10aVg+MxpU6aThhNZ/OLLPF4NG qs3fW1IPUQjG8egguqWEwwTyjZ7qQAe3KuHVTIBGfPaT6I4nbZa6 X-Google-Smtp-Source: AGHT+IFraDFjzyYNyFfHxb72p9lXiL6KT8JkIXYNRRUOfw3EvWsWkAonWEf9Wk40n5OtmfqlmbZDyA== X-Received: by 2002:a17:903:41d2:b0:20c:f648:e3a3 with SMTP id d9443c01a7336-20fb9b3d6abmr10371555ad.60.1729760394025; Thu, 24 Oct 2024 01:59:54 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.01.59.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 01:59:53 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 2/9] gpio: Add Nuvoton NCT6694 GPIO support Date: Thu, 24 Oct 2024 16:59:15 +0800 Message-Id: <20241024085922.133071-3-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports GPIO and IRQ functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 12 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-nct6694.c | 489 ++++++++++++++++++++++++++++++++++++ 4 files changed, 503 insertions(+) create mode 100644 drivers/gpio/gpio-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index 30157ca95cf3..2c86d5dab3f1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16438,6 +16438,7 @@ NUVOTON NCT6694 MFD DRIVER M: Ming Yu L: linux-kernel@vger.kernel.org S: Supported +F: drivers/gpio/gpio-nct6694.c F: drivers/mfd/nct6694.c F: include/linux/mfd/nct6694.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d93cd4f722b4..aa78ad9ff4ac 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1450,6 +1450,18 @@ config GPIO_MAX77650 GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. These chips have a single pin that can be configured as GPIO. +config GPIO_NCT6694 + tristate "Nuvoton NCT6694 GPIO controller support" + depends on MFD_NCT6694 + select GENERIC_IRQ_CHIP + select GPIOLIB_IRQCHIP + help + This driver supports 8 GPIO pins per bank that can all be interrupt + sources. + + This driver can also be built as a module. If so, the module will be + called gpio-nct6694. + config GPIO_PALMAS bool "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 1429e8c0229b..02c94aa28017 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -121,6 +121,7 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o +obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c new file mode 100644 index 000000000000..42c0e6e76730 --- /dev/null +++ b/drivers/gpio/gpio-nct6694.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 GPIO controller driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-gpio" + +/* Host interface */ +#define REQUEST_GPIO_MOD 0xFF +#define REQUEST_GPIO_LEN 0x01 + +/* Report Channel */ +#define GPIO_VER_REG 0x90 +#define GPIO_VALID_REG 0x110 +#define GPI_DATA_REG 0x120 +#define GPO_DIR_REG 0x170 +#define GPO_TYPE_REG 0x180 +#define GPO_DATA_REG 0x190 + +#define GPI_STS_REG 0x130 +#define GPI_CLR_REG 0x140 +#define GPI_FALLING_REG 0x150 +#define GPI_RISING_REG 0x160 + +struct nct6694_gpio_data { + struct nct6694 *nct6694; + struct gpio_chip gpio; + struct work_struct irq_work; + struct work_struct irq_trig_work; + + /* Protect irq operation */ + struct mutex irq_lock; + + unsigned char irq_trig_falling; + unsigned char irq_trig_rising; + + /* Current gpio group */ + unsigned char group; +}; + +static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + return !(BIT(offset) & buf); +} + +static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + buf &= ~(1 << offset); + + ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, &buf); + + return ret; +} + +static int nct6694_direction_output(struct gpio_chip *gpio, + unsigned int offset, int val) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + /* Set direction to output */ + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + buf |= (1 << offset); + ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, &buf); + if (ret < 0) + return ret; + + /* Then set output level */ + ret = nct6694_read_msg(data->nct6694, 0xFF, + GPO_DATA_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + if (val) + buf |= (1 << offset); + else + buf &= ~(1 << offset); + + ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DATA_REG + data->group, + REQUEST_GPIO_LEN, &buf); + + return ret; +} + +static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DIR_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + if (BIT(offset) & buf) { + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DATA_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + return !!(BIT(offset) & buf); + } + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_DATA_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + return !!(BIT(offset) & buf); +} + +static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset, + int val) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + + nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DATA_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + + if (val) + buf |= (1 << offset); + else + buf &= ~(1 << offset); + + nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_DATA_REG + data->group, + REQUEST_GPIO_LEN, &buf); +} + +static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset, + unsigned long config) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_TYPE_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + buf |= (1 << offset); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + buf &= ~(1 << offset); + break; + default: + return -EOPNOTSUPP; + } + + ret = nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPO_TYPE_REG + data->group, + REQUEST_GPIO_LEN, &buf); + + return ret; +} + +static int nct6694_init_valid_mask(struct gpio_chip *gpio, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + unsigned char buf; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPIO_VALID_REG + data->group, + REQUEST_GPIO_LEN, 0, 1, &buf); + if (ret < 0) + return ret; + + *valid_mask = buf; + + return ret; +} + +static void nct6694_irq(struct work_struct *irq_work) +{ + struct nct6694_gpio_data *data; + unsigned char status; + + data = container_of(irq_work, struct nct6694_gpio_data, irq_work); + + nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_STS_REG + data->group, 1, + 0, 1, &status); + + while (status) { + int bit = __ffs(status); + unsigned char val = BIT(bit); + + handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit)); + status &= ~(1 << bit); + nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_CLR_REG + data->group, + REQUEST_GPIO_LEN, &val); + } +} + +static void nct6694_gpio_handler(void *private_data) +{ + struct nct6694_gpio_data *data = private_data; + struct nct6694 *nct6694 = data->nct6694; + + queue_work(nct6694->async_workqueue, &data->irq_work); +} + +static int nct6694_get_irq_trig(struct nct6694_gpio_data *data) +{ + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_FALLING_REG + data->group, + 1, 0, 1, &data->irq_trig_falling); + if (ret) + return ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_RISING_REG + data->group, + 1, 0, 1, &data->irq_trig_rising); + + return ret; +} + +static void nct6694_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_disable_irq(gpio, hwirq); +} + +static void nct6694_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(gpio, hwirq); +} + +static void nct6694_irq_trig(struct work_struct *irq_trig_work) +{ + struct nct6694_gpio_data *data; + + data = container_of(irq_trig_work, struct nct6694_gpio_data, + irq_trig_work); + + nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_FALLING_REG + data->group, + 1, &data->irq_trig_falling); + + nct6694_write_msg(data->nct6694, REQUEST_GPIO_MOD, + GPI_RISING_REG + data->group, + 1, &data->irq_trig_rising); +} + +static int nct6694_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + struct nct6694 *nct6694 = data->nct6694; + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + data->irq_trig_rising |= BIT(hwirq); + break; + + case IRQ_TYPE_EDGE_FALLING: + data->irq_trig_falling |= BIT(hwirq); + break; + + case IRQ_TYPE_EDGE_BOTH: + data->irq_trig_rising |= BIT(hwirq); + data->irq_trig_falling |= BIT(hwirq); + break; + + default: + return -EOPNOTSUPP; + } + + queue_work(nct6694->async_workqueue, &data->irq_trig_work); + + return 0; +} + +static void nct6694_irq_bus_lock(struct irq_data *d) +{ + struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + + mutex_lock(&data->irq_lock); +} + +static void nct6694_irq_bus_sync_unlock(struct irq_data *d) +{ + struct gpio_chip *gpio = irq_data_get_irq_chip_data(d); + struct nct6694_gpio_data *data = gpiochip_get_data(gpio); + + mutex_unlock(&data->irq_lock); +} + +static const struct irq_chip nct6694_irq_chip = { + .name = "nct6694", + .irq_mask = nct6694_irq_mask, + .irq_unmask = nct6694_irq_unmask, + .irq_set_type = nct6694_irq_set_type, + .irq_bus_lock = nct6694_irq_bus_lock, + .irq_bus_sync_unlock = nct6694_irq_bus_sync_unlock, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static const char * const nct6694_gpio_name[] = { + "NCT6694-GPIO0", + "NCT6694-GPIO1", + "NCT6694-GPIO2", + "NCT6694-GPIO3", + "NCT6694-GPIO4", + "NCT6694-GPIO5", + "NCT6694-GPIO6", + "NCT6694-GPIO7", + "NCT6694-GPIO8", + "NCT6694-GPIO9", + "NCT6694-GPIOA", + "NCT6694-GPIOB", + "NCT6694-GPIOC", + "NCT6694-GPIOD", + "NCT6694-GPIOE", + "NCT6694-GPIOF", +}; + +static int nct6694_gpio_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct nct6694_gpio_data *data; + struct gpio_irq_chip *girq; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nct6694 = nct6694; + data->group = cell->id; + + data->gpio.label = nct6694_gpio_name[cell->id]; + data->gpio.direction_input = nct6694_direction_input; + data->gpio.get = nct6694_get_value; + data->gpio.direction_output = nct6694_direction_output; + data->gpio.set = nct6694_set_value; + data->gpio.get_direction = nct6694_get_direction; + data->gpio.set_config = nct6694_set_config; + data->gpio.init_valid_mask = nct6694_init_valid_mask; + data->gpio.base = -1; + data->gpio.can_sleep = false; + data->gpio.owner = THIS_MODULE; + data->gpio.ngpio = 8; + + INIT_WORK(&data->irq_work, nct6694_irq); + INIT_WORK(&data->irq_trig_work, nct6694_irq_trig); + mutex_init(&data->irq_lock); + + ret = nct6694_register_handler(nct6694, GPIO_IRQ_STATUS, + nct6694_gpio_handler, data); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n", + __func__, ERR_PTR(ret)); + return ret; + } + + platform_set_drvdata(pdev, data); + + ret = nct6694_get_irq_trig(data); + if (ret) + return ret; + + /* Register gpio chip to GPIO framework */ + girq = &data->gpio.irq; + gpio_irq_chip_set_chip(girq, &nct6694_irq_chip); + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_level_irq; + girq->threaded = true; + + ret = gpiochip_add_data(&data->gpio, data); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register GPIO chip: %pe", + __func__, ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static void nct6694_gpio_remove(struct platform_device *pdev) +{ + struct nct6694_gpio_data *data = platform_get_drvdata(pdev); + + gpiochip_remove(&data->gpio); + cancel_work(&data->irq_work); + cancel_work(&data->irq_trig_work); +} + +static struct platform_driver nct6694_gpio_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_gpio_probe, + .remove = nct6694_gpio_remove, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_gpio_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_gpio_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_gpio_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848559 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 6B0901BD4FD; Thu, 24 Oct 2024 08:59:59 +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=1729760401; cv=none; b=phJZemTbj0BR7i1k4UzcwFV8v0Q8lUZoKGdHHx3eqfccD6DjQmne+7W0FSE/MStvt7tGhiCvlC1RtpP9c5KwQ+QiM4RZGfB/Ye2eyUVgw8MtDmyU4Q5nb2tJxbCgQdtajRROK42oRz/PnhuZI8spV5oqis718Tj/7St0M0jN91g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760401; c=relaxed/simple; bh=WQUkdkll0EZQjEkCZ8F8X+Jv3ok5OWWObt4FU2xYsp8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=QDFvXkPZs0XW1ZpV0noMPY2yLjpxSLul4zA8XJvlzRVgcLR3hvjNUwlS/7DFwVHJU/WL35ih5bpTYLGGxuoXxfnq0uCXLjOMpP2M4GYxycoELySAUQfsgRB+BHa50BDiFRFer++423l0SU6b4je80/uvf9CLTYpD4XfeYMR+eY8= 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=LFXQJU7e; 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="LFXQJU7e" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-20e576dbc42so5929835ad.0; Thu, 24 Oct 2024 01:59:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760399; x=1730365199; 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=ZweD2U9xX/45ajeNahBwZRNjF317AbysfEfeb0LtmmE=; b=LFXQJU7eyovvB+KXfE5sBf1C0BrJvc3SU3yK7jf+jdprsH/n0DVFqrTBCA+6bNNrFW a7Dw1yrhxnPZ5RDUECcYgCZqFj6b+o/SLJBkfbpE/goY6VMGRl48iMyL8PLKjFN1cX50 zPyZFFCasHSffhV91yutjCDLhEMjcUS7gGq67eWy9Rh/PrwAUG+i0n25A2lth5E1kISd nAbK6vkSWAKl34HHxn3/OBBLtayQvcaIMKK6T2x4QfcL+93pkMs1jSoB70Vu5HcWyE6u H8jkitzVf+ZKK/6qhTUfGM65qfGwX1UmEzWdXE3rUAKqphybWP8Cux6MjWmmSgJv0igg ggXQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760399; x=1730365199; 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=ZweD2U9xX/45ajeNahBwZRNjF317AbysfEfeb0LtmmE=; b=cv2UX+w83+/UIsC44kPnWdKdJqckhfLoaWwPMlPCSFpY9nhiYfWloAmr1wG75h2M6f cBGUatBqeS9HoW0TJpH0RpXul9EXvtSi0QDtYnHtp08z9vo8J+ghfaoNTDwRyRtFphyu PxPi3+tBOFMSiVTYuzAMKHtOWxYKYyrkgCinIYjgOmjLvMuG0l/JJZ86z090GZCQmbwU kdiqf3KBoo4aZAeD3wB9XjNdLtK8mXHQbd/4F73M8qn4JeY7bO9r3Sj+l8xYYF69o5Hw UFkxsXabJvGXcXsAfFXoIp67wPVGi9kzYZa2r54mU5JpQ1sirMP8+OtFvshBVfM6anL6 bpjA== X-Forwarded-Encrypted: i=1; AJvYcCUHPzJpWSOapsclRaVB3J5BoWVPqhYDRnjamJGLThZ5fO9eBdi+XpOBLvU1i7Bsvs8tS7VIDASs6u0t@vger.kernel.org, AJvYcCULh/CY4mLtC+/pqvoIX8dLPpcvccmQteygzUm/F6KeoHHDyzf1dyDoSG/FQWY0QiQ8LH1caeFnZ0zI@vger.kernel.org, AJvYcCUfgHn2VC+XJ+4CUlgW6jmr0CBkly4idwBeXfAOsQiK6LroH4yzmXuFsN96J26YUGwQ/pvCtlcq@vger.kernel.org, AJvYcCUgnZu75r2mFwKErFFutLLqcM2BlddC7bGJEHjoE0iOlAfGbypT3g8A13JHHtmrl48K+4JD7GeCwaV35UNhufQ=@vger.kernel.org, AJvYcCUhQnyUPFubXbjTTfZckvQdAzpb6yFdW3Ov81qIDJLs+U+0O9kpl2qGlrCL0MFuSv26FlnFHzBzE9WmDsE=@vger.kernel.org, AJvYcCUiQGAkL4AsxrWG74dCB31WxlnIJPPtsL2yjOtY+QllMUe4nvyx35f0b7l3L2EXiVwgeIaFeVkPWBE=@vger.kernel.org, AJvYcCVigO1w4WGhtZA/LAoW0gUqRRJqXyf3W/t3mCafH32XI4Byw2GLObb3N4dXQnJtSAjPCm2mH3UlrRus@vger.kernel.org, AJvYcCVpv1q+PHNMuz7zCaeLuAe69Xp6MuUnj6Vby9muzDAMUQmVkTmcW6jCP4pplxDdqeuiRMAHaRDe+s/P5g==@vger.kernel.org, AJvYcCWfM5uEA/mgwwTfY8fjdjyqsIkd/5EEujLpfMi/z4BTvxaZJW28hTBsW1AIGcA0JPZ7V5oGC0qtgTLa@vger.kernel.org X-Gm-Message-State: AOJu0YwObmeZailiaquVWwu8ZkPEODqUvLd2Q1aWDe92xsAY1TKQGWaK boHqKfS24o0LdmBgyGy+Ujxm3kOVTAKZ594Y+fXPN/HyVlgJB1ui X-Google-Smtp-Source: AGHT+IGS0YUVIrQJrOqKX4snrMyqjbJuOa1vJLnjp+UhWn8DcszHIP8Vj9VWGI9HdvQXxoSYfHONcw== X-Received: by 2002:a17:903:245:b0:20b:c287:202d with SMTP id d9443c01a7336-20fab2dfecbmr59088725ad.55.1729760398650; Thu, 24 Oct 2024 01:59:58 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.01.59.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 01:59:58 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 3/9] i2c: Add Nuvoton NCT6694 I2C support Date: Thu, 24 Oct 2024 16:59:16 +0800 Message-Id: <20241024085922.133071-4-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports I2C adapter functionality for NCT6694 MFD device based on USB interface, each I2C controller use default baudrate(100K). Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/i2c/busses/Kconfig | 10 ++ drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-nct6694.c | 166 +++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 drivers/i2c/busses/i2c-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index 2c86d5dab3f1..1cc64f9f154a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16439,6 +16439,7 @@ M: Ming Yu L: linux-kernel@vger.kernel.org S: Supported F: drivers/gpio/gpio-nct6694.c +F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: include/linux/mfd/nct6694.h diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 6b3ba7e5723a..01a60de4b8a4 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1315,6 +1315,16 @@ config I2C_LJCA This driver can also be built as a module. If so, the module will be called i2c-ljca. +config I2C_NCT6694 + tristate "Nuvoton NCT6694 I2C adapter support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB to I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-nct6604. + config I2C_CP2615 tristate "Silicon Labs CP2615 USB sound card and I2C adapter" depends on USB diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ecc07c50f2a0..3c4a0ea5a46f 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o +obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c new file mode 100644 index 000000000000..b33d90f26f9f --- /dev/null +++ b/drivers/i2c/busses/i2c-nct6694.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 I2C adapter driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include + +/* Host interface */ +#define REQUEST_I2C_MOD 0x03 + +/* Message Channel*/ +/* Command 00h */ +#define REQUEST_I2C_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define REQUEST_I2C_LEN 0x90 +#define I2C_PORT_IDX 0x00 +#define I2C_BR_IDX 0x01 +#define I2C_ADDR_IDX 0x02 +#define I2C_W_CNT_IDX 0x03 +#define I2C_R_CNT_IDX 0x04 + +#define I2C_RD_IDX 0x50 +#define I2C_WR_IDX 0x10 + +#define DRVNAME "nct6694-i2c" + +enum i2c_baudrate { + I2C_BR_25K = 0, + I2C_BR_50K, + I2C_BR_100K, + I2C_BR_200K, + I2C_BR_400K, + I2C_BR_800K, + I2C_BR_1M +}; + +struct nct6694_i2c_data { + struct nct6694 *nct6694; + struct i2c_adapter adapter; + unsigned char port; + unsigned char br; +}; + +static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct nct6694_i2c_data *data = adap->algo_data; + int ret, i; + + for (i = 0; i < num ; i++) { + unsigned char buf[REQUEST_I2C_LEN] = {0}; + struct i2c_msg *msg_temp = &msgs[i]; + + if (msg_temp->len > 64) + return -EPROTO; + + buf[I2C_PORT_IDX] = data->port; + buf[I2C_BR_IDX] = data->br; + buf[I2C_ADDR_IDX] = i2c_8bit_addr_from_msg(msg_temp); + if (msg_temp->flags & I2C_M_RD) { + buf[I2C_R_CNT_IDX] = msg_temp->len; + ret = nct6694_write_msg(data->nct6694, REQUEST_I2C_MOD, + REQUEST_I2C_OFFSET, REQUEST_I2C_LEN, + buf); + if (ret < 0) + return 0; + memcpy(msg_temp->buf, buf + I2C_RD_IDX, msg_temp->len); + } else { + buf[I2C_W_CNT_IDX] = msg_temp->len; + memcpy(buf + I2C_WR_IDX, msg_temp->buf, msg_temp->len); + ret = nct6694_write_msg(data->nct6694, REQUEST_I2C_MOD, + REQUEST_I2C_OFFSET, REQUEST_I2C_LEN, + buf); + if (ret < 0) + return 0; + } + } + + return num; +} + +static u32 nct6694_func(struct i2c_adapter *adapter) +{ + return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL); +} + +static const struct i2c_algorithm algorithm = { + .master_xfer = nct6694_xfer, + .functionality = nct6694_func, +}; + +static int nct6694_i2c_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct nct6694_i2c_data *data; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nct6694 = nct6694; + data->port = cell->id; + data->br = I2C_BR_100K; + + sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", cell->id); + data->adapter.owner = THIS_MODULE; + data->adapter.algo = &algorithm; + data->adapter.dev.parent = &pdev->dev; + data->adapter.algo_data = data; + + platform_set_drvdata(pdev, data); + + ret = i2c_add_adapter(&data->adapter); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register I2C Adapter: %pe\n", + __func__, ERR_PTR(ret)); + } + + return ret; +} + +static void nct6694_i2c_remove(struct platform_device *pdev) +{ + struct nct6694_i2c_data *data = platform_get_drvdata(pdev); + + i2c_del_adapter(&data->adapter); +} + +static struct platform_driver nct6694_i2c_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_i2c_probe, + .remove = nct6694_i2c_remove, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_i2c_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_i2c_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_i2c_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848560 Received: from mail-pl1-f177.google.com (mail-pl1-f177.google.com [209.85.214.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 5E8031C07FE; Thu, 24 Oct 2024 09:00:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760408; cv=none; b=JR0DhekVl4nyJ1FKXLdysZhezC8sjiR+j2RnBi7ptOUGyeJCY7bdu1MZQSeofsc8anzixJ6fiVBWUYK78oTIomemzfuprAtmPyYyxI7HspmFfjAaI8/ZLvLeVEWLxkXL3SbcAxO9UqiYe37NQax5XyvCWT1kIpIO6qiHfI3/mKA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760408; c=relaxed/simple; bh=iQ3K8EZrrfqkyjrAlgOLbILB4piINUkxIykDeuJYq1o=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=no9VhQS4XEpl/iIh/V/xdOm1yC0EmBguH4FRJwyHXAZ5org5JdYwgDcUzrQc9aR29A4mB0PvJB1f4o9HmVj/8+sRZqTJw2h9yzoKEub1BVWwkXoRMO1KC+tahX8QGxbsjz7wMqpkkIFJPWGmpBzn/DaS/EaAiI+4F19++ge0osg= 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=Km3Z1Mz/; arc=none smtp.client-ip=209.85.214.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="Km3Z1Mz/" Received: by mail-pl1-f177.google.com with SMTP id d9443c01a7336-20caea61132so4604725ad.2; Thu, 24 Oct 2024 02:00:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760403; x=1730365203; 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=mdyMyahtFVsEp79sMHjJu3KLMnbAklyizcUbpHuuqUE=; b=Km3Z1Mz/70Fd5579iIXGwC3MtKYaP3hIM2HnTw5begDlMBXdf48h/7SHYIaa+BX1OO /cNEaed1tC3CADO2bCAo3FKnYgF/oG5qs72JyKRVkcQFUYi7P6yZM8fzh1cUl/6mVWMc mg9GHValsB0meQ/oud0Aq9rm+LgHeQGa3wNapctBOra2w3fUJcNYBTZDqkQFm4GiDqS9 SSH2WeCTohdOYeYuvmwYQ22yr/9P7M29Zr2qOwsX5/xaPxMrlqjzuDD62Clhe7He6qTD cie2OQQj5vEGfl3oEuqOT3W5U4dcxz4H5fJZjDEmXsMnO5rPZr7mirhFFjTN/mpZwu56 m1qg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760403; x=1730365203; 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=mdyMyahtFVsEp79sMHjJu3KLMnbAklyizcUbpHuuqUE=; b=FriZT2EKimtquCD5zyFNg7AWWHkBrwMLLK1B/jtK1NuRf8mBazgkeiXJpqw4tJ4umr tg3hElsnBA1a+cvQ1RqcV2+4o7KFLix0aBUjaB7wMNXRbtXGgqb6PoCHR5caK2A8fNZQ 45wVTeQnmIJ1ovcw+ry72po8tt+OwXCxRWJKhrvrGgnmzxksTSPve0OlnZ7YhhxNHiM5 HsyYN1tvMgUNyZBb4/tcLeqGgmt9vAt1ttDX/B9sTG2IWZa8ZiKm26KssmdBSrITcEG2 JxLxLfTNKxQp8uBm+BTY2cbajWcCki0K+EbTp7TKEj2r3EBAyszqI+K3ZjK0ArfT5znp dVDw== X-Forwarded-Encrypted: i=1; AJvYcCU3rkKQk+twzJIE4UPk95Dy4szKWpv6SUeyMDd5lhKVyXdbOO0jmwfZRBRTDj3S+qHuchEa1BR9A5AdmivdL88=@vger.kernel.org, AJvYcCU4VssL0FVerdbVaxdKEQqlHrid8jwV+GtUlARk6xZBFpQxZ0+2Y0GLRN79EGCMumKphrg09ZuQtOYQErI=@vger.kernel.org, AJvYcCUjqSa5P/8kqQhge9AJF6XOnZKikkD69kkthHgebky/UQTEheZAw2rx1LhycmJL3ZXk5F/rL+wBJIyE@vger.kernel.org, AJvYcCVnUaGGfuxzG3TvzDNQf+CCmpfE63Tyw9aDrYlbdQu3hlkxfISGfE7TxL36EO/eBjx1bVMvm9fKnA6F@vger.kernel.org, AJvYcCW0KoN5Ab/757rFVmQlCptX0mNPrxPE8FoXCmD5ZcOiPXWsVS/n56TT/H7mTWoz/Ue2cSiZsr5wwkmE@vger.kernel.org, AJvYcCWWwT8Q3q6cdRqwReLkqh1hsEqBqvraMkCkFA9IeqiDpzLfGEqJNmoouD+885NYFeLOPnR8QmRM@vger.kernel.org, AJvYcCWm7nD8rSBDW36XBIF1HV8EaKWqZucHr6d2pU9KJvV6b8otIwcFQJf5AtOuDBE0vXs3xObzoSafLJKl@vger.kernel.org, AJvYcCWskLXZaMgncBcdrv8TyvuZj26RgkNt2I/1eJ897BhucISVW/1mwFUfmZ+UEmku2YYyKcrACQ8kWGo=@vger.kernel.org, AJvYcCXNVOJzd4Hhv5Frnfpc6B9aKBVx53n6n3aoekbJQdrAEzYDuOc9dvhmIf4mVTGXyfrmXNeMkOm3wGNHag==@vger.kernel.org X-Gm-Message-State: AOJu0YzDkRli8G/e4On59Ru8LSvTwQWk3W3nA3HoVfyJtUTKkW9gx61z UUR2g5Z4bB1FhV3JsXFhno6ePCWuiKiVUaM61l/KBkka4z4jqCcx X-Google-Smtp-Source: AGHT+IFooLjqdIJUXBLi3SvU+5PMETQIkGyWuyR6xOZFGX7I4F1qD0PxuoOftdfwxv9mONkWZfxFsw== X-Received: by 2002:a17:902:e54a:b0:20c:8907:902 with SMTP id d9443c01a7336-20fa9eb5a9bmr68529045ad.49.1729760403312; Thu, 24 Oct 2024 02:00:03 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.01.59.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:03 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 4/9] can: Add Nuvoton NCT6694 CAN support Date: Thu, 24 Oct 2024 16:59:17 +0800 Message-Id: <20241024085922.133071-5-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports Socket CANfd functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/net/can/Kconfig | 10 + drivers/net/can/Makefile | 1 + drivers/net/can/nct6694_canfd.c | 843 ++++++++++++++++++++++++++++++++ 4 files changed, 855 insertions(+) create mode 100644 drivers/net/can/nct6694_canfd.c diff --git a/MAINTAINERS b/MAINTAINERS index 1cc64f9f154a..eccd5e795daa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16441,6 +16441,7 @@ S: Supported F: drivers/gpio/gpio-nct6694.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c +F: drivers/net/can/nct6694_canfd.c F: include/linux/mfd/nct6694.h NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index cf989bea9aa3..569feda37731 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -200,6 +200,16 @@ config CAN_SUN4I To compile this driver as a module, choose M here: the module will be called sun4i_can. +config CAN_NCT6694 + tristate "Nuvoton NCT6694 Socket CANfd support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to socket CANfd controller. + + This driver can also be built as a module. If so, the module + will be called nct6694_canfd. + config CAN_TI_HECC depends on ARM tristate "TI High End CAN Controller" diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index a71db2cfe990..825c011aead5 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd.o obj-$(CONFIG_CAN_MSCAN) += mscan/ obj-$(CONFIG_CAN_M_CAN) += m_can/ +obj-$(CONFIG_CAN_NCT6694) += nct6604_canfd.o obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/ obj-$(CONFIG_CAN_SJA1000) += sja1000/ obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o diff --git a/drivers/net/can/nct6694_canfd.c b/drivers/net/can/nct6694_canfd.c new file mode 100644 index 000000000000..d873a9fd656d --- /dev/null +++ b/drivers/net/can/nct6694_canfd.c @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 Socket CANfd driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-can" + +#define RX_QUATA 64 + +/* Host interface */ +#define REQUEST_CAN_MOD 0x05 + +/* Message Channel*/ +/* Command 00h */ +#define REQUEST_CAN_CMD0_LEN 0x18 +#define REQUEST_CAN_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) +#define CAN_NBR_IDX 0x00 +#define CAN_DBR_IDX 0x04 +#define CAN_CTRL1_IDX 0x0C +#define CAN_CTRL1_MON BIT(0) +#define CAN_CTRL1_NISO BIT(1) +#define CAN_CTRL1_LBCK BIT(2) + +/* Command 01h */ +#define REQUEST_CAN_CMD1_LEN 0x08 +#define REQUEST_CAN_CMD1_OFFSET 0x0001 +#define CAN_CLK_IDX 0x04 +#define CAN_CLK_LEN 0x04 + +/* Command 02h */ +#define REQUEST_CAN_CMD2_LEN 0x10 +#define REQUEST_CAN_CMD2_OFFSET(idx, mask) \ + ({ typeof(mask) mask_ = (mask); \ + idx ? ((0x80 | (mask_ & 0xFF)) << 8 | 0x02) : \ + ((0x00 | (mask_ & 0xFF)) << 8 | 0x02); }) + +#define CAN_ERR_IDX(idx) (idx ? 0x08 : 0x00) /* Read-clear */ +#define CAN_STATUS_IDX(idx) (idx ? 0x09 : 0x01) +#define CAN_TX_EVT_IDX(idx) (idx ? 0x0A : 0x02) +#define CAN_RX_EVT_IDX(idx) (idx ? 0x0B : 0x03) +#define CAN_REC_IDX(idx) (idx ? 0x0C : 0x04) +#define CAN_TEC_IDX(idx) (idx ? 0x0D : 0x05) +#define CAN_EVENT_ERR BIT(0) +#define CAN_EVENT_STATUS BIT(1) +#define CAN_EVENT_TX_EVT BIT(2) +#define CAN_EVENT_RX_EVT BIT(3) +#define CAN_EVENT_REC BIT(4) +#define CAN_EVENT_TEC BIT(5) +#define CAN_EVENT_ERR_NO_ERROR 0x00 /* Read-clear */ +#define CAN_EVENT_ERR_CRC_ERROR 0x01 /* Read-clear */ +#define CAN_EVENT_ERR_STUFF_ERROR 0x02 /* Read-clear */ +#define CAN_EVENT_ERR_ACK_ERROR 0x03 /* Read-clear */ +#define CAN_EVENT_ERR_FORM_ERROR 0x04 /* Read-clear */ +#define CAN_EVENT_ERR_BIT_ERROR 0x05 /* Read-clear */ +#define CAN_EVENT_ERR_TIMEOUT_ERROR 0x06 /* Read-clear */ +#define CAN_EVENT_ERR_UNKNOWN_ERROR 0x07 /* Read-clear */ +#define CAN_EVENT_STATUS_ERROR_ACTIVE 0x00 +#define CAN_EVENT_STATUS_ERROR_PASSIVE 0x01 +#define CAN_EVENT_STATUS_BUS_OFF 0x02 +#define CAN_EVENT_STATUS_WARNING 0x03 +#define CAN_EVENT_TX_EVT_TX_FIFO_EMPTY BIT(7) /* Read-clear */ +#define CAN_EVENT_RX_EVT_DATA_LOST BIT(5) /* Read-clear */ +#define CAN_EVENT_RX_EVT_HALF_FULL BIT(6) /* Read-clear */ +#define CAN_EVENT_RX_EVT_DATA_IN BIT(7) + +/* Command 10h */ +#define REQUEST_CAN_CMD10_LEN 0x90 +#define REQUEST_CAN_CMD10_OFFSET(buf_cnt) \ + (((buf_cnt) & 0xFF) << 8 | 0x10) +#define CAN_TAG_IDX 0x00 +#define CAN_FLAG_IDX 0x01 +#define CAN_DLC_IDX 0x03 +#define CAN_ID_IDX 0x04 +#define CAN_DATA_IDX 0x08 +#define CAN_TAG_CAN0 0xC0 +#define CAN_TAG_CAN1 0xC1 +#define CAN_FLAG_EFF BIT(0) +#define CAN_FLAG_RTR BIT(1) +#define CAN_FLAG_FD BIT(2) +#define CAN_FLAG_BRS BIT(3) +#define CAN_FLAG_ERR BIT(4) + +/* Command 11h */ +#define REQUEST_CAN_CMD11_LEN 0x90 +#define REQUEST_CAN_CMD11_OFFSET(idx, buf_cnt) \ + ({ typeof(buf_cnt) buf_cnt_ = (buf_cnt); \ + idx ? ((0x80 | (buf_cnt_ & 0xFF)) << 8 | 0x11) : \ + ((0x00 | (buf_cnt_ & 0xFF)) << 8 | 0x11); }) + +struct nct6694_canfd_priv { + struct can_priv can; /* must be the first member */ + + struct nct6694 *nct6694; + struct net_device *ndev; + struct work_struct rx_work; + struct work_struct tx_work; + unsigned char data_buf[REQUEST_CAN_CMD10_LEN]; + unsigned char can_idx; +}; + +static inline void set_buf16(void *buf, u16 u16_val) +{ + u8 *p = (u8 *)buf; + + p[0] = u16_val & 0xFF; + p[1] = (u16_val >> 8) & 0xFF; +} + +static inline void set_buf32(void *buf, u32 u32_val) +{ + u8 *p = (u8 *)buf; + + p[0] = u32_val & 0xFF; + p[1] = (u32_val >> 8) & 0xFF; + p[2] = (u32_val >> 16) & 0xFF; + p[3] = (u32_val >> 24) & 0xFF; +} + +static const struct can_bittiming_const nct6694_canfd_bittiming_nominal_const = { + .name = DRVNAME, + .tseg1_min = 2, + .tseg1_max = 256, + .tseg2_min = 2, + .tseg2_max = 128, + .sjw_max = 128, + .brp_min = 1, + .brp_max = 511, + .brp_inc = 1, +}; + +static const struct can_bittiming_const nct6694_canfd_bittiming_data_const = { + .name = DRVNAME, + .tseg1_min = 1, + .tseg1_max = 32, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 16, + .brp_min = 1, + .brp_max = 31, + .brp_inc = 1, +}; + +static void nct6694_canfd_set_bittiming(struct net_device *ndev, + unsigned char *buf) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + const struct can_bittiming *n_bt = &priv->can.bittiming; + const struct can_bittiming *d_bt = &priv->can.data_bittiming; + + set_buf32(&buf[CAN_NBR_IDX], n_bt->bitrate); + set_buf32(&buf[CAN_DBR_IDX], d_bt->bitrate); + + pr_info("%s: can(%d): NBR = %d, DBR = %d\n", __func__, priv->can_idx, + n_bt->bitrate, d_bt->bitrate); +} + +static int nct6694_canfd_start(struct net_device *ndev) +{ + int ret; + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + unsigned char buf[REQUEST_CAN_CMD0_LEN] = {0}; + u16 temp = 0; + + nct6694_canfd_set_bittiming(ndev, buf); + + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + temp |= CAN_CTRL1_MON; + + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && + priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO) + temp |= CAN_CTRL1_NISO; + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + temp |= CAN_CTRL1_LBCK; + + set_buf16(&buf[CAN_CTRL1_IDX], temp); + + ret = nct6694_write_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD0_OFFSET(priv->can_idx), + REQUEST_CAN_CMD0_LEN, buf); + if (ret < 0) { + pr_err("%s: Failed to set data bittiming\n", __func__); + return ret; + } + + priv->can.state = CAN_STATE_ERROR_ACTIVE; + + return 0; +} + +static void nct6694_canfd_stop(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + + priv->can.state = CAN_STATE_STOPPED; +} + +static int nct6694_canfd_set_mode(struct net_device *ndev, enum can_mode mode) +{ + switch (mode) { + case CAN_MODE_START: + nct6694_canfd_start(ndev); + netif_wake_queue(ndev); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nct6694_canfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + unsigned char mask = CAN_EVENT_REC | CAN_EVENT_TEC; + unsigned char buf[REQUEST_CAN_CMD2_LEN] = {0}; + int ret; + + ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD2_OFFSET(priv->can_idx, mask), + REQUEST_CAN_CMD2_LEN, 0x00, + REQUEST_CAN_CMD2_LEN, (unsigned char *)&buf); + if (ret < 0) + return -EINVAL; + + bec->rxerr = buf[CAN_REC_IDX(priv->can_idx)]; + bec->txerr = buf[CAN_TEC_IDX(priv->can_idx)]; + + return 0; +} + +static int nct6694_canfd_open(struct net_device *ndev) +{ + int ret; + + ret = open_candev(ndev); + if (ret) + return ret; + + ret = nct6694_canfd_start(ndev); + if (ret) { + close_candev(ndev); + return ret; + } + + netif_start_queue(ndev); + + return 0; +} + +static int nct6694_canfd_close(struct net_device *ndev) +{ + netif_stop_queue(ndev); + nct6694_canfd_stop(ndev); + close_candev(ndev); + + return 0; +} + +static netdev_tx_t nct6694_canfd_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + struct nct6694 *nct6694 = priv->nct6694; + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + struct net_device_stats *stats = &ndev->stats; + int can_idx = priv->can_idx; + u32 txid = 0; + int i; + unsigned int echo_byte; + u8 data_buf[REQUEST_CAN_CMD10_LEN] = {0}; + + if (can_dropped_invalid_skb(ndev, skb)) + return NETDEV_TX_OK; + + /* + * No check for NCT66794 because the TX bit is read-clear + * and may be read-cleared by other function + * Just check the result of tx command. + */ + /* Check if the TX buffer is full */ + netif_stop_queue(ndev); + + if (can_idx == 0) + data_buf[CAN_TAG_IDX] = CAN_TAG_CAN0; + else + data_buf[CAN_TAG_IDX] = CAN_TAG_CAN1; + + if (cf->can_id & CAN_EFF_FLAG) { + txid = cf->can_id & CAN_EFF_MASK; + /* + * In case the Extended ID frame is transmitted, the + * standard and extended part of the ID are swapped + * in the register, so swap them back to send the + * correct ID. + */ + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_EFF; + } else { + txid = cf->can_id & CAN_SFF_MASK; + } + + set_buf32(&data_buf[CAN_ID_IDX], txid); + + data_buf[CAN_DLC_IDX] = cf->len; + + if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_FD; + if (cf->flags & CANFD_BRS) + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_BRS; + } + + if (cf->can_id & CAN_RTR_FLAG) + data_buf[CAN_FLAG_IDX] |= CAN_FLAG_RTR; + + /* set data to buf */ + for (i = 0; i < cf->len; i++) + data_buf[CAN_DATA_IDX + i] = *(u8 *)(cf->data + i); + + can_put_echo_skb(skb, ndev, 0, 0); + + memcpy(priv->data_buf, data_buf, REQUEST_CAN_CMD10_LEN); + queue_work(nct6694->async_workqueue, &priv->tx_work); + + stats->tx_bytes += cf->len; + stats->tx_packets++; + echo_byte = can_get_echo_skb(ndev, 0, NULL); + + netif_wake_queue(ndev); + + return NETDEV_TX_OK; +} + +static void nct6694_canfd_tx_work(struct work_struct *work) +{ + struct nct6694_canfd_priv *priv; + + priv = container_of(work, struct nct6694_canfd_priv, tx_work); + + nct6694_write_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD10_OFFSET(1), + REQUEST_CAN_CMD10_LEN, + priv->data_buf); +} + +static int nuv_canfd_handle_lost_msg(struct net_device *ndev) +{ + struct net_device_stats *stats = &ndev->stats; + struct sk_buff *skb; + struct can_frame *frame; + + netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n"); + + stats->rx_errors++; + stats->rx_over_errors++; + + skb = alloc_can_err_skb(ndev, &frame); + if (unlikely(!skb)) + return 0; + + pr_info("%s: CAN_ERR_CRTL_RX_OVERFLOW\r\n", __func__); + + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; + + netif_receive_skb(skb); + + return 1; +} + +static void nuv_canfd_read_fifo(struct net_device *ndev) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct canfd_frame *cf; + struct sk_buff *skb; + int can_idx = priv->can_idx; + u32 id; + int ret; + u8 data_buf[REQUEST_CAN_CMD11_LEN] = {0}; + u8 fd_format = 0; + int i; + + ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD11_OFFSET(can_idx, 1), + REQUEST_CAN_CMD11_LEN, 0, REQUEST_CAN_CMD11_LEN, + data_buf); + if (ret < 0) + return; + + /* Check type of frame and create skb */ + fd_format = data_buf[CAN_FLAG_IDX] & CAN_FLAG_FD; + if (fd_format) + skb = alloc_canfd_skb(ndev, &cf); + else + skb = alloc_can_skb(ndev, (struct can_frame **)&cf); + + if (!skb) { + stats->rx_dropped++; + return; + } + + cf->len = data_buf[CAN_DLC_IDX]; + + /* Get ID and set flag by its type(Standard ID format or Ext ID format) */ + id = le32_to_cpu(*(u32 *)(&data_buf[CAN_ID_IDX])); + if (data_buf[CAN_FLAG_IDX] & CAN_FLAG_EFF) { + /* + * In case the Extended ID frame is received, the standard + * and extended part of the ID are swapped in the register, + * so swap them back to obtain the correct ID. + */ + id |= CAN_EFF_FLAG; + } + + cf->can_id = id; + + /* Set ESI flag */ + if (data_buf[CAN_FLAG_IDX] & CAN_FLAG_ERR) { + cf->flags |= CANFD_ESI; + netdev_dbg(ndev, "ESI Error\n"); + } + + /* Set RTR and BRS */ + if (!fd_format && (data_buf[CAN_FLAG_IDX] & CAN_FLAG_RTR)) { + cf->can_id |= CAN_RTR_FLAG; + } else { + if (data_buf[CAN_FLAG_IDX] & CAN_FLAG_BRS) + cf->flags |= CANFD_BRS; + + for (i = 0; i < cf->len; i++) + *(u8 *)(cf->data + i) = data_buf[CAN_DATA_IDX + i]; + } + + /* Remove the packet from FIFO */ + stats->rx_packets++; + stats->rx_bytes += cf->len; + netif_receive_skb(skb); +} + +static int nct6694_canfd_do_rx_poll(struct net_device *ndev, int quota) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + int can_idx = priv->can_idx; + u32 pkts = 0; + u8 data_buf_can_evt[REQUEST_CAN_CMD2_LEN] = {0}; + u8 mask_rx = CAN_EVENT_RX_EVT; + u8 rx_evt; + + for (;;) { + nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD2_OFFSET(can_idx, mask_rx), + REQUEST_CAN_CMD2_LEN, 0, + REQUEST_CAN_CMD2_LEN, data_buf_can_evt); + + /* Handle lost messages when handling RX because it is read-cleared reg */ + rx_evt = data_buf_can_evt[CAN_RX_EVT_IDX(can_idx)]; + if (rx_evt & CAN_EVENT_RX_EVT_DATA_LOST) + nuv_canfd_handle_lost_msg(ndev); + + /* No data */ + if ((rx_evt & CAN_EVENT_RX_EVT_DATA_IN) == 0) + break; + + if (quota <= 0) + break; + + nuv_canfd_read_fifo(ndev); + quota--; + pkts++; + } + + return pkts; +} + +static int nct6694_canfd_handle_lec_err(struct net_device *ndev, u8 bus_err) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + + if (bus_err == CAN_EVENT_ERR_NO_ERROR) + return 0; + + priv->can.can_stats.bus_error++; + stats->rx_errors++; + + /* Propagate the error condition to the CAN stack. */ + skb = alloc_can_err_skb(ndev, &cf); + + if (unlikely(!skb)) + return 0; + + /* Read the error counter register and check for new errors. */ + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + switch (bus_err) { + case CAN_EVENT_ERR_CRC_ERROR: + cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; + break; + + case CAN_EVENT_ERR_STUFF_ERROR: + cf->data[2] |= CAN_ERR_PROT_STUFF; + break; + + case CAN_EVENT_ERR_ACK_ERROR: + cf->data[3] = CAN_ERR_PROT_LOC_ACK; + break; + + case CAN_EVENT_ERR_FORM_ERROR: + cf->data[2] |= CAN_ERR_PROT_FORM; + break; + + case CAN_EVENT_ERR_BIT_ERROR: + cf->data[2] |= CAN_ERR_PROT_BIT | + CAN_ERR_PROT_BIT0 | + CAN_ERR_PROT_BIT1; + break; + + case CAN_EVENT_ERR_TIMEOUT_ERROR: + cf->data[2] |= CAN_ERR_PROT_UNSPEC; + break; + + case CAN_EVENT_ERR_UNKNOWN_ERROR: + cf->data[2] |= CAN_ERR_PROT_UNSPEC; + /* + * It means 'unspecified'(the value is '0'). + * But it is not sure if it's ok to send an error package + * without specific error bit. + */ + break; + + default: + break; + } + + /* Reset the error counter, ack the IRQ and re-enable the counter. */ + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + netif_receive_skb(skb); + + return 1; +} + +static int nct6694_canfd_handle_state_change(struct net_device *ndev, + enum can_state new_state) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + struct can_berr_counter bec; + + switch (new_state) { + case CAN_STATE_ERROR_ACTIVE: + /* error active state */ + priv->can.can_stats.error_warning++; + priv->can.state = CAN_STATE_ERROR_ACTIVE; + break; + case CAN_STATE_ERROR_WARNING: + /* error warning state */ + priv->can.can_stats.error_warning++; + priv->can.state = CAN_STATE_ERROR_WARNING; + break; + case CAN_STATE_ERROR_PASSIVE: + /* error passive state */ + priv->can.can_stats.error_passive++; + priv->can.state = CAN_STATE_ERROR_PASSIVE; + break; + case CAN_STATE_BUS_OFF: + /* bus-off state */ + priv->can.state = CAN_STATE_BUS_OFF; + priv->can.can_stats.bus_off++; + can_bus_off(ndev); + break; + default: + break; + } + + /* propagate the error condition to the CAN stack */ + skb = alloc_can_err_skb(ndev, &cf); + if (unlikely(!skb)) + return 0; + + nct6694_canfd_get_berr_counter(ndev, &bec); + + switch (new_state) { + case CAN_STATE_ERROR_WARNING: + /* error warning state */ + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] = (bec.txerr > bec.rxerr) ? CAN_ERR_CRTL_TX_WARNING : + CAN_ERR_CRTL_RX_WARNING; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + break; + case CAN_STATE_ERROR_PASSIVE: + /* error passive state */ + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE; + if (bec.txerr > 127) + cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + break; + case CAN_STATE_BUS_OFF: + /* bus-off state */ + cf->can_id |= CAN_ERR_BUSOFF; + break; + default: + break; + } + + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + netif_receive_skb(skb); + + return 1; +} + +static int nct6694_canfd_handle_state_errors(struct net_device *ndev, + u8 *can_evt_buf) +{ + struct nct6694_canfd_priv *priv = netdev_priv(ndev); + int can_idx = priv->can_idx; + int work_done = 0; + u8 can_status; + + can_status = can_evt_buf[CAN_STATUS_IDX(can_idx)]; + + if (can_status == CAN_EVENT_STATUS_ERROR_ACTIVE && + priv->can.state != CAN_STATE_ERROR_ACTIVE) { + netdev_dbg(ndev, "Error, entered active state\n"); + work_done += nct6694_canfd_handle_state_change(ndev, + CAN_STATE_ERROR_ACTIVE); + } + + if (can_status == CAN_EVENT_STATUS_WARNING && + priv->can.state != CAN_STATE_ERROR_WARNING) { + netdev_dbg(ndev, "Error, entered warning state\n"); + work_done += nct6694_canfd_handle_state_change(ndev, + CAN_STATE_ERROR_WARNING); + } + + if (can_status == CAN_EVENT_STATUS_ERROR_PASSIVE && + priv->can.state != CAN_STATE_ERROR_PASSIVE) { + netdev_dbg(ndev, "Error, entered passive state\n"); + work_done += nct6694_canfd_handle_state_change(ndev, + CAN_STATE_ERROR_PASSIVE); + } + + if (can_status == CAN_EVENT_STATUS_BUS_OFF && + priv->can.state != CAN_STATE_BUS_OFF) { + netdev_dbg(ndev, "Error, entered bus-off state\n"); + work_done += nct6694_canfd_handle_state_change(ndev, + CAN_STATE_BUS_OFF); + } + + return work_done; +} + +static void nct6694_canfd_rx_work(struct work_struct *work) +{ + struct nct6694_canfd_priv *priv; + struct net_device *ndev; + struct net_device_stats *stats; + int ret, can_idx; + int work_done = 0; + int quota = RX_QUATA; + u8 data_buf_can_evt[REQUEST_CAN_CMD2_LEN] = {0}; + u8 bus_err; + u8 mask_sts = CAN_EVENT_ERR | CAN_EVENT_STATUS; + + priv = container_of(work, struct nct6694_canfd_priv, rx_work); + ndev = priv->ndev; + can_idx = priv->can_idx; + stats = &ndev->stats; + + ret = nct6694_read_msg(priv->nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD2_OFFSET(can_idx, mask_sts), + REQUEST_CAN_CMD2_LEN, 0, REQUEST_CAN_CMD2_LEN, + data_buf_can_evt); + if (ret < 0) + return; + + /* Handle bus state changes */ + work_done += nct6694_canfd_handle_state_errors(ndev, data_buf_can_evt); + + /* Handle lec errors on the bus */ + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) { + bus_err = data_buf_can_evt[CAN_ERR_IDX(can_idx)]; + work_done += nct6694_canfd_handle_lec_err(ndev, bus_err); + } + + /* + * Check data lost and handle normal messages on RX. + * Don't check rx fifo-empty here because the data-lost bit in the same reg is read-cleared, + * we handle it when handling rx event + */ + + work_done += nct6694_canfd_do_rx_poll(ndev, quota - work_done); +} + +static void nct6694_can_handler(void *private_data) +{ + struct nct6694_canfd_priv *priv = private_data; + struct nct6694 *nct6694 = priv->nct6694; + + queue_work(nct6694->async_workqueue, &priv->rx_work); +} + +static const struct net_device_ops nct6694_canfd_netdev_ops = { + .ndo_open = nct6694_canfd_open, + .ndo_stop = nct6694_canfd_close, + .ndo_start_xmit = nct6694_canfd_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +static int nct6694_canfd_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct nct6694_canfd_priv *priv; + struct net_device *ndev; + unsigned int can_clk; + int ret; + + ret = nct6694_read_msg(nct6694, REQUEST_CAN_MOD, + REQUEST_CAN_CMD1_OFFSET, + REQUEST_CAN_CMD1_LEN, + CAN_CLK_IDX, CAN_CLK_LEN, + (unsigned char *)&can_clk); + if (ret < 0) { + dev_err(&pdev->dev, "%s: Failed to get can clock frequency: %pe\n", + __func__, ERR_PTR(ret)); + return ret; + } + + pr_info("can_clk = %d\n", le32_to_cpu(can_clk)); + + ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1); + if (!ndev) + return -ENOMEM; + + ndev->flags |= IFF_ECHO; + ndev->netdev_ops = &nct6694_canfd_netdev_ops; + + priv = netdev_priv(ndev); + priv->nct6694 = nct6694; + priv->ndev = ndev; + + ret = nct6694_register_handler(nct6694, CAN_IRQ_STATUS, + nct6694_can_handler, priv); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n", + __func__, ERR_PTR(ret)); + free_candev(ndev); + return ret; + } + + INIT_WORK(&priv->rx_work, nct6694_canfd_rx_work); + INIT_WORK(&priv->tx_work, nct6694_canfd_tx_work); + + priv->can_idx = cell->id; + priv->can.state = CAN_STATE_STOPPED; + priv->can.clock.freq = le32_to_cpu(can_clk); + priv->can.bittiming_const = &nct6694_canfd_bittiming_nominal_const; + priv->can.data_bittiming_const = &nct6694_canfd_bittiming_data_const; + priv->can.do_set_mode = nct6694_canfd_set_mode; + priv->can.do_get_berr_counter = nct6694_canfd_get_berr_counter; + + priv->can.ctrlmode = CAN_CTRLMODE_FD; + + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_FD | + CAN_CTRLMODE_FD_NON_ISO | + CAN_CTRLMODE_BERR_REPORTING; + + platform_set_drvdata(pdev, priv); + SET_NETDEV_DEV(priv->ndev, &pdev->dev); + + ret = register_candev(priv->ndev); + if (ret) { + dev_err(&pdev->dev, "register_candev failed: %d\n", ret); + free_candev(ndev); + return ret; + } + + return 0; +} + +static void nct6694_canfd_remove(struct platform_device *pdev) +{ + struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev); + + cancel_work_sync(&priv->rx_work); + unregister_candev(priv->ndev); + free_candev(priv->ndev); +} + +static struct platform_driver nct6694_canfd_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_canfd_probe, + .remove = nct6694_canfd_remove, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_canfd_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_canfd_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_canfd_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848561 Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.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 A53AC1C4A0B; Thu, 24 Oct 2024 09:00:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760411; cv=none; b=mNGcdBv9oOKeBAOCkI2B8N20Gvt1dHk7S1Syd4pKC0RVXQc3JujqWwM+7XZl/NzVDUBVGg1pCqfp6rj8Vy7olhhri6tZBtXxvVWzQefbiObNQSMmaZMc5Zom0YxoiHbQGgCMWDBWDVyU0ddrATIlkUGbeOV5a22Dzf7md+uuvfQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760411; c=relaxed/simple; bh=3I8ywrsU0xhtRdbt2GAAAVfMRR4Z0NFWWn/DEx8sutw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=qTxtKRmXjHDBGFKJUOuffsAPc4KXkxVs6SF98rIQgeYKrvWx1tzHZO0l3Jb1aRYc45EmpdOmC/FtPyj+QJSeXAQybVIPRTFPCPB0PaqpnuYI5KzeQshf4hOuiviSlGQ2BGdkcb3yIEbY1b3g0AxIncvVU9a2cKYJr/YIO2JX5F8= 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=fnEQJk45; arc=none smtp.client-ip=209.85.214.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="fnEQJk45" Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-20cbcd71012so5909395ad.3; Thu, 24 Oct 2024 02:00:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760408; x=1730365208; 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=5FARd89uMKMiwdRE0HODiifxKuOk++DnE6Elk0uN6w0=; b=fnEQJk458wv1gR/FZmPZcnURLeS3uD/tg2Z+LiShknc4oHqLGf5Cn69ZwqUaTmghfN ShPwYkucCZAk+LbuwYyjyPJEpETUVfdIPi7BlmNFgMgx9QxjDMDc0wdlNOPk2x7AA5O7 mX/RXveGSzAa8NyLTFzLNpxKS3DWwSogBYlCyExisnvWj6+3N2cAsUTqA1HVOZaatA1f bffZAnoWtxElnoDKyl/0JS/PO6gtajcvi+r76Qh3wyjVcrMN4LSZbp+xdIWojNpSA0MZ 8atkpjYjA756vX8/LIXpMaj2SGOtfN00vH6xnpNOBICglmroRSxlRXYjYx3NcjWZptFH teZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760408; x=1730365208; 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=5FARd89uMKMiwdRE0HODiifxKuOk++DnE6Elk0uN6w0=; b=fyzlpN4aBvY6ch+7JTiIrIJ2PN/vu+QZ0eOOttY+9jFRcMhJoGwNjs7dMRNNc3uMKM M1rwp27IFBdyLQa8m+wJq/sKwdIm3v2c/O7OVI+P1Es+cLbGIWEwrf3pA4KDzllnzAlz 25hIoSSn16UJKK5YJxWLb21TYm/YHASiXaCEQbyBFPUfp5D/kG3GYbDSUzp5DnIRySjH JfQ0zxM51K28L064QBTtpqQzhXWCA1dfRxoPPDD8v+jzLk8vtnC5HDzDev8iiT5Fiv+u TciCorZ4T9HFK+6BaYFiEXZ2CvFgt7mZ1Wo1/77W2WS3Cgqcq+5mPu97IEiqyAyB1Lft wwug== X-Forwarded-Encrypted: i=1; AJvYcCUsZUi8xqW3nf+3mlSi12tKUSGU6tNiqFiA59BOWmSKztzLr36aEKtfNArpHNBzlQFeVVKpz9hNlQfA@vger.kernel.org, AJvYcCUwfYx2j9FaOYxi9ILdFyyy/5dHHSAAo/+Yx7yv7C/NHISCvFMiezMkbz8tp0Dyc4wgXg/TWF50Rr3NS1Wh4+A=@vger.kernel.org, AJvYcCVF3ICxiuOKprFAPFtnQltPPtYUSSm4StI0XC3ivD7LdiPR6RH8D+xH0M70t36gnQ3QdcwjEdpwkCs=@vger.kernel.org, AJvYcCVJ4fqFZDIsBMSRn0MV5GtNiEQNIV4nm8aLx5wYR3lfAtEPlMK4XdFBpL3ufaSPSPDdIV3HEw8PsI9wUA==@vger.kernel.org, AJvYcCVvmdHb67rkC/zuJq3NJXnOW3Cn5IGm5k4SpuH0gn1+tE5Zf+byThXxLHoI3B8Z+AhJUDExR9h4EdRU@vger.kernel.org, AJvYcCVvwFKTOmEPcKrE5BzprR6XNIvN2UYktutu99NNbI6IdSEa2K3e5/ew3Yc8+n5LySXquOA17Wuh@vger.kernel.org, AJvYcCWeBt1aKZBQlz6oBLbeIc2SkZDwinEZLyixJry9cQEzAZU9GE0CTr2cSGbY+0gtgxRy9CjST6TKZwXn@vger.kernel.org, AJvYcCWoGGqXeIGeSE6EAEHq6NET03ollHo1nQeFTCBWNtw3pYnsD/6WeMLWyH5HwbAp8dZjBJADccDpdI+xpkw=@vger.kernel.org, AJvYcCXPe0rPPI4yYE8gxiPeISsEZ5QnNECPA3wTxE5Vw1AmS29GpM5iSfblq/c8Q7J5tCaC/jPWJIGVSAQg@vger.kernel.org X-Gm-Message-State: AOJu0Yx6G7PSQRRac63TsJwZJ5Eor94b82ECH8hMu2l0LWLgXgJ6jcqm bD01wUDjdyk4JZDsF9ME96Cp1fp6hajcJDE6E8u2FQDXQwlQr0p3 X-Google-Smtp-Source: AGHT+IEjlQjtK3IdPe7OozHEaVT+1//rpRzkg/Zm2w6oPWo0AbisWaaw/ay7mB7dih1HMy0pVb7n2Q== X-Received: by 2002:a17:902:fc83:b0:20c:aae9:7bd7 with SMTP id d9443c01a7336-20fa9e9f8d1mr50554125ad.39.1729760407938; Thu, 24 Oct 2024 02:00:07 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.02.00.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:07 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 5/9] watchdog: Add Nuvoton NCT6694 WDT support Date: Thu, 24 Oct 2024 16:59:18 +0800 Message-Id: <20241024085922.133071-6-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports Watchdog timer functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/watchdog/Kconfig | 11 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/nct6694_wdt.c | 329 +++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 drivers/watchdog/nct6694_wdt.c diff --git a/MAINTAINERS b/MAINTAINERS index eccd5e795daa..63387c0d4ab6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16442,6 +16442,7 @@ F: drivers/gpio/gpio-nct6694.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/nct6694_canfd.c +F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 684b9fe84fff..bc9d63d69204 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -739,6 +739,17 @@ config MAX77620_WATCHDOG MAX77620 chips. To compile this driver as a module, choose M here: the module will be called max77620_wdt. +config NCT6694_WATCHDOG + tristate "Nuvoton NCT6694 watchdog support" + depends on MFD_NCT6694 + select WATCHDOG_CORE + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to watchdog timer. + + This driver can also be built as a module. If so, the module + will be called nct6694_wdt. + config IMX2_WDT tristate "IMX2+ Watchdog" depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ab6f2b41e38e..453ceacd43ab 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -231,6 +231,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o +obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c new file mode 100644 index 000000000000..68e2926ec504 --- /dev/null +++ b/drivers/watchdog/nct6694_wdt.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 WDT driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-wdt" + +#define WATCHDOG_TIMEOUT 10 +#define WATCHDOG_PRETIMEOUT 0 + +/* Host interface */ +#define REQUEST_WDT_MOD 0x07 + +/* Message Channel*/ +/* Command 00h */ +#define REQUEST_WDT_CMD0_LEN 0x0F +#define REQUEST_WDT_CMD0_OFFSET(idx) (idx ? 0x0100 : 0x0000) /* OFFSET = SEL|CMD */ +#define WDT_PRETIMEOUT_IDX 0x00 +#define WDT_PRETIMEOUT_LEN 0x04 /* PRETIMEOUT(3byte) | ACT(1byte) */ +#define WDT_TIMEOUT_IDX 0x04 +#define WDT_TIMEOUT_LEN 0x04 /* TIMEOUT(3byte) | ACT(1byte) */ +#define WDT_COUNTDOWN_IDX 0x0C +#define WDT_COUNTDOWN_LEN 0x03 + +#define WDT_PRETIMEOUT_ACT BIT(1) +#define WDT_TIMEOUT_ACT BIT(1) + +/* Command 01h */ +#define REQUEST_WDT_CMD1_LEN 0x04 +#define REQUEST_WDT_CMD1_OFFSET(idx) (idx ? 0x0101 : 0x0001) /* OFFSET = SEL|CMD */ +#define WDT_CMD_IDX 0x00 +#define WDT_CMD_LEN 0x04 + +static unsigned int timeout; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); + +static unsigned int pretimeout; +module_param(pretimeout, int, 0); +MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +struct nct6694_wdt_data { + struct nct6694 *nct6694; + struct watchdog_device wdev; + unsigned int wdev_idx; +}; + +static inline void set_buf32(void *buf, u32 u32_val) +{ + u8 *p = (u8 *)buf; + + p[0] = u32_val & 0xFF; + p[1] = (u32_val >> 8) & 0xFF; + p[2] = (u32_val >> 16) & 0xFF; + p[3] = (u32_val >> 24) & 0xFF; +} + +static int nct6694_wdt_start(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + + pr_debug("%s: WDT(%d) Start\n", __func__, data->wdev_idx); + + return 0; +} + +static int nct6694_wdt_stop(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'C'}; + int ret; + + pr_debug("%s: WDT(%d) Close\n", __func__, data->wdev_idx); + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD1_LEN, buf); + if (ret) + pr_err("%s: Failed to start WDT device!\n", __func__); + + return ret; +} + +static int nct6694_wdt_ping(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned char buf[REQUEST_WDT_CMD1_LEN] = {'W', 'D', 'T', 'S'}; + int ret; + + pr_debug("%s: WDT(%d) Ping\n", __func__, data->wdev_idx); + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD1_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD1_LEN, buf); + if (ret) + pr_err("%s: Failed to ping WDT device!\n", __func__); + + return ret; +} + +static int nct6694_wdt_set_timeout(struct watchdog_device *wdev, + unsigned int timeout) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned int timeout_fmt, pretimeout_fmt; + unsigned char buf[REQUEST_WDT_CMD0_LEN]; + int ret; + + if (timeout < wdev->pretimeout) { + pr_err("%s: 'timeout' must be greater than 'pre timeout'!\n", + __func__); + return -EINVAL; + } + + timeout_fmt = timeout * 1000 | (WDT_TIMEOUT_ACT << 24); + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24); + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt)); + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt)); + + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD0_LEN, buf); + if (ret) { + pr_err("%s: Don't write the setup command in Start stage!\n", + __func__); + return ret; + } + + wdev->timeout = timeout; + + return 0; +} + +static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev, + unsigned int pretimeout) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned int timeout_fmt, pretimeout_fmt; + unsigned char buf[REQUEST_WDT_CMD0_LEN]; + int ret; + + if (pretimeout > wdev->timeout) { + pr_err("%s: 'pre timeout' must be less than 'timeout'!\n", + __func__); + return -EINVAL; + } + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24); + pretimeout_fmt = pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24); + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt)); + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt)); + + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD0_LEN, buf); + if (ret) { + pr_err("%s: Don't write the setup command in Start stage!\n", __func__); + return ret; + } + + wdev->pretimeout = pretimeout; + return 0; +} + +static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned char buf[WDT_COUNTDOWN_LEN]; + unsigned int timeleft_ms; + int ret; + + ret = nct6694_read_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD0_LEN, WDT_COUNTDOWN_IDX, + WDT_COUNTDOWN_LEN, buf); + if (ret) + pr_err("%s: Failed to get WDT device!\n", __func__); + + timeleft_ms = ((buf[2] << 16) | (buf[1] << 8) | buf[0]) & 0xFFFFFF; + + return timeleft_ms / 1000; +} + +static int nct6694_wdt_setup(struct watchdog_device *wdev) +{ + struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev); + struct nct6694 *nct6694 = data->nct6694; + unsigned char buf[REQUEST_WDT_CMD0_LEN] = {0}; + unsigned int timeout_fmt, pretimeout_fmt; + int ret; + + if (timeout) + wdev->timeout = timeout; + + if (pretimeout) { + wdev->pretimeout = pretimeout; + pretimeout_fmt = wdev->pretimeout * 1000 | (WDT_PRETIMEOUT_ACT << 24); + } else { + pretimeout_fmt = 0; + } + + timeout_fmt = wdev->timeout * 1000 | (WDT_TIMEOUT_ACT << 24); + set_buf32(&buf[WDT_TIMEOUT_IDX], le32_to_cpu(timeout_fmt)); + set_buf32(&buf[WDT_PRETIMEOUT_IDX], le32_to_cpu(pretimeout_fmt)); + + ret = nct6694_write_msg(nct6694, REQUEST_WDT_MOD, + REQUEST_WDT_CMD0_OFFSET(data->wdev_idx), + REQUEST_WDT_CMD0_LEN, buf); + if (ret) + return ret; + + pr_info("Setting WDT(%d): timeout = %d, pretimeout = %d\n", + data->wdev_idx, wdev->timeout, wdev->pretimeout); + + return 0; +} + +static const struct watchdog_info nct6694_wdt_info = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, + .identity = DRVNAME, +}; + +static const struct watchdog_ops nct6694_wdt_ops = { + .owner = THIS_MODULE, + .start = nct6694_wdt_start, + .stop = nct6694_wdt_stop, + .set_timeout = nct6694_wdt_set_timeout, + .set_pretimeout = nct6694_wdt_set_pretimeout, + .get_timeleft = nct6694_wdt_get_time, + .ping = nct6694_wdt_ping, +}; + +static int nct6694_wdt_probe(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct nct6694_wdt_data *data; + struct watchdog_device *wdev; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nct6694 = nct6694; + data->wdev_idx = cell->id; + + wdev = &data->wdev; + wdev->info = &nct6694_wdt_info; + wdev->ops = &nct6694_wdt_ops; + wdev->timeout = WATCHDOG_TIMEOUT; + wdev->pretimeout = WATCHDOG_PRETIMEOUT; + wdev->min_timeout = 1; + wdev->max_timeout = 255; + + platform_set_drvdata(pdev, data); + + /* Register watchdog timer device to WDT framework */ + watchdog_set_drvdata(&data->wdev, data); + watchdog_init_timeout(&data->wdev, timeout, &pdev->dev); + watchdog_set_nowayout(&data->wdev, nowayout); + watchdog_stop_on_reboot(&data->wdev); + + ret = devm_watchdog_register_device(&pdev->dev, &data->wdev); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register watchdog device: %d\n", + __func__, ret); + return ret; + } + + ret = nct6694_wdt_setup(&data->wdev); + if (ret) { + dev_err(&pdev->dev, "Failed to setup WDT device!\n"); + return ret; + } + + return 0; +} + +static struct platform_driver nct6694_wdt_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_wdt_probe, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_wdt_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_wdt_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_wdt_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-WDT driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848562 Received: from mail-pl1-f174.google.com (mail-pl1-f174.google.com [209.85.214.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 793B51CACF3; Thu, 24 Oct 2024 09:00:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760416; cv=none; b=umDsIBAWFgK5TbmjQoQkvS+bfGQBysnaEYqpcR2riIw7Ec7uLcV0sfLi65LpuEyVuwA9jPr2daJ1DXdRvtjRNJFbBOAy8DppsXb+4flygm8B4TvffRRYVqTR1IsC4yicvdwDW2faiSGmqqPyzb4AB2Krh5ZxMZim4nsxoJ1mzCk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760416; c=relaxed/simple; bh=G7L4TJRjXe10tGv6q3i4077rvAHxAl3xEvV9p8tOxwo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fBPjQAt2hCJzE7AvLFiQRktI63Co9NMs808oQG8ssJMJg1r3vWSZVwMasUO8buXUfhfgulgZoEftCaGArYu/8xLXK5hRTY9BlMzY01ENwTMgyD57VmTZ8ix0eUdjqdZMMlw9PcZqAO1SI+kQDbODOYo8C+PCXvlnkltpchQVK2k= 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=nGvrFbyw; arc=none smtp.client-ip=209.85.214.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="nGvrFbyw" Received: by mail-pl1-f174.google.com with SMTP id d9443c01a7336-20b5affde14so4238585ad.3; Thu, 24 Oct 2024 02:00:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760413; x=1730365213; 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=bg6mJIYXLvLavjiOqD84MMx4rmvQyCtl1MVLqjoMoQg=; b=nGvrFbywH28En4OEcynhfYwG+1JvpiB9SDuF1OsGcNRGiu3aDBZJOsm1w1Lrw5sWUE yGdd57EFIAhZN/LGNRCmFshLQhhKHBWiNKwyG3PG4LqExZ0cTB8OCrIfOAO2auqBqcS8 Y30Xuw7RDGI65N+8j9D2OtjGsWpABr5Vs/dyvBJLkGO7KMeaTpoy4huK8ZoTQ9+rX7SW sABb84woG9Et9WROMVCVSmRTRA/aIfsEh+G1RvfE1JYHltQl+Qj05HN4K+kb1Q4nHo4W iqlmMJuLqiaUlDevvMEx32Tq5PpHdmzw0lKJaU7U1h9j5gqNLAmXF8Rpd5ddDMO5Pf59 T2pA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760413; x=1730365213; 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=bg6mJIYXLvLavjiOqD84MMx4rmvQyCtl1MVLqjoMoQg=; b=cpT79yokxFfllYe2a665llMNJHQx6JVvzV+x+4viVasEdbtmrCvgJvb0ZM/xEadJw+ jfUnu6tlsBBEn4D2Xrpa6SuRR9dFCzCyVH3VJNseTKNhmdoJQxVQVgd781EGfEvHl655 6Gy48cjhEb9WbPZOKDiFMZZaYa6T7WiWgIQznYA4qjS9Ef1bN6Wb0tqGO7zyoSRzfcCu 53FDAA0h/NZBXej/44mEt9yRIAqpcvArf59ruxdZWJR7DVzbHF1WAN6HU+pFvGQQgl35 1gvY4fcBC9MGJxobgTkQW7HmMXLvP5YsATGUu5LClTX++ulHfYultTD796Pcz2/pOlsc PoQw== X-Forwarded-Encrypted: i=1; AJvYcCUZ+4tzc7zbLEYEeWfhri7ffVT/CoYNDBKTA18gN4EIPPXXRJ6tUx0/wQqvRoIHXK/1U6Db9aThPuDgDdo=@vger.kernel.org, AJvYcCUfxMWYiDTlJv8vWwJLGRdBTsbiKi6m8L3kP2fUZ3WqXj0ArpKREW7Ke0quZgVi0gpsY2QwE0rJWVZVscj1SpU=@vger.kernel.org, AJvYcCUoTdzsclTjyHQ485zuDumKT4ROcsPeMDdKTd0ETfekh8TR/zOM1UaaFlmkzw6krnErr6vw9zHGQZib@vger.kernel.org, AJvYcCUzFQMcEuCjtRhZDmGAht1Yl8RL3StAQ+dR+jH+09tgkjXdEPuOKASwOsXshtqZmLjUAmJDguNacyf3@vger.kernel.org, AJvYcCV3CoU1ADdcx709HxYmczu0giYAsVp60VXXuIzNllSlxzLRPXPzANWCgPw0+236AfvGaTfoiT3dNww=@vger.kernel.org, AJvYcCVaN+xxfyro+J1bNhFRZ3B/7WulE6qz40fInuKCqW1oO2txQMeDNqLrJc/h/X8UMtxVYWklOiAESnKw@vger.kernel.org, AJvYcCVmeY9eSuAR2lh/7koSxaVmhT8iVj/x/3Rsm84fRjK+DP6168KT3hXuJ7g+GVIgrapqTytS1CzS@vger.kernel.org, AJvYcCVmrbsKxDJArgPOA2k7ET9ECzzgRbs99YnpwDVGESEmh4NjrDnfhVL+Uk5GYA3H2CU1yy46z1UIVfiy@vger.kernel.org, AJvYcCW5gbYPpHWrm3K605LmNEdkb3GnVjkJGPNT+gWx4SvrWv12w3SxwDXw4qCiK1jCcx41x3XksJWLQQe0zg==@vger.kernel.org X-Gm-Message-State: AOJu0Yz1kE9ioq+E3MbLjeiIC+bmrii7ncP2K5JTAuCYC7RU6FxL6bzh 09msJpyyZ4FKDU1u/x1VmLJarXp3fVE3BOGNn4UN23XrsgEINorj X-Google-Smtp-Source: AGHT+IFCigJK2WjL7FVPN2XkbVmqaeHaqnQ5f1BT79j73Jb4llrXAQ5s/hE5xWaauxcn7iEmbmGGDg== X-Received: by 2002:a17:903:2284:b0:20b:fd73:32bb with SMTP id d9443c01a7336-20fb98f21d1mr11678555ad.2.1729760412547; Thu, 24 Oct 2024 02:00:12 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.02.00.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:12 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 6/9] hwmon: Add Nuvoton NCT6694 HWMON support Date: Thu, 24 Oct 2024 16:59:19 +0800 Message-Id: <20241024085922.133071-7-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports Hardware monitor functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6694-hwmon.c | 407 ++++++++++++++++++++++++++++++++++ 4 files changed, 419 insertions(+) create mode 100644 drivers/hwmon/nct6694-hwmon.c diff --git a/MAINTAINERS b/MAINTAINERS index 63387c0d4ab6..2aa87ad84156 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16439,6 +16439,7 @@ M: Ming Yu L: linux-kernel@vger.kernel.org S: Supported F: drivers/gpio/gpio-nct6694.c +F: drivers/hwmon/nct6694-hwmon.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/nct6694_canfd.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 08a3c863f80a..740e4afe6582 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1625,6 +1625,16 @@ config SENSORS_NCT6683 This driver can also be built as a module. If so, the module will be called nct6683. +config SENSORS_NCT6694 + tristate "Nuvoton NCT6694 Hardware Monitor support" + depends on MFD_NCT6694 + help + Say Y here to support Nuvoton NCT6694 hardware monitoring + functionality. + + This driver can also be built as a module. If so, the module + will be called nct6694-hwmon. + config SENSORS_NCT6775_CORE tristate select REGMAP diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9554d2fdcf7b..729961176d00 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -167,6 +167,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) += mr75203.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o +obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o nct6775-objs := nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c new file mode 100644 index 000000000000..7d7d22a650b0 --- /dev/null +++ b/drivers/hwmon/nct6694-hwmon.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 HWMON driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-hwmon" + +/* Host interface */ +#define REQUEST_RPT_MOD 0xFF +#define REQUEST_HWMON_MOD 0x00 + +/* Report Channel */ +#define HWMON_FIN_IDX(x) (0x50 + ((x) * 2)) +#define HWMON_FIN_STS(x) (0x6E + (x)) +#define HWMON_PWM_IDX(x) (0x70 + (x)) + +/* Message Channel*/ +/* Command 00h */ +#define REQUEST_HWMON_CMD0_LEN 0x40 +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define HWMON_FIN_EN(x) (0x04 + (x)) +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x)) +/* Command 02h */ +#define REQUEST_HWMON_CMD2_LEN 0x90 +#define REQUEST_HWMON_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */ +#define HWMON_SMI_CTRL_IDX 0x00 +#define HWMON_FIN_LIMIT_IDX(x) (0x70 + ((x) * 2)) +#define HWMON_CMD2_HYST_MASK 0x1F +/* Command 03h */ +#define REQUEST_HWMON_CMD3_LEN 0x08 +#define REQUEST_HWMON_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */ + +struct nct6694_hwmon_data { + struct nct6694 *nct6694; + + /* Make sure read & write commands are consecutive */ + struct mutex hwmon_lock; +}; + +#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_ENABLE | HWMON_F_INPUT | \ + HWMON_F_MIN | HWMON_F_MIN_ALARM) +#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_FREQ) + +static const struct hwmon_channel_info *nct6694_info[] = { + HWMON_CHANNEL_INFO(fan, + NCT6694_HWMON_FAN_CONFIG, /* FIN0 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN1 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN2 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN3 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN4 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN5 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN6 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN7 */ + NCT6694_HWMON_FAN_CONFIG, /* FIN8 */ + NCT6694_HWMON_FAN_CONFIG), /* FIN9 */ + + HWMON_CHANNEL_INFO(pwm, + NCT6694_HWMON_PWM_CONFIG, /* PWM0 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM1 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM2 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM3 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM4 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM5 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM6 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM7 */ + NCT6694_HWMON_PWM_CONFIG, /* PWM8 */ + NCT6694_HWMON_PWM_CONFIG), /* PWM9 */ + NULL +}; + +static int nct6694_fan_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); + unsigned char buf[2]; + int ret; + + switch (attr) { + case hwmon_fan_enable: + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, + HWMON_FIN_EN(channel / 8), + 1, buf); + if (ret) + return -EINVAL; + + *val = buf[0] & BIT(channel % 8) ? 1 : 0; + + break; + + case hwmon_fan_input: + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + HWMON_FIN_IDX(channel), 2, 0, + 2, buf); + if (ret) + return -EINVAL; + + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF; + + break; + + case hwmon_fan_min: + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD2_OFFSET, + REQUEST_HWMON_CMD2_LEN, + HWMON_FIN_LIMIT_IDX(channel), + 2, buf); + if (ret) + return -EINVAL; + + *val = (buf[1] | (buf[0] << 8)) & 0xFFFF; + + break; + + case hwmon_fan_min_alarm: + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + HWMON_FIN_STS(channel / 8), + 1, 0, 1, buf); + if (ret) + return -EINVAL; + + *val = buf[0] & BIT(channel % 8); + + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nct6694_pwm_read(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); + unsigned char buf; + int ret; + + switch (attr) { + case hwmon_pwm_input: + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + HWMON_PWM_IDX(channel), + 1, 0, 1, &buf); + if (ret) + return -EINVAL; + + *val = buf; + + break; + case hwmon_pwm_freq: + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, + HWMON_PWM_FREQ_IDX(channel), + 1, &buf); + if (ret) + return -EINVAL; + + *val = buf * 25000 / 255; + + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nct6694_fan_write(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); + unsigned char enable_buf[REQUEST_HWMON_CMD0_LEN] = {0}; + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0}; + u16 fan_val = (u16)val; + int ret; + + switch (attr) { + case hwmon_fan_enable: + mutex_lock(&data->hwmon_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, 0, + REQUEST_HWMON_CMD0_LEN, + enable_buf); + if (ret) + goto err; + + if (val) + enable_buf[HWMON_FIN_EN(channel / 8)] |= BIT(channel % 8); + else + enable_buf[HWMON_FIN_EN(channel / 8)] &= ~BIT(channel % 8); + + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, enable_buf); + if (ret) + goto err; + + break; + + case hwmon_fan_min: + mutex_lock(&data->hwmon_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD2_OFFSET, + REQUEST_HWMON_CMD2_LEN, 0, + REQUEST_HWMON_CMD2_LEN, buf); + if (ret) + goto err; + + buf[HWMON_FIN_LIMIT_IDX(channel)] = (u8)((fan_val >> 8) & 0xFF); + buf[HWMON_FIN_LIMIT_IDX(channel) + 1] = (u8)(fan_val & 0xFF); + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD2_OFFSET, + REQUEST_HWMON_CMD2_LEN, buf); + if (ret) + goto err; + + break; + + default: + ret = -EOPNOTSUPP; + goto err; + } + +err: + mutex_unlock(&data->hwmon_lock); + return ret; +} + +static int nct6694_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_fan: /* in RPM */ + return nct6694_fan_read(dev, attr, channel, val); + + case hwmon_pwm: /* in value 0~255 */ + return nct6694_pwm_read(dev, attr, channel, val); + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nct6694_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_fan: + return nct6694_fan_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static umode_t nct6694_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_enable: + case hwmon_fan_min: + return 0644; + + case hwmon_fan_input: + case hwmon_fan_min_alarm: + return 0444; + + default: + return 0; + } + + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_freq: + return 0444; + default: + return 0; + } + + default: + return 0; + } + + return 0; +} + +static const struct hwmon_ops nct6694_hwmon_ops = { + .is_visible = nct6694_is_visible, + .read = nct6694_read, + .write = nct6694_write, +}; + +static const struct hwmon_chip_info nct6694_chip_info = { + .ops = &nct6694_hwmon_ops, + .info = nct6694_info, +}; + +static int nct6694_hwmon_init(struct nct6694_hwmon_data *data) +{ + unsigned char buf[REQUEST_HWMON_CMD2_LEN] = {0}; + int ret; + + /* Set Fan input Real Time alarm mode */ + mutex_lock(&data->hwmon_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD2_OFFSET, + REQUEST_HWMON_CMD2_LEN, 0, + REQUEST_HWMON_CMD2_LEN, buf); + if (ret) + goto err; + + buf[HWMON_SMI_CTRL_IDX] = 0x02; + + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD2_OFFSET, + REQUEST_HWMON_CMD2_LEN, buf); + if (ret) + goto err; + +err: + mutex_unlock(&data->hwmon_lock); + return ret; +} + +static int nct6694_hwmon_probe(struct platform_device *pdev) +{ + struct nct6694_hwmon_data *data; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + struct device *hwmon_dev; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nct6694 = nct6694; + mutex_init(&data->hwmon_lock); + platform_set_drvdata(pdev, data); + + ret = nct6694_hwmon_init(data); + if (ret) + return -EIO; + + /* Register hwmon device to HWMON framework */ + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "nct6694", data, + &nct6694_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) { + dev_err(&pdev->dev, "%s: Failed to register hwmon device!\n", + __func__); + return PTR_ERR(hwmon_dev); + } + + return 0; +} + +static struct platform_driver nct6694_hwmon_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_hwmon_probe, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_hwmon_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_hwmon_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_hwmon_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-HWMON driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848563 Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) (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 7B9C61CB9E3; Thu, 24 Oct 2024 09:00:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760422; cv=none; b=DqSl1+sat0Xq0YoT46j/hGqe9EaNbOoear+A/065Q90mEmSd9SEx8HsVVfIimVMTlenfa7B13PV3n68Y/h9ZctSOjaqRbcuFR3pSYgsPx1XzMNT7ZzsCQmVIlZ1xLBxYFYmsW7Rm9zQT9OEx995zHhMB/I2k6oEjLH6EszTfAlU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760422; c=relaxed/simple; bh=/icXWOgKtTqQkBjzhGxSpSk5O+DNM8LglkQd54dsAno=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=idYcLmRNfzM4oxybFiLA6YNc5QGXvvya9tzJzwFk0CWcy0YxxX+nZ/ywLoxdOoVq19TuqMJfNq9GYgPfhkvWydyYZ8uzAcP8mu9xrr3D5JqWY2F4OVVsUTUY3n9tx0RhJZwl6lQzCaP8v/CBlXQ8QWNxeVDedRtzkq+G8ygSf68= 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=ACqV1n83; arc=none smtp.client-ip=209.85.214.169 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="ACqV1n83" Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-20ce65c8e13so4394575ad.1; Thu, 24 Oct 2024 02:00:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760419; x=1730365219; 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=I9IZudYcHUgSCXGjbVTD1FlqJ67XAgyb889HnrrbtzQ=; b=ACqV1n83EcR1pK5fmfIFUNS/oSwfQa0lWLVJwT9AD2OkOTJii5urximrBcUzQxs5/2 vWUobPhwFKfZd5/DRuj+ir/NlMJyYJKG0RhvuSnsaqku9DPskg9Cc6Nrw7QFtEUwX7JT TI4Ugo/zksqAw6JVm0mMFa5tr+/CBPB1ct+xIZ9sdGFblEPVDZpks3ft+5eFGBJ1kIvZ 5Nl76xBTLFhZaJj1txCf0ZClAR7zJFK0kIAXJSJOgOAX5Be7IlkxHUuaMa/rwSppeYgG u/upN4yZKsXAJ6EQsQG5YCPbjLmBsz53Gl+2R7uUd2N6wrglJhlJNkvFXi6sdPs8dNrm /Cig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760419; x=1730365219; 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=I9IZudYcHUgSCXGjbVTD1FlqJ67XAgyb889HnrrbtzQ=; b=lb1GYx8wH7o3BaYM/HRmKdqnLILLMac0Ay+7YW9sK2pWSjENfbkrDSYNADp4X4aDp2 FmObgT/xuOkN7jUg/DtW7KWv71JBefEqNPJbDmqwhSLR1fvjIysqyYiP/TYCY7NEdhiv G4dSZvnUIsCLj3PWuyYzNrASMIv9+oFTbBs+fjqZ9GmIz93oW39IYgv/C1vCkaY9X8Im Kpi+GrhS1Tiwjg6nP8F2dLQvqWANr9ZSUsygUSH1/yTlizq5ngwLgjnm1GVVHHVfmsya +pp9FRynTEhihpaZSMp5wLngXJijQuE5e8D7NpaU/GCEIrUrrN4sl5qf6jAW5Ly+3pr8 dhgA== X-Forwarded-Encrypted: i=1; AJvYcCU3tWnbEPLkCTDrfNIrwCTSzQ75L+EtVsTknObuzCZ4wIj/2opKNYZXQdhqX9gWmFD5xcktmMGt@vger.kernel.org, AJvYcCUc+gLb/eq5nfE5/Nqt4iO7McR4GN3Hgp5QApMXsq7MBpKClIbM2YJRBlgK4r/z9qM8nPicDTtPoRmR13E=@vger.kernel.org, AJvYcCUm0ShID4kJaYeuPZdgG0F8uJZPZApfjECRICnhNfHa1JiLT7aiqiRB6xNAQNmrXEooH7KyWqDOJ1E=@vger.kernel.org, AJvYcCVAOqnPyRX3rxQ/G//Ajy/iOU7OoYfM3y0I8KumohOll3K+yvgmmkrYVElKBN9fYe4H4eQ98hdD9gOoVQ==@vger.kernel.org, AJvYcCVYBH21SArDmCyTOe7ieE0EkKqU1Pex8pkfIOcstxNkdCj/D+rXgvFY8fjOGWuXq2mZ/SHb8/HGWc+E@vger.kernel.org, AJvYcCX2Cb/SfbU3yeqL35/PhXFafvJ9G6iKAp9Sgncj5ryLB1OnCniSYyQjZh13nuNtuILNf8oUfCGIpIYG@vger.kernel.org, AJvYcCXFoVF/jBotzLYSS/Il55h/WAU5yMc0cOrHszlL4dUp60iKVl9qMGUJ7xNW7KVNDm3EDml9wJL+pnr8@vger.kernel.org, AJvYcCXG+I5ySIcF4X/qe2vyw7JoG8M7XPSopHj+cGNtaa3gyA1uO55t0kC0SCxc5EJRMhQW1CIUTfWpM653@vger.kernel.org, AJvYcCXH44JQtZixkbRgUgqtLnjk0+kb2AZyPwf7OaAfoldxefFKCM5nvFwjuJEgMpa+3EJrsaQKpd0kTQ1CwjQ0fR8=@vger.kernel.org X-Gm-Message-State: AOJu0YwasvUfbTmblZbuYI0pLcvEZMVXb+RM8WC7IjYrOrNr80EjGWZg V2FORxqRhGjb+YlwoE9jKLs/toEPRF7I7mzUqM0HA0ZZ4xIXfWkX X-Google-Smtp-Source: AGHT+IEofYiLx2/5wMR+sM1290hRfxy8vnOc+kD+ZzgBkjVwn9H+NbPR1DcIAyqiApHCWTBPsWel6A== X-Received: by 2002:a17:902:e74c:b0:20b:8109:2c90 with SMTP id d9443c01a7336-20fab2f5bb8mr57543295ad.61.1729760417186; Thu, 24 Oct 2024 02:00:17 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.02.00.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:16 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 7/9] iio: adc: Add Nuvoton NCT6694 IIO support Date: Thu, 24 Oct 2024 16:59:20 +0800 Message-Id: <20241024085922.133071-8-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports IIO functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/nct6694_adc.c | 616 ++++++++++++++++++++++++++++++++++ 4 files changed, 628 insertions(+) create mode 100644 drivers/iio/adc/nct6694_adc.c diff --git a/MAINTAINERS b/MAINTAINERS index 2aa87ad84156..5c350eac187d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16440,6 +16440,7 @@ L: linux-kernel@vger.kernel.org S: Supported F: drivers/gpio/gpio-nct6694.c F: drivers/hwmon/nct6694-hwmon.c +F: drivers/iio/adc/nct6694_adc.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/nct6694_canfd.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 6c4e74420fd2..302511d166db 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1018,6 +1018,16 @@ config NPCM_ADC This driver can also be built as a module. If so, the module will be called npcm_adc. +config NCT6694_ADC + tristate "Nuvoton NCT6694 ADC driver" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to ADC. + + This driver can also be built as a module. If so, the module + will be called nct6694_adc. + config PAC1921 tristate "Microchip Technology PAC1921 driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7b91cd98c0e0..db419f77365c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o +obj-$(CONFIG_NCT6694_ADC) += nct6694_adc.o obj-$(CONFIG_PAC1921) += pac1921.o obj-$(CONFIG_PAC1934) += pac1934.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o diff --git a/drivers/iio/adc/nct6694_adc.c b/drivers/iio/adc/nct6694_adc.c new file mode 100644 index 000000000000..de4cddc8addc --- /dev/null +++ b/drivers/iio/adc/nct6694_adc.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 IIO driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-iio" + +/* Host interface */ +#define REQUEST_RPT_MOD 0xFF +#define REQUEST_IIO_MOD 0x00 + +/* Report Channel */ +#define IIO_VIN_STS(x) (0x68 + (x)) +#define IIO_TMP_STS(x) (0x6A + (x)) +#define IIO_TMP_STS_CH(x) \ + ({ typeof(x) x_ = x; \ + (x_ < 10) ? x_ : x_ + 6; }) + +/* Message Channel*/ +/* Command 00h */ +#define REQUEST_IIO_CMD0_LEN 0x40 +#define REQUEST_IIO_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define IIO_VIN_EN(x) (0x00 + (x)) +#define IIO_TMP_EN(x) (0x02 + (x)) +/* Command 02h */ +#define REQUEST_IIO_CMD2_LEN 0x90 +#define REQUEST_IIO_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */ +#define IIO_SMI_CTRL_IDX 0x00 +#define IIO_VIN_LIMIT_IDX(x) (0x10 + ((x) * 2)) +#define IIO_TMP_LIMIT_IDX(x) (0x30 + ((x) * 2)) +#define IIO_CMD2_HYST_MASK 0x1F +/* Command 03h */ +#define REQUEST_IIO_CMD3_LEN 0x08 +#define REQUEST_IIO_CMD3_OFFSET 0x0003 /* OFFSET = SEL|CMD */ + +struct nct6694_iio_data { + struct nct6694 *nct6694; + + /* Make sure read & write commands are consecutive */ + struct mutex iio_lock; +}; + +static const struct iio_event_spec nct6694_volt_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_event_spec nct6694_temp_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +#define NCT6694_VOLTAGE_CHANNEL(num, addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .event_spec = nct6694_volt_events, \ + .num_event_specs = ARRAY_SIZE(nct6694_volt_events), \ +} + +#define NCT6694_TEMPERATURE_CHANNEL(num, addr) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = num, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_HYSTERESIS), \ + .event_spec = nct6694_temp_events, \ + .num_event_specs = ARRAY_SIZE(nct6694_temp_events), \ +} + +static const struct iio_chan_spec nct6694_iio_channels[] = { + NCT6694_VOLTAGE_CHANNEL(0, 0x0), /* VIN0 */ + NCT6694_VOLTAGE_CHANNEL(1, 0x1), /* VIN1 */ + NCT6694_VOLTAGE_CHANNEL(2, 0x2), /* VIN2 */ + NCT6694_VOLTAGE_CHANNEL(3, 0x3), /* VIN3 */ + NCT6694_VOLTAGE_CHANNEL(4, 0x4), /* VIN5 */ + NCT6694_VOLTAGE_CHANNEL(5, 0x5), /* VIN6 */ + NCT6694_VOLTAGE_CHANNEL(6, 0x6), /* VIN7 */ + NCT6694_VOLTAGE_CHANNEL(7, 0x7), /* VIN14 */ + NCT6694_VOLTAGE_CHANNEL(8, 0x8), /* VIN15 */ + NCT6694_VOLTAGE_CHANNEL(9, 0x9), /* VIN16 */ + NCT6694_VOLTAGE_CHANNEL(10, 0xA), /* VBAT */ + NCT6694_VOLTAGE_CHANNEL(11, 0xB), /* VSB */ + NCT6694_VOLTAGE_CHANNEL(12, 0xC), /* AVSB */ + NCT6694_VOLTAGE_CHANNEL(13, 0xD), /* VCC */ + NCT6694_VOLTAGE_CHANNEL(14, 0xE), /* VHIF */ + NCT6694_VOLTAGE_CHANNEL(15, 0xF), /* VTT */ + + NCT6694_TEMPERATURE_CHANNEL(0, 0x10), /* THR1 */ + NCT6694_TEMPERATURE_CHANNEL(1, 0x12), /* THR2 */ + NCT6694_TEMPERATURE_CHANNEL(2, 0x14), /* THR14 */ + NCT6694_TEMPERATURE_CHANNEL(3, 0x16), /* THR15 */ + NCT6694_TEMPERATURE_CHANNEL(4, 0x18), /* THR16 */ + NCT6694_TEMPERATURE_CHANNEL(5, 0x1A), /* TDP0 */ + NCT6694_TEMPERATURE_CHANNEL(6, 0x1C), /* TDP1 */ + NCT6694_TEMPERATURE_CHANNEL(7, 0x1E), /* TDP2 */ + NCT6694_TEMPERATURE_CHANNEL(8, 0x20), /* TDP3 */ + NCT6694_TEMPERATURE_CHANNEL(9, 0x22), /* TDP4 */ + + NCT6694_TEMPERATURE_CHANNEL(10, 0x30), /* DTIN0 */ + NCT6694_TEMPERATURE_CHANNEL(11, 0x32), /* DTIN1 */ + NCT6694_TEMPERATURE_CHANNEL(12, 0x34), /* DTIN2 */ + NCT6694_TEMPERATURE_CHANNEL(13, 0x36), /* DTIN3 */ + NCT6694_TEMPERATURE_CHANNEL(14, 0x38), /* DTIN4 */ + NCT6694_TEMPERATURE_CHANNEL(15, 0x3A), /* DTIN5 */ + NCT6694_TEMPERATURE_CHANNEL(16, 0x3C), /* DTIN6 */ + NCT6694_TEMPERATURE_CHANNEL(17, 0x3E), /* DTIN7 */ + NCT6694_TEMPERATURE_CHANNEL(18, 0x40), /* DTIN8 */ + NCT6694_TEMPERATURE_CHANNEL(19, 0x42), /* DTIN9 */ + NCT6694_TEMPERATURE_CHANNEL(20, 0x44), /* DTIN10 */ + NCT6694_TEMPERATURE_CHANNEL(21, 0x46), /* DTIN11 */ + NCT6694_TEMPERATURE_CHANNEL(22, 0x48), /* DTIN12 */ + NCT6694_TEMPERATURE_CHANNEL(23, 0x4A), /* DTIN13 */ + NCT6694_TEMPERATURE_CHANNEL(24, 0x4C), /* DTIN14 */ + NCT6694_TEMPERATURE_CHANNEL(25, 0x4E), /* DTIN15 */ +}; + +static int nct6694_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct nct6694_iio_data *data = iio_priv(indio_dev); + unsigned char buf[2], tmp_hyst, enable_idx; + int ret; + + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EOPNOTSUPP; + + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_VOLTAGE: + enable_idx = IIO_VIN_EN(chan->channel / 8); + break; + + case IIO_TEMP: + enable_idx = IIO_TMP_EN(chan->channel / 8); + break; + + default: + return -EINVAL; + } + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD0_OFFSET, + REQUEST_IIO_CMD0_LEN, enable_idx, + 1, buf); + if (ret) + return -EINVAL; + + *val = buf[0] & BIT(chan->channel % 8) ? 1 : 0; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + chan->address, 2, 0, 2, buf); + if (ret) + return -EINVAL; + + switch (chan->type) { + case IIO_VOLTAGE: /* in micro Voltage */ + *val = buf[0] * 16; + + return IIO_VAL_INT; + + case IIO_TEMP: /* in milli degrees Celsius */ + *val = (signed char)buf[0] * 1000; + *val += buf[1] & 0x80 ? 500 : 0; + *val += buf[1] & 0x40 ? 250 : 0; + *val += buf[1] & 0x20 ? 125 : 0; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_HYSTERESIS: + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, + IIO_TMP_LIMIT_IDX(chan->channel), + 2, buf); + if (ret) + return -EINVAL; + + switch (chan->type) { + case IIO_TEMP: /* in milli degrees Celsius */ + tmp_hyst = buf[0] >> 5; + *val = (buf[1] - tmp_hyst) * 1000; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int nct6694_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nct6694_iio_data *data = iio_priv(indio_dev); + unsigned char enable_buf[REQUEST_IIO_CMD0_LEN] = {0}; + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0}; + unsigned char delta_hyst; + int ret; + + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EOPNOTSUPP; + + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + mutex_lock(&data->iio_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD0_OFFSET, + REQUEST_IIO_CMD0_LEN, 0, + REQUEST_IIO_CMD0_LEN, + enable_buf); + if (ret) + goto err; + + switch (chan->type) { + case IIO_VOLTAGE: + if (val) { + enable_buf[IIO_VIN_EN(chan->channel / 8)] |= + BIT(chan->channel % 8); + } else { + enable_buf[IIO_VIN_EN(chan->channel / 8)] &= + ~BIT(chan->channel % 8); + } + + break; + + case IIO_TEMP: + if (val) { + enable_buf[IIO_TMP_EN(chan->channel / 8)] |= + BIT(chan->channel % 8); + } else { + enable_buf[IIO_TMP_EN(chan->channel / 8)] &= + ~BIT(chan->channel % 8); + } + + break; + + default: + ret = -EINVAL; + goto err; + } + + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD0_OFFSET, + REQUEST_IIO_CMD0_LEN, enable_buf); + if (ret) + goto err; + + break; + + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_TEMP: + mutex_lock(&data->iio_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, 0, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + + delta_hyst = buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] - (u8)val; + if (delta_hyst > 7) { + pr_err("%s: The Hysteresis value must be less than 7!\n", + __func__); + ret = -EINVAL; + goto err; + } + + buf[IIO_TMP_LIMIT_IDX(chan->channel)] &= IIO_CMD2_HYST_MASK; + buf[IIO_TMP_LIMIT_IDX(chan->channel)] |= (delta_hyst << 5); + break; + + default: + ret = -EINVAL; + goto err; + } + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + + break; + + default: + ret = -EINVAL; + goto err; + } + +err: + mutex_unlock(&data->iio_lock); + return ret; +} + +static int nct6694_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct nct6694_iio_data *data = iio_priv(indio_dev); + unsigned char buf, ch; + int ret; + + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EOPNOTSUPP; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_VOLTAGE: + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + IIO_VIN_STS(chan->channel / 8), + 1, 0, 1, &buf); + if (ret) + return -EINVAL; + + return !!(buf & BIT(chan->channel % 8)); + + case IIO_TEMP: + ch = (u8)IIO_TMP_STS_CH(chan->channel); + + ret = nct6694_read_msg(data->nct6694, REQUEST_RPT_MOD, + IIO_TMP_STS(ch / 8), + 1, 0, 1, &buf); + if (ret) + return -EINVAL; + + return !!(buf & BIT(ch % 8)); + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } + + return 0; +} + +static int nct6694_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct nct6694_iio_data *data = iio_priv(indio_dev); + unsigned char buf[2]; + int ret; + + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EOPNOTSUPP; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_VOLTAGE: + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, + IIO_VIN_LIMIT_IDX(chan->channel), + 2, buf); + if (ret) + return -EINVAL; + + *val = buf[0] * 16; + return IIO_VAL_INT; + + case IIO_TEMP: + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, + IIO_TMP_LIMIT_IDX(chan->channel), + 2, buf); + if (ret) + return -EINVAL; + + *val = (signed char)buf[1] * 1000; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_VOLTAGE: + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, + IIO_VIN_LIMIT_IDX(chan->channel), + 2, buf); + if (ret) + return -EINVAL; + + *val = buf[1] * 16; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int nct6694_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct nct6694_iio_data *data = iio_priv(indio_dev); + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0}; + int ret; + + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EOPNOTSUPP; + + mutex_lock(&data->iio_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, 0, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_VOLTAGE: + buf[IIO_VIN_LIMIT_IDX(chan->channel)] = (u8)val; + break; + + case IIO_TEMP: + buf[IIO_TMP_LIMIT_IDX(chan->channel) + 1] = (s8)val; + break; + + default: + ret = -EINVAL; + goto err; + } + break; + + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_VOLTAGE: + buf[IIO_VIN_LIMIT_IDX(chan->channel) + 1] = (u8)val; + break; + + default: + ret = -EINVAL; + goto err; + } + break; + + default: + ret = -EINVAL; + goto err; + } + + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + +err: + mutex_unlock(&data->iio_lock); + return ret; +} + +static const struct iio_info nct6694_iio_info = { + .read_raw = nct6694_read_raw, + .write_raw = nct6694_write_raw, + .read_event_config = nct6694_read_event_config, + .read_event_value = nct6694_read_event_value, + .write_event_value = nct6694_write_event_value, +}; + +static int nct6694_iio_init(struct nct6694_iio_data *data) +{ + unsigned char buf[REQUEST_IIO_CMD2_LEN] = {0}; + int ret; + + /* Set VIN & TMP Real Time alarm mode */ + mutex_lock(&data->iio_lock); + ret = nct6694_read_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, 0, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + + buf[IIO_SMI_CTRL_IDX] = 0x02; + ret = nct6694_write_msg(data->nct6694, REQUEST_IIO_MOD, + REQUEST_IIO_CMD2_OFFSET, + REQUEST_IIO_CMD2_LEN, buf); + if (ret) + goto err; + +err: + mutex_unlock(&data->iio_lock); + return ret; +} + +static int nct6694_iio_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct nct6694_iio_data *data; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->nct6694 = nct6694; + mutex_init(&data->iio_lock); + platform_set_drvdata(pdev, data); + + ret = nct6694_iio_init(data); + if (ret) + return -EIO; + + indio_dev->name = pdev->name; + indio_dev->channels = nct6694_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(nct6694_iio_channels); + indio_dev->info = &nct6694_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Register iio device to IIO framework */ + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register iio device!\n"); + return ret; + } + + return 0; +} + +static struct platform_driver nct6694_iio_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_iio_probe, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_iio_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_iio_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_iio_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-IIO driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848564 Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) (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 6B9801B4F0C; Thu, 24 Oct 2024 09:00:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760425; cv=none; b=ar8nnRevKma+NtfU/6QeJ10HmkibaV8kP5SgJamLPIZFRBEnGzfD33n6S480pSIf51cRwQPqGFaE3hG+gMQ7lfjyl76oL65HzV5bQd/GunsyaiP8qC7RusiFGaPhAhVof3G1Cz2zh87TWs6VGPeRLvQqJX9J7QEw2sgtYwJ37S0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760425; c=relaxed/simple; bh=UnhLaJ0JL15jAgZ553cxcb3YFcwd+/F1Mzjh+QcL+pY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=WV9ycxlzd1wMtzBnIi0boAtDg6qCQ6ROMg7rzRNVp7NJGNoRdnZ/YCCf94zcJ4WcU4ALDcYvwC/Q6n/7fL+lyGxQsx4Plr18R+JWpw4yNIa9tPQsf36gzo6pKH+eVHuGqTqHMlXFoToWabtQ7RcFGcDiL0ugmTNUav6xrMzQb5Q= 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=LuxOkdag; arc=none smtp.client-ip=209.85.214.169 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="LuxOkdag" Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-20c805a0753so5795665ad.0; Thu, 24 Oct 2024 02:00:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760422; x=1730365222; 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=KIbqZDJc8mnLl8JM1vRVacj6/HhHdNNqQLheCYEGuDY=; b=LuxOkdagkHRp3pKiITdvYfrPEFLzl3Yv511gQTpOlXQxZsd4ekN+KkHylrZq9nfc8b OVyEur/xo4jM5nqCuu04QyaD/4Sk8amLx/71/VX6GMbdZaj7cKuErfMKfbSp5qpUYdrA 7F1PY/12Advxg3KueXMXk3yJyIgXYiaZ7HIesgwgGgrBl7XAOmwYyhN0W/HrSDbwuPTu MFaStE7DGf7D1oJfH/GKKZzlmiBZhcXPyJ52yD3aUy0pwSL1i2Sw9Tjr3JBY+cd7Spgx AobYDdbdnmn1PsKuVcDrOGsSJh9HTDbj2yvz2RZHxhZL0reeyY4U4VhkbyLfnYQtPd9u 98dg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760422; x=1730365222; 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=KIbqZDJc8mnLl8JM1vRVacj6/HhHdNNqQLheCYEGuDY=; b=omO28SLM5636f6Xzs/flbRIMh2M+FRVhxnK2cKsycOxBK8YjMS2hcwgJoRBPXsZhPe 6XvL8y1//kcueuktJpUhCJUWva+tfJrkbdyeTYiZECpFxrzVhN+9wA5I5oyVIVeVIeoU 8YX8wokJwK9DTmXkJOsWsvgpWggJEWvUdjWLykc4rkmRNnu7iEA5mxypEu2YrYmIW1Hr uRSJsuqc3ww3XO0XaeE/DR4Wyh9NDavvnv3BsgvKgwRJq3rytXfdz90bJLulrcyw542O gAl204MaASqvF7CGapEJAhOD22VaCUssjqdp7Y31otORnS/FkY5Do6WI+oNAFUh0zu9O 2plQ== X-Forwarded-Encrypted: i=1; AJvYcCV3bEEEMKWCltExC3E297Dulzr/ymLTEcH+u1DOxRN3NWt9C3wA1k5v8PKVwMSeudz8YhGc5OXGvsma@vger.kernel.org, AJvYcCVgZnQa9MLM/gfo9xvUVckAKlcffOcFjsu4rLhSwhfybqrMXInNrwbGaFxkrthGxsLU3hXjb8keVEF1@vger.kernel.org, AJvYcCVvIf9gDogqm64/oj+3j7/asHCDSlvEJrOxpbZehr3uPBwzfPCS7/Q4oCTnJelSwZCpIlzAqOQRQIQo@vger.kernel.org, AJvYcCW9ybUoQ/v4+7G/zBE5vJpch5U2Rd0jsXehmM5MZSKCu5rKFqrhG9k6nS6ZzEF0fIYUDz2wh0oLdi0zMA==@vger.kernel.org, AJvYcCWKAfgIoSTPNAym5jKOHxguwqUWA2CLKP/a5w34FwvXVFiTxpdKzKA3D/xCU6it0+wALdURwc88@vger.kernel.org, AJvYcCWMZz1bzv1Z1b4l5RCKkITAJo5/SQ7O4EYns653miZURD7MnqSKjA/BrWT3IPenYcYVS9CC8grT7HLUlCnISEA=@vger.kernel.org, AJvYcCWUbn3bjchTVkLMPN9oiJW/QQ97Z5I8QgBaL4l8Yu/tXbBbkoMm27fAuyJParuz0kdqCxvKJpTLJcwY@vger.kernel.org, AJvYcCX5WLopbebuPQ+yRLHa6nWLK7bLA/ZH3cN0CAXHB3nFVPtaKqHIJG9nULGFnIwCNVRen94pIOqwUHU=@vger.kernel.org, AJvYcCX8gRq92AnAGh8JdjbcX8d9lUK2ywzne1QRRVmPLVCU4jfzoVUYGoxNOCKwY8GGi5MTcHRAni8DqvFVWcA=@vger.kernel.org X-Gm-Message-State: AOJu0YwhX4OKri3L45Iq6OOHTDuMgOPmKopJVdLjgVHmos01f8Uhk836 rznVQu97KKScel9J/DqhB/fgbn6ifNEF/pQwPLsoNsfNMfBRXa+C X-Google-Smtp-Source: AGHT+IHfU5bGuzEbC6pA7wiKRq0B2bksWNaijYHyEsR3X33rOKlukYQey29WPzYg3brlpUWbKJ1bTQ== X-Received: by 2002:a17:902:fc4f:b0:20b:bad4:5b6e with SMTP id d9443c01a7336-20fb9aa038fmr15925915ad.38.1729760421780; Thu, 24 Oct 2024 02:00:21 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.02.00.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:21 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 8/9] pwm: Add Nuvoton NCT6694 PWM support Date: Thu, 24 Oct 2024 16:59:21 +0800 Message-Id: <20241024085922.133071-9-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports PWM functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-nct6694.c | 245 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 drivers/pwm/pwm-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index 5c350eac187d..4d5a5eded3b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16444,6 +16444,7 @@ F: drivers/iio/adc/nct6694_adc.c F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/nct6694_canfd.c +F: drivers/pwm/pwm-nct6694.c F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0915c1e7df16..00b5eb13f99d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -471,6 +471,16 @@ config PWM_NTXEC controller found in certain e-book readers designed by the original design manufacturer Netronix. +config PWM_NCT6694 + tristate "Nuvoton NCT6694 PWM support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to PWM controller. + + This driver can also be built as a module. If so, the module + will be called pwm-nct6694. + config PWM_OMAP_DMTIMER tristate "OMAP Dual-Mode Timer PWM support" depends on OF diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9081e0c0e9e0..5c5602b79402 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o +obj-$(CONFIG_PWM_NCT6694) += pwm-nct6694.o obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o diff --git a/drivers/pwm/pwm-nct6694.c b/drivers/pwm/pwm-nct6694.c new file mode 100644 index 000000000000..915a2ab50834 --- /dev/null +++ b/drivers/pwm/pwm-nct6694.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 PWM driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-pwm" + +#define NR_PWM 10 +#define MAX_PERIOD_NS 40000 /* PWM Maximum Frequency = 25kHz */ +#define PERIOD_NS_CONST 10200000 /* Period_ns to Freq_reg */ + +/* Host interface */ +#define REQUEST_RPT_MOD 0xFF +#define REQUEST_HWMON_MOD 0x00 +#define REQUEST_PWM_MOD 0x01 + +/* Report Channel */ +#define HWMON_PWM_IDX(x) (0x70 + (x)) + +/* Message Channel -HWMON */ +/* Command 00h */ +#define REQUEST_HWMON_CMD0_LEN 0x40 +#define REQUEST_HWMON_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define HWMON_PWM_EN(x) (0x06 + (x)) +#define HWMON_PWM_PP(x) (0x08 + (x)) +#define HWMON_PWM_FREQ_IDX(x) (0x30 + (x)) + +/* Message Channel -FAN */ +/* Command 00h */ +#define REQUEST_PWM_CMD0_LEN 0x08 +#define REQUEST_PWM_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define PWM_CH_EN(x) (x ? 0x00 : 0x01) +/* Command 01h */ +#define REQUEST_PWM_CMD1_LEN 0x20 +#define REQUEST_PWM_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */ +#define PWM_MAL_EN(x) (x ? 0x00 : 0x01) +#define PWM_MAL_VAL(x) (0x02 + (x)) + +/* + * Frequency <-> Period + * (10^9 * 255) / (25000 * Freq_reg) = Period_ns + * 10200000 / Freq_reg = Period_ns + * + * | Freq_reg | Freq_Hz | Period_ns | + * | 1 (01h | 98.039 | 10200000 | + * | 2 (02h) | 196.078 | 5100000 | + * | 3 (03h) | 294.117 | 3400000 | + * | ... | + * | ... | + * | ... | + * | 253 (FDh)| 24803.9 | 40316.20 | + * | 254 (FEh)| 24901.9 | 40157.48 | + * | 255 (FFh)| 25000 | 40000 | + * + */ + +struct nct6694_pwm_data { + struct nct6694 *nct6694; + unsigned char hwmon_cmd0_buf[REQUEST_HWMON_CMD0_LEN]; + unsigned char pwm_cmd0_buf[REQUEST_PWM_CMD0_LEN]; + unsigned char pwm_cmd1_buf[REQUEST_PWM_CMD1_LEN]; +}; + +static inline struct nct6694_pwm_data *to_nct6694_pwm_data(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int nct6694_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip); + unsigned char ch_enable = data->pwm_cmd0_buf[PWM_CH_EN(pwm->hwpwm / 8)]; + unsigned char mal_enable = data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)]; + bool ch_en = ch_enable & BIT(pwm->hwpwm % 8); + bool mal_en = mal_enable & BIT(pwm->hwpwm % 8); + + if (!(ch_en && mal_en)) { + pr_err("%s: PWM(%d) is running in other mode!\n", + __func__, pwm->hwpwm); + return -EINVAL; + } + + return 0; +} + +static int nct6694_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip); + unsigned char freq_reg, duty; + + /* Get pwm device initial state */ + state->enabled = true; + + freq_reg = data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)]; + state->period = PERIOD_NS_CONST / freq_reg; + + duty = data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)]; + state->duty_cycle = duty * state->period / 0xFF; + + return 0; +} + +static int nct6694_pwm_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct nct6694_pwm_data *data = to_nct6694_pwm_data(chip); + unsigned char freq_reg, duty; + int ret; + + if (state->period < MAX_PERIOD_NS) + return -EINVAL; + + /* (10^9 * 255) / (25000 * Freq_reg) = Period_ns */ + freq_reg = (unsigned char)(PERIOD_NS_CONST / state->period); + data->hwmon_cmd0_buf[HWMON_PWM_FREQ_IDX(pwm->hwpwm)] = freq_reg; + ret = nct6694_write_msg(data->nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, + data->hwmon_cmd0_buf); + if (ret) + return -EIO; + + /* Duty = duty * 0xFF */ + duty = (unsigned char)(state->duty_cycle * 0xFF / state->period); + data->pwm_cmd1_buf[PWM_MAL_VAL(pwm->hwpwm)] = duty; + if (state->enabled) + data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] |= BIT(pwm->hwpwm % 8); + else + data->pwm_cmd1_buf[PWM_MAL_EN(pwm->hwpwm / 8)] &= ~BIT(pwm->hwpwm % 8); + ret = nct6694_write_msg(data->nct6694, REQUEST_PWM_MOD, + REQUEST_PWM_CMD1_OFFSET, REQUEST_PWM_CMD1_LEN, + data->pwm_cmd1_buf); + if (ret) + return -EIO; + + return 0; +} + +static const struct pwm_ops nct6694_pwm_ops = { + .request = nct6694_pwm_request, + .apply = nct6694_pwm_apply, + .get_state = nct6694_pwm_get_state, +}; + +static int nct6694_pwm_init(struct nct6694_pwm_data *data) +{ + struct nct6694 *nct6694 = data->nct6694; + int ret; + + ret = nct6694_read_msg(nct6694, REQUEST_HWMON_MOD, + REQUEST_HWMON_CMD0_OFFSET, + REQUEST_HWMON_CMD0_LEN, 0, + REQUEST_HWMON_CMD0_LEN, + data->hwmon_cmd0_buf); + if (ret) + return ret; + + ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD, + REQUEST_PWM_CMD0_OFFSET, + REQUEST_PWM_CMD0_LEN, 0, + REQUEST_PWM_CMD0_LEN, + data->pwm_cmd0_buf); + if (ret) + return ret; + + ret = nct6694_read_msg(nct6694, REQUEST_PWM_MOD, + REQUEST_PWM_CMD1_OFFSET, + REQUEST_PWM_CMD1_LEN, 0, + REQUEST_PWM_CMD1_LEN, + data->pwm_cmd1_buf); + return ret; +} + +static int nct6694_pwm_probe(struct platform_device *pdev) +{ + struct pwm_chip *chip; + struct nct6694_pwm_data *data; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + int ret; + + chip = devm_pwmchip_alloc(&pdev->dev, NR_PWM, sizeof(*data)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + data = to_nct6694_pwm_data(chip); + + data->nct6694 = nct6694; + chip->ops = &nct6694_pwm_ops; + + ret = nct6694_pwm_init(data); + if (ret) + return -EIO; + + /* Register pwm device to PWM framework */ + ret = devm_pwmchip_add(&pdev->dev, chip); + if (ret) { + dev_err(&pdev->dev, "Failed to register pwm device!\n"); + return ret; + } + + return 0; +} + +static struct platform_driver nct6694_pwm_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_pwm_probe, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_pwm_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_pwm_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_pwm_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-PWM driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL"); From patchwork Thu Oct 24 08:59:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ming Yu X-Patchwork-Id: 13848565 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 4BAA21B4F3D; Thu, 24 Oct 2024 09:00:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760430; cv=none; b=s1bOx/kZGksIEQcRXgKyeJpgbID15fFhMpI38qx59dzfNo36UT7T1/Wp0YdT35A5BI7hOK2tb+hNbKfNoLm88koSXL8u7sL3s/R1bRyrmxoNTEo226/q94U2bPH7s2xdGNnZeq9ZA70rX0t87Yvu0iXubyxOTWr1qoq/1VMpK4U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1729760430; c=relaxed/simple; bh=zHqxB7fmBbca/3/uhh+sdA/oXZ4DFNAbHKvHPJIehv8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=RTB+C4BkwYiieCXfzgGilFj6Ns9L1GXMj4UWLrVtQ+4IvFnVnuibWgNYPPtVqPR/zpIzFhUTg+66OCq3Ey+wh3e7YadJSuMHbOEe4hhYfYgaGh7X+ivKb0zySrkFTQTMFciHiTLF/V+TLULF9JtZE+RI3ZVfBea49a5YgKRzoS4= 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=QQ/GYbx/; arc=none smtp.client-ip=209.85.214.176 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="QQ/GYbx/" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-20c803787abso5231765ad.0; Thu, 24 Oct 2024 02:00:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729760426; x=1730365226; 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=2CuRkBznOtCHHxZzKq/9IVvhKUdKupfyQysLLGavF5I=; b=QQ/GYbx/LpvR2YILsaPm1l/27LhUV7S1qpVXU1vBdrX/89haRzJglwvTQ3ndYT5Ogf NVLuR7n3u0vU7Bvd8ZXMDf+tWlld+DslD4hTCZL7vQ65T/TOQbbhinPIsAI4aHVSfJIK 26oOCWWesnt881t3OKVuH+9J9ysptsePMgZJ8t48NNlZjJhqIO6Z6ITK6qZAHw7avedC 5tNK4wooVGVFb4WPPE24UqbCK3ikz+Aga9C5aL855aEUzAPTF/eBW8SUNa6gIhrXlig/ 37L9wbhgK5uoW9HLSp8SRciPTDgmp+VF2+RUK5OdPgnTH705mkclEH1mbMeb4wuT+PwH gf5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729760426; x=1730365226; 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=2CuRkBznOtCHHxZzKq/9IVvhKUdKupfyQysLLGavF5I=; b=WmUc+HLGx9xARK+9B67p0zdcxgWULViQ0cPVGp2YcpQoFVrxXDcjyCAhz/qmKwdMth vkZZJmePz3yEG9jYcHPIIx50H1HjinF8zNOvbPLP/0wfUOfsKcxDgTOKNYBQu47o7f8R IUJvdEwXxn+WnQV4jbCg8fC2/cCj0EwcF3c9XgltBlxNMb5NkZABX+QwLBSqOkgmv+RW whscymiXRpA0xGR2cQ+2T/N0Ccozk0ARjwZsK7a/WNmvYvXEF9lVLUbRKZiTp6g9nJpe wTH8SmZxavk1/jjolsjbuEYCmU2zm8jjMPgdvx0ydQonbwuiCWIxZq5MsK0g5vJk+jmt 07iw== X-Forwarded-Encrypted: i=1; AJvYcCUYkrI2vWdEpkslXiibV8LEOcm+UQcJD7OPqyP1PdQzQ6AhwhB/5ML59rETsruEI/ANU7BUsh9pnPLTNQk=@vger.kernel.org, AJvYcCUr/1TxJsLY8TW4lSCDtZtAqHKsExqyo4eArlxiI95EnjP7usu174BylDwIUADaOZDmulbV6Z+gWfyw@vger.kernel.org, AJvYcCVJgaKxK+ztTI1y0wVvFwkKYs7EzDkWATEfqALeVFYa+sOPMywKxSxc7dtVUl7IlrnAtOzhAJx+3WQ=@vger.kernel.org, AJvYcCW0R+ViVymcTLMMPkMkEc+EBVwd5vdI5VLlmwEmbeIL+9gEyXnxPAx9Ra/xmgJ8JwVzIhFLfftRFRKs@vger.kernel.org, AJvYcCW5ECR+3VGn31TGzaBYTjiZCsX5IZ/gxUu7yCjE90LhdsLgWgd2wK80PiVPQ1iEMTolXAIVaMpCkJjRYw==@vger.kernel.org, AJvYcCXHYYybyBn3XGjkQHzt27f9lPKBTPiM9Rsn0s7gkt9ZrpnZlmcSf+5NxC9xtMISDcf0+t6yq6tj31OfO2zFhoo=@vger.kernel.org, AJvYcCXZpcVHMZlUnMukbga0AknJKy7Adjyf+x8uwOeZwy7xZ0Acd62KKN7Gop3dFJ9iUddT94Ickx5A@vger.kernel.org, AJvYcCXdxS/awQtq+l04UvK80Sdiy8zrjwNcpO0sZ7eVdHVTbWHTPodFdF3tQAX7a6A16TCPQtsA0OtF1wVI@vger.kernel.org, AJvYcCXoMQKQM9CW6X763/IoGP72AkJUlEY/2wiI8sfwsttn3tjskzjxpVOHhHKCN++Y9KPq5hg3QX2fotbL@vger.kernel.org X-Gm-Message-State: AOJu0Yy4355rbf59QBq6iPAfbp6HCk1GOphbGGx2JCbsRvjQKF2oFRMw 2+c6aUtNhY+nXAj4V0IuILJ5kFa59jq8MlyAwRjbLMHCNEF8W6wo X-Google-Smtp-Source: AGHT+IGm0bfEw6JC+/h3k0ziqfQnO701FaANVWXMniBwl81s116rE2DFv9I4iE6IYfeiqIdcjDUCGg== X-Received: by 2002:a17:903:32ce:b0:20c:5990:897c with SMTP id d9443c01a7336-20fb8a5b3c2mr20278635ad.27.1729760426485; Thu, 24 Oct 2024 02:00:26 -0700 (PDT) Received: from hcdev-d520mt2.. (60-250-192-107.hinet-ip.hinet.net. [60.250.192.107]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-20e7f0f6e89sm68503615ad.277.2024.10.24.02.00.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Oct 2024 02:00:26 -0700 (PDT) From: Ming Yu X-Google-Original-From: Ming Yu 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, jic23@kernel.org, lars@metafoo.de, ukleinek@kernel.org, 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-iio@vger.kernel.org, linux-pwm@vger.kernel.org, linux-rtc@vger.kernel.org Subject: [PATCH v1 9/9] rtc: Add Nuvoton NCT6694 RTC support Date: Thu, 24 Oct 2024 16:59:22 +0800 Message-Id: <20241024085922.133071-10-tmyu0@nuvoton.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20241024085922.133071-1-tmyu0@nuvoton.com> References: <20241024085922.133071-1-tmyu0@nuvoton.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver supports RTC functionality for NCT6694 MFD device based on USB interface. Signed-off-by: Ming Yu --- MAINTAINERS | 1 + drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-nct6694.c | 276 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 288 insertions(+) create mode 100644 drivers/rtc/rtc-nct6694.c diff --git a/MAINTAINERS b/MAINTAINERS index 4d5a5eded3b9..8de90bda8b5e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16445,6 +16445,7 @@ F: drivers/i2c/busses/i2c-nct6694.c F: drivers/mfd/nct6694.c F: drivers/net/can/nct6694_canfd.c F: drivers/pwm/pwm-nct6694.c +F: drivers/rtc/rtc-nct6694.c F: drivers/watchdog/nct6694_wdt.c F: include/linux/mfd/nct6694.h diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 66eb1122248b..240c496d95f7 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -406,6 +406,16 @@ config RTC_DRV_NCT3018Y This driver can also be built as a module, if so, the module will be called "rtc-nct3018y". +config RTC_DRV_NCT6694 + tristate "Nuvoton NCT6694 RTC support" + depends on MFD_NCT6694 + help + If you say yes to this option, support will be included for Nuvoton + NCT6694, a USB device to RTC. + + This driver can also be built as a module. If so, the module + will be called rtc-nct6694. + config RTC_DRV_RK808 tristate "Rockchip RK805/RK808/RK809/RK817/RK818 RTC" depends on MFD_RK8XX diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index f62340ecc534..64443d26bb5b 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -116,6 +116,7 @@ obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o obj-$(CONFIG_RTC_DRV_GAMECUBE) += rtc-gamecube.o obj-$(CONFIG_RTC_DRV_NCT3018Y) += rtc-nct3018y.o +obj-$(CONFIG_RTC_DRV_NCT6694) += rtc-nct6694.o obj-$(CONFIG_RTC_DRV_NTXEC) += rtc-ntxec.o obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o obj-$(CONFIG_RTC_DRV_OPAL) += rtc-opal.o diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c new file mode 100644 index 000000000000..622bb9fbe6f6 --- /dev/null +++ b/drivers/rtc/rtc-nct6694.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NCT6694 RTC driver based on USB interface. + * + * Copyright (C) 2024 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct6694-rtc" + +/* Host interface */ +#define REQUEST_RTC_MOD 0x08 + +/* Message Channel */ +/* Command 00h */ +#define REQUEST_RTC_CMD0_LEN 0x07 +#define REQUEST_RTC_CMD0_OFFSET 0x0000 /* OFFSET = SEL|CMD */ +#define RTC_SEC_IDX 0x00 +#define RTC_MIN_IDX 0x01 +#define RTC_HOUR_IDX 0x02 +#define RTC_WEEK_IDX 0x03 +#define RTC_DAY_IDX 0x04 +#define RTC_MONTH_IDX 0x05 +#define RTC_YEAR_IDX 0x06 +/* Command 01h */ +#define REQUEST_RTC_CMD1_LEN 0x05 +#define REQUEST_RTC_CMD1_OFFSET 0x0001 /* OFFSET = SEL|CMD */ +#define RTC_ALRM_EN_IDX 0x03 +#define RTC_ALRM_PEND_IDX 0x04 +/* Command 02h */ +#define REQUEST_RTC_CMD2_LEN 0x02 +#define REQUEST_RTC_CMD2_OFFSET 0x0002 /* OFFSET = SEL|CMD */ +#define RTC_IRQ_EN_IDX 0x00 +#define RTC_IRQ_PEND_IDX 0x01 + +#define RTC_IRQ_EN (BIT(0) | BIT(5)) +#define RTC_IRQ_INT_EN BIT(0) /* Transmit a USB INT-in when RTC alarm */ +#define RTC_IRQ_GPO_EN BIT(5) /* Trigger a GPO Low Pulse when RTC alarm */ +#define RTC_IRQ_STS BIT(0) /* Write 1 clear IRQ status */ + +struct nct6694_rtc_data { + struct nct6694 *nct6694; + struct rtc_device *rtc; + struct work_struct alarm_work; +}; + +static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + unsigned char buf[REQUEST_RTC_CMD0_LEN]; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN, + 0, REQUEST_RTC_CMD0_LEN, buf); + if (ret) { + pr_err("%s: Failed to get rtc device!\n", __func__); + return -EIO; + } + + tm->tm_sec = bcd2bin(buf[RTC_SEC_IDX]); /* tm_sec expect 0 ~ 59 */ + tm->tm_min = bcd2bin(buf[RTC_MIN_IDX]); /* tm_min expect 0 ~ 59 */ + tm->tm_hour = bcd2bin(buf[RTC_HOUR_IDX]); /* tm_hour expect 0 ~ 23 */ + tm->tm_wday = bcd2bin(buf[RTC_WEEK_IDX]) - 1; /* tm_wday expect 0 ~ 6 */ + tm->tm_mday = bcd2bin(buf[RTC_DAY_IDX]); /* tm_mday expect 1 ~ 31 */ + tm->tm_mon = bcd2bin(buf[RTC_MONTH_IDX]) - 1; /* tm_month expect 0 ~ 11 */ + tm->tm_year = bcd2bin(buf[RTC_YEAR_IDX]) + 100; /* tm_year expect since 1900 */ + + return ret; +} + +static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + unsigned char buf[REQUEST_RTC_CMD0_LEN]; + int ret; + + buf[RTC_SEC_IDX] = bin2bcd(tm->tm_sec); + buf[RTC_MIN_IDX] = bin2bcd(tm->tm_min); + buf[RTC_HOUR_IDX] = bin2bcd(tm->tm_hour); + buf[RTC_WEEK_IDX] = bin2bcd(tm->tm_wday + 1); + buf[RTC_DAY_IDX] = bin2bcd(tm->tm_mday); + buf[RTC_MONTH_IDX] = bin2bcd(tm->tm_mon + 1); + buf[RTC_YEAR_IDX] = bin2bcd(tm->tm_year - 100); + + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD0_OFFSET, REQUEST_RTC_CMD0_LEN, + buf); + if (ret) { + pr_err("%s: Failed to set rtc device!\n", __func__); + return -EIO; + } + + return ret; +} + +static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + unsigned char buf[REQUEST_RTC_CMD1_LEN]; + int ret; + + ret = nct6694_read_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN, + 0, REQUEST_RTC_CMD1_LEN, buf); + if (ret) { + pr_err("%s: Failed to get rtc device!\n", __func__); + return -EIO; + } + + alrm->time.tm_sec = bcd2bin(buf[RTC_SEC_IDX]); + alrm->time.tm_min = bcd2bin(buf[RTC_MIN_IDX]); + alrm->time.tm_hour = bcd2bin(buf[RTC_HOUR_IDX]); + + alrm->enabled = buf[RTC_ALRM_EN_IDX]; + alrm->pending = buf[RTC_ALRM_PEND_IDX]; + + return ret; +} + +static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + unsigned char buf[REQUEST_RTC_CMD1_LEN]; + int ret; + + buf[RTC_SEC_IDX] = bin2bcd(alrm->time.tm_sec); + buf[RTC_MIN_IDX] = bin2bcd(alrm->time.tm_min); + buf[RTC_HOUR_IDX] = bin2bcd(alrm->time.tm_hour); + buf[RTC_ALRM_EN_IDX] = alrm->enabled ? RTC_IRQ_EN : 0; + buf[RTC_ALRM_PEND_IDX] = 0; + + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD1_OFFSET, REQUEST_RTC_CMD1_LEN, + buf); + if (ret) { + pr_err("%s: Failed to set rtc device!\n", __func__); + return -EIO; + } + + return ret; +} + +static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct nct6694_rtc_data *data = dev_get_drvdata(dev); + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0}; + int ret; + + if (enabled) + buf[RTC_IRQ_EN_IDX] |= RTC_IRQ_EN; + else + buf[RTC_IRQ_EN_IDX] &= ~RTC_IRQ_EN; + + ret = nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD2_OFFSET, REQUEST_RTC_CMD2_LEN, + buf); + if (ret) { + pr_err("%s: Failed to set rtc device!\n", __func__); + return -EIO; + } + + return ret; +} + +static const struct rtc_class_ops nct6694_rtc_ops = { + .read_time = nct6694_rtc_read_time, + .set_time = nct6694_rtc_set_time, + .read_alarm = nct6694_rtc_read_alarm, + .set_alarm = nct6694_rtc_set_alarm, + .alarm_irq_enable = nct6694_rtc_alarm_irq_enable, +}; + +static void nct6694_rtc_alarm(struct work_struct *work) +{ + struct nct6694_rtc_data *data; + unsigned char buf[REQUEST_RTC_CMD2_LEN] = {0}; + + data = container_of(work, struct nct6694_rtc_data, alarm_work); + + pr_info("%s: Got RTC alarm!\n", __func__); + buf[RTC_IRQ_EN_IDX] = RTC_IRQ_EN; + buf[RTC_IRQ_PEND_IDX] = RTC_IRQ_STS; + nct6694_write_msg(data->nct6694, REQUEST_RTC_MOD, + REQUEST_RTC_CMD2_OFFSET, + REQUEST_RTC_CMD2_LEN, buf); +} + +static void nct6694_rtc_handler(void *private_data) +{ + struct nct6694_rtc_data *data = private_data; + struct nct6694 *nct6694 = data->nct6694; + + queue_work(nct6694->async_workqueue, &data->alarm_work); +} + +static int nct6694_rtc_probe(struct platform_device *pdev) +{ + struct nct6694_rtc_data *data; + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(data->rtc)) + return PTR_ERR(data->rtc); + + data->nct6694 = nct6694; + data->rtc->ops = &nct6694_rtc_ops; + data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; + data->rtc->range_max = RTC_TIMESTAMP_END_2099; + + INIT_WORK(&data->alarm_work, nct6694_rtc_alarm); + + ret = nct6694_register_handler(nct6694, RTC_IRQ_STATUS, + nct6694_rtc_handler, data); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to register handler: %pe\n", + __func__, ERR_PTR(ret)); + return ret; + } + + device_set_wakeup_capable(&pdev->dev, 1); + + platform_set_drvdata(pdev, data); + + /* Register rtc device to RTC framework */ + ret = devm_rtc_register_device(data->rtc); + if (ret) { + dev_err(&pdev->dev, "Failed to register rtc device!\n"); + return ret; + } + + return 0; +} + +static struct platform_driver nct6694_rtc_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = nct6694_rtc_probe, +}; + +static int __init nct6694_init(void) +{ + int err; + + err = platform_driver_register(&nct6694_rtc_driver); + if (!err) { + if (err) + platform_driver_unregister(&nct6694_rtc_driver); + } + + return err; +} +subsys_initcall(nct6694_init); + +static void __exit nct6694_exit(void) +{ + platform_driver_unregister(&nct6694_rtc_driver); +} +module_exit(nct6694_exit); + +MODULE_DESCRIPTION("USB-RTC driver for NCT6694"); +MODULE_AUTHOR("Ming Yu "); +MODULE_LICENSE("GPL");