Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

mfd: Add core driver for Nuvoton NCT6694

The Nuvoton NCT6694 provides an USB interface to the host to
access its features.

Sub-devices can use the USB functions nct6694_read_msg() and
nct6694_write_msg() to issue a command. They can also request
interrupt that will be called when the USB device receives its
interrupt pipe.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
Link: https://lore.kernel.org/r/20250912091952.1169369-2-a0282524688@gmail.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Ming Yu and committed by
Lee Jones
51dad33e 8f5ae30d

+513
+6
MAINTAINERS
··· 18082 18082 F: include/linux/nubus.h 18083 18083 F: include/uapi/linux/nubus.h 18084 18084 18085 + NUVOTON NCT6694 MFD DRIVER 18086 + M: Ming Yu <tmyu0@nuvoton.com> 18087 + S: Supported 18088 + F: drivers/mfd/nct6694.c 18089 + F: include/linux/mfd/nct6694.h 18090 + 18085 18091 NUVOTON NCT7201 IIO DRIVER 18086 18092 M: Eason Yang <j2anfernee@gmail.com> 18087 18093 L: linux-iio@vger.kernel.org
+15
drivers/mfd/Kconfig
··· 1134 1134 This driver can also be built as a module. If so the module 1135 1135 will be called menf21bmc. 1136 1136 1137 + config MFD_NCT6694 1138 + tristate "Nuvoton NCT6694 support" 1139 + select MFD_CORE 1140 + depends on USB 1141 + help 1142 + This enables support for the Nuvoton USB device NCT6694, which shares 1143 + peripherals. 1144 + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, 1145 + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, 1146 + PWM, and RTC. 1147 + This driver provides core APIs to access the NCT6694 hardware 1148 + monitoring and control features. 1149 + Additional drivers must be enabled to utilize the specific 1150 + functionalities of the device. 1151 + 1137 1152 config MFD_OCELOT 1138 1153 tristate "Microsemi Ocelot External Control Support" 1139 1154 depends on SPI_MASTER
+2
drivers/mfd/Makefile
··· 121 121 obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o 122 122 obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o 123 123 124 + obj-$(CONFIG_MFD_NCT6694) += nct6694.o 125 + 124 126 obj-$(CONFIG_MFD_CORE) += mfd-core.o 125 127 126 128 ocelot-soc-objs := ocelot-core.o ocelot-spi.o
+388
drivers/mfd/nct6694.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2025 Nuvoton Technology Corp. 4 + * 5 + * Nuvoton NCT6694 core driver using USB interface to provide 6 + * access to the NCT6694 hardware monitoring and control features. 7 + * 8 + * The NCT6694 is an integrated controller that provides GPIO, I2C, 9 + * CAN, WDT, HWMON and RTC management. 10 + */ 11 + 12 + #include <linux/bits.h> 13 + #include <linux/interrupt.h> 14 + #include <linux/idr.h> 15 + #include <linux/irq.h> 16 + #include <linux/irqdomain.h> 17 + #include <linux/kernel.h> 18 + #include <linux/mfd/core.h> 19 + #include <linux/mfd/nct6694.h> 20 + #include <linux/module.h> 21 + #include <linux/slab.h> 22 + #include <linux/spinlock.h> 23 + #include <linux/usb.h> 24 + 25 + static const struct mfd_cell nct6694_devs[] = { 26 + MFD_CELL_NAME("nct6694-gpio"), 27 + MFD_CELL_NAME("nct6694-gpio"), 28 + MFD_CELL_NAME("nct6694-gpio"), 29 + MFD_CELL_NAME("nct6694-gpio"), 30 + MFD_CELL_NAME("nct6694-gpio"), 31 + MFD_CELL_NAME("nct6694-gpio"), 32 + MFD_CELL_NAME("nct6694-gpio"), 33 + MFD_CELL_NAME("nct6694-gpio"), 34 + MFD_CELL_NAME("nct6694-gpio"), 35 + MFD_CELL_NAME("nct6694-gpio"), 36 + MFD_CELL_NAME("nct6694-gpio"), 37 + MFD_CELL_NAME("nct6694-gpio"), 38 + MFD_CELL_NAME("nct6694-gpio"), 39 + MFD_CELL_NAME("nct6694-gpio"), 40 + MFD_CELL_NAME("nct6694-gpio"), 41 + MFD_CELL_NAME("nct6694-gpio"), 42 + 43 + MFD_CELL_NAME("nct6694-i2c"), 44 + MFD_CELL_NAME("nct6694-i2c"), 45 + MFD_CELL_NAME("nct6694-i2c"), 46 + MFD_CELL_NAME("nct6694-i2c"), 47 + MFD_CELL_NAME("nct6694-i2c"), 48 + MFD_CELL_NAME("nct6694-i2c"), 49 + 50 + MFD_CELL_NAME("nct6694-canfd"), 51 + MFD_CELL_NAME("nct6694-canfd"), 52 + 53 + MFD_CELL_NAME("nct6694-wdt"), 54 + MFD_CELL_NAME("nct6694-wdt"), 55 + 56 + MFD_CELL_NAME("nct6694-hwmon"), 57 + 58 + MFD_CELL_NAME("nct6694-rtc"), 59 + }; 60 + 61 + static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status) 62 + { 63 + switch (err_status) { 64 + case NCT6694_NO_ERROR: 65 + return 0; 66 + case NCT6694_NOT_SUPPORT_ERROR: 67 + dev_err(nct6694->dev, "Command is not supported!\n"); 68 + break; 69 + case NCT6694_NO_RESPONSE_ERROR: 70 + dev_warn(nct6694->dev, "Command received no response!\n"); 71 + break; 72 + case NCT6694_TIMEOUT_ERROR: 73 + dev_warn(nct6694->dev, "Command timed out!\n"); 74 + break; 75 + case NCT6694_PENDING: 76 + dev_err(nct6694->dev, "Command is pending!\n"); 77 + break; 78 + default: 79 + return -EINVAL; 80 + } 81 + 82 + return -EIO; 83 + } 84 + 85 + /** 86 + * nct6694_read_msg() - Read message from NCT6694 device 87 + * @nct6694: NCT6694 device pointer 88 + * @cmd_hd: command header structure 89 + * @buf: buffer to store the response data 90 + * 91 + * Sends a command to the NCT6694 device and reads the response. 92 + * The command header is specified in @cmd_hd, and the response 93 + * data is stored in @buf. 94 + * 95 + * Return: Negative value on error or 0 on success. 96 + */ 97 + int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) 98 + { 99 + union nct6694_usb_msg *msg = nct6694->usb_msg; 100 + struct usb_device *udev = nct6694->udev; 101 + int tx_len, rx_len, ret; 102 + 103 + guard(mutex)(&nct6694->access_lock); 104 + 105 + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); 106 + msg->cmd_header.hctrl = NCT6694_HCTRL_GET; 107 + 108 + /* Send command packet to USB device */ 109 + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, 110 + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); 111 + if (ret) 112 + return ret; 113 + 114 + /* Receive response packet from USB device */ 115 + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, 116 + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); 117 + if (ret) 118 + return ret; 119 + 120 + /* Receive data packet from USB device */ 121 + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, 122 + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); 123 + if (ret) 124 + return ret; 125 + 126 + if (rx_len != le16_to_cpu(cmd_hd->len)) { 127 + dev_err(nct6694->dev, "Expected received length %d, but got %d\n", 128 + le16_to_cpu(cmd_hd->len), rx_len); 129 + return -EIO; 130 + } 131 + 132 + return nct6694_response_err_handling(nct6694, msg->response_header.sts); 133 + } 134 + EXPORT_SYMBOL_GPL(nct6694_read_msg); 135 + 136 + /** 137 + * nct6694_write_msg() - Write message to NCT6694 device 138 + * @nct6694: NCT6694 device pointer 139 + * @cmd_hd: command header structure 140 + * @buf: buffer containing the data to be sent 141 + * 142 + * Sends a command to the NCT6694 device and writes the data 143 + * from @buf. The command header is specified in @cmd_hd. 144 + * 145 + * Return: Negative value on error or 0 on success. 146 + */ 147 + int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) 148 + { 149 + union nct6694_usb_msg *msg = nct6694->usb_msg; 150 + struct usb_device *udev = nct6694->udev; 151 + int tx_len, rx_len, ret; 152 + 153 + guard(mutex)(&nct6694->access_lock); 154 + 155 + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); 156 + msg->cmd_header.hctrl = NCT6694_HCTRL_SET; 157 + 158 + /* Send command packet to USB device */ 159 + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, 160 + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); 161 + if (ret) 162 + return ret; 163 + 164 + /* Send data packet to USB device */ 165 + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf, 166 + le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT); 167 + if (ret) 168 + return ret; 169 + 170 + /* Receive response packet from USB device */ 171 + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, 172 + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); 173 + if (ret) 174 + return ret; 175 + 176 + /* Receive data packet from USB device */ 177 + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, 178 + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); 179 + if (ret) 180 + return ret; 181 + 182 + if (rx_len != le16_to_cpu(cmd_hd->len)) { 183 + dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n", 184 + le16_to_cpu(cmd_hd->len), rx_len); 185 + return -EIO; 186 + } 187 + 188 + return nct6694_response_err_handling(nct6694, msg->response_header.sts); 189 + } 190 + EXPORT_SYMBOL_GPL(nct6694_write_msg); 191 + 192 + static void usb_int_callback(struct urb *urb) 193 + { 194 + struct nct6694 *nct6694 = urb->context; 195 + __le32 *status_le = urb->transfer_buffer; 196 + u32 int_status; 197 + int ret; 198 + 199 + switch (urb->status) { 200 + case 0: 201 + break; 202 + case -ECONNRESET: 203 + case -ENOENT: 204 + case -ESHUTDOWN: 205 + return; 206 + default: 207 + goto resubmit; 208 + } 209 + 210 + int_status = le32_to_cpu(*status_le); 211 + 212 + while (int_status) { 213 + int irq = __ffs(int_status); 214 + 215 + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); 216 + int_status &= ~BIT(irq); 217 + } 218 + 219 + resubmit: 220 + ret = usb_submit_urb(urb, GFP_ATOMIC); 221 + if (ret) 222 + dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret)); 223 + } 224 + 225 + static void nct6694_irq_enable(struct irq_data *data) 226 + { 227 + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); 228 + irq_hw_number_t hwirq = irqd_to_hwirq(data); 229 + 230 + guard(spinlock_irqsave)(&nct6694->irq_lock); 231 + 232 + nct6694->irq_enable |= BIT(hwirq); 233 + } 234 + 235 + static void nct6694_irq_disable(struct irq_data *data) 236 + { 237 + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); 238 + irq_hw_number_t hwirq = irqd_to_hwirq(data); 239 + 240 + guard(spinlock_irqsave)(&nct6694->irq_lock); 241 + 242 + nct6694->irq_enable &= ~BIT(hwirq); 243 + } 244 + 245 + static const struct irq_chip nct6694_irq_chip = { 246 + .name = "nct6694-irq", 247 + .flags = IRQCHIP_SKIP_SET_WAKE, 248 + .irq_enable = nct6694_irq_enable, 249 + .irq_disable = nct6694_irq_disable, 250 + }; 251 + 252 + static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) 253 + { 254 + struct nct6694 *nct6694 = d->host_data; 255 + 256 + irq_set_chip_data(irq, nct6694); 257 + irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq); 258 + 259 + return 0; 260 + } 261 + 262 + static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq) 263 + { 264 + irq_set_chip_and_handler(irq, NULL, NULL); 265 + irq_set_chip_data(irq, NULL); 266 + } 267 + 268 + static const struct irq_domain_ops nct6694_irq_domain_ops = { 269 + .map = nct6694_irq_domain_map, 270 + .unmap = nct6694_irq_domain_unmap, 271 + }; 272 + 273 + static int nct6694_usb_probe(struct usb_interface *iface, 274 + const struct usb_device_id *id) 275 + { 276 + struct usb_device *udev = interface_to_usbdev(iface); 277 + struct usb_endpoint_descriptor *int_endpoint; 278 + struct usb_host_interface *interface; 279 + struct device *dev = &iface->dev; 280 + struct nct6694 *nct6694; 281 + int ret; 282 + 283 + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL); 284 + if (!nct6694) 285 + return -ENOMEM; 286 + 287 + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL); 288 + if (!nct6694->usb_msg) 289 + return -ENOMEM; 290 + 291 + nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL); 292 + if (!nct6694->int_buffer) 293 + return -ENOMEM; 294 + 295 + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); 296 + if (!nct6694->int_in_urb) 297 + return -ENOMEM; 298 + 299 + nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0, 300 + &nct6694_irq_domain_ops, 301 + nct6694); 302 + if (!nct6694->domain) { 303 + ret = -ENODEV; 304 + goto err_urb; 305 + } 306 + 307 + nct6694->dev = dev; 308 + nct6694->udev = udev; 309 + 310 + ida_init(&nct6694->gpio_ida); 311 + ida_init(&nct6694->i2c_ida); 312 + ida_init(&nct6694->canfd_ida); 313 + ida_init(&nct6694->wdt_ida); 314 + 315 + spin_lock_init(&nct6694->irq_lock); 316 + 317 + ret = devm_mutex_init(dev, &nct6694->access_lock); 318 + if (ret) 319 + goto err_ida; 320 + 321 + interface = iface->cur_altsetting; 322 + 323 + int_endpoint = &interface->endpoint[0].desc; 324 + if (!usb_endpoint_is_int_in(int_endpoint)) { 325 + ret = -ENODEV; 326 + goto err_ida; 327 + } 328 + 329 + usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP), 330 + nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback, 331 + nct6694, int_endpoint->bInterval); 332 + 333 + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL); 334 + if (ret) 335 + goto err_ida; 336 + 337 + usb_set_intfdata(iface, nct6694); 338 + 339 + ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs)); 340 + if (ret) 341 + goto err_mfd; 342 + 343 + return 0; 344 + 345 + err_mfd: 346 + usb_kill_urb(nct6694->int_in_urb); 347 + err_ida: 348 + ida_destroy(&nct6694->wdt_ida); 349 + ida_destroy(&nct6694->canfd_ida); 350 + ida_destroy(&nct6694->i2c_ida); 351 + ida_destroy(&nct6694->gpio_ida); 352 + irq_domain_remove(nct6694->domain); 353 + err_urb: 354 + usb_free_urb(nct6694->int_in_urb); 355 + return ret; 356 + } 357 + 358 + static void nct6694_usb_disconnect(struct usb_interface *iface) 359 + { 360 + struct nct6694 *nct6694 = usb_get_intfdata(iface); 361 + 362 + mfd_remove_devices(nct6694->dev); 363 + usb_kill_urb(nct6694->int_in_urb); 364 + ida_destroy(&nct6694->wdt_ida); 365 + ida_destroy(&nct6694->canfd_ida); 366 + ida_destroy(&nct6694->i2c_ida); 367 + ida_destroy(&nct6694->gpio_ida); 368 + irq_domain_remove(nct6694->domain); 369 + usb_free_urb(nct6694->int_in_urb); 370 + } 371 + 372 + static const struct usb_device_id nct6694_ids[] = { 373 + { USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) }, 374 + { } 375 + }; 376 + MODULE_DEVICE_TABLE(usb, nct6694_ids); 377 + 378 + static struct usb_driver nct6694_usb_driver = { 379 + .name = "nct6694", 380 + .id_table = nct6694_ids, 381 + .probe = nct6694_usb_probe, 382 + .disconnect = nct6694_usb_disconnect, 383 + }; 384 + module_usb_driver(nct6694_usb_driver); 385 + 386 + MODULE_DESCRIPTION("Nuvoton NCT6694 core driver"); 387 + MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>"); 388 + MODULE_LICENSE("GPL");
+102
include/linux/mfd/nct6694.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Copyright (C) 2025 Nuvoton Technology Corp. 4 + * 5 + * Nuvoton NCT6694 USB transaction and data structure. 6 + */ 7 + 8 + #ifndef __MFD_NCT6694_H 9 + #define __MFD_NCT6694_H 10 + 11 + #define NCT6694_VENDOR_ID 0x0416 12 + #define NCT6694_PRODUCT_ID 0x200B 13 + #define NCT6694_INT_IN_EP 0x81 14 + #define NCT6694_BULK_IN_EP 0x02 15 + #define NCT6694_BULK_OUT_EP 0x03 16 + 17 + #define NCT6694_HCTRL_SET 0x40 18 + #define NCT6694_HCTRL_GET 0x80 19 + 20 + #define NCT6694_URB_TIMEOUT 1000 21 + 22 + enum nct6694_irq_id { 23 + NCT6694_IRQ_GPIO0 = 0, 24 + NCT6694_IRQ_GPIO1, 25 + NCT6694_IRQ_GPIO2, 26 + NCT6694_IRQ_GPIO3, 27 + NCT6694_IRQ_GPIO4, 28 + NCT6694_IRQ_GPIO5, 29 + NCT6694_IRQ_GPIO6, 30 + NCT6694_IRQ_GPIO7, 31 + NCT6694_IRQ_GPIO8, 32 + NCT6694_IRQ_GPIO9, 33 + NCT6694_IRQ_GPIOA, 34 + NCT6694_IRQ_GPIOB, 35 + NCT6694_IRQ_GPIOC, 36 + NCT6694_IRQ_GPIOD, 37 + NCT6694_IRQ_GPIOE, 38 + NCT6694_IRQ_GPIOF, 39 + NCT6694_IRQ_CAN0, 40 + NCT6694_IRQ_CAN1, 41 + NCT6694_IRQ_RTC, 42 + NCT6694_NR_IRQS, 43 + }; 44 + 45 + enum nct6694_response_err_status { 46 + NCT6694_NO_ERROR = 0, 47 + NCT6694_FORMAT_ERROR, 48 + NCT6694_RESERVED1, 49 + NCT6694_RESERVED2, 50 + NCT6694_NOT_SUPPORT_ERROR, 51 + NCT6694_NO_RESPONSE_ERROR, 52 + NCT6694_TIMEOUT_ERROR, 53 + NCT6694_PENDING, 54 + }; 55 + 56 + struct __packed nct6694_cmd_header { 57 + u8 rsv1; 58 + u8 mod; 59 + union __packed { 60 + __le16 offset; 61 + struct __packed { 62 + u8 cmd; 63 + u8 sel; 64 + }; 65 + }; 66 + u8 hctrl; 67 + u8 rsv2; 68 + __le16 len; 69 + }; 70 + 71 + struct __packed nct6694_response_header { 72 + u8 sequence_id; 73 + u8 sts; 74 + u8 reserved[4]; 75 + __le16 len; 76 + }; 77 + 78 + union __packed nct6694_usb_msg { 79 + struct nct6694_cmd_header cmd_header; 80 + struct nct6694_response_header response_header; 81 + }; 82 + 83 + struct nct6694 { 84 + struct device *dev; 85 + struct ida gpio_ida; 86 + struct ida i2c_ida; 87 + struct ida canfd_ida; 88 + struct ida wdt_ida; 89 + struct irq_domain *domain; 90 + struct mutex access_lock; 91 + spinlock_t irq_lock; 92 + struct urb *int_in_urb; 93 + struct usb_device *udev; 94 + union nct6694_usb_msg *usb_msg; 95 + __le32 *int_buffer; 96 + unsigned int irq_enable; 97 + }; 98 + 99 + int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); 100 + int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); 101 + 102 + #endif