Message ID | 20250604041418.1188792-2-tmyu0@nuvoton.com |
---|---|
State | New |
Headers | show |
Series | Add Nuvoton NCT6694 MFD drivers | expand |
On 04.06.25 06:14, a0282524688@gmail.com wrote: > From: Ming Yu <tmyu0@nuvoton.com> > > 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 <tmyu0@nuvoton.com> > --- > Changes since version 11: > - Modify the irq_domain_add_simple() to irq_domain_create_simple() > - Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA > in sub-drivers > > Changes since version 10: > - Add change log for the patch > - Fix mfd_cell to MFD_CELL_NAME() > - Remove unnecessary blank line > > Changes since version 9: > - Add KernelDoc to exported functions > > Changes since version 8: > - Modify the signed-off-by with my work address > - Rename all MFD cell names to "nct6694-xxx" > - Fix some comments in nct6694.c and in nct6694.h > > Changes since version 7: > - Add error handling for devm_mutex_init() > > Changes since version 6: > > Changes since version 5: > - Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC() > - Drop unnecessary macros > > Changes since version 4: > - Modify arguments in read/write function to a pointer to cmd_header > > Changes since version 3: > - Modify array buffer to structure > - Fix defines and comments > - Add header <linux/bits.h> and use BIT macro > - Modify mutex_init() to devm_mutex_init() > > Changes since version 2: > > Changes since version 1: > - Implement IRQ domain to handle IRQ demux > - Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API > - Add command structure > - Fix USB functions > - Sort each driver's header files alphabetically > > MAINTAINERS | 6 + > drivers/mfd/Kconfig | 15 ++ > drivers/mfd/Makefile | 2 + > drivers/mfd/nct6694.c | 386 ++++++++++++++++++++++++++++++++++++ > include/linux/mfd/nct6694.h | 98 +++++++++ > 5 files changed, 507 insertions(+) > create mode 100644 drivers/mfd/nct6694.c > create mode 100644 include/linux/mfd/nct6694.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 98201e1f4ab5..29d2d05bac22 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -17679,6 +17679,12 @@ F: drivers/nubus/ > F: include/linux/nubus.h > F: include/uapi/linux/nubus.h > > +NUVOTON NCT6694 MFD DRIVER > +M: Ming Yu <tmyu0@nuvoton.com> > +S: Supported > +F: drivers/mfd/nct6694.c > +F: include/linux/mfd/nct6694.h > + > NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER > M: Antonino Daplas <adaplas@gmail.com> > L: linux-fbdev@vger.kernel.org > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig > index 96992af22565..489c1950f1ac 100644 > --- a/drivers/mfd/Kconfig > +++ b/drivers/mfd/Kconfig > @@ -1078,6 +1078,21 @@ config MFD_MENF21BMC > This driver can also be built as a module. If so the module > will be called menf21bmc. > > +config MFD_NCT6694 > + tristate "Nuvoton NCT6694 support" > + select MFD_CORE > + depends on USB > + help > + This enables support for the Nuvoton USB device NCT6694, which shares > + peripherals. > + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, > + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, > + PWM, and RTC. > + This driver provides core APIs to access the NCT6694 hardware > + monitoring and control features. > + Additional drivers must be enabled to utilize the specific > + functionalities of the device. > + > config MFD_OCELOT > tristate "Microsemi Ocelot External Control Support" > depends on SPI_MASTER > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile > index 5e5cc279af60..a96204d938fc 100644 > --- a/drivers/mfd/Makefile > +++ b/drivers/mfd/Makefile > @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o > obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o > obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o > > +obj-$(CONFIG_MFD_NCT6694) += nct6694.o > + > obj-$(CONFIG_MFD_CORE) += mfd-core.o > > ocelot-soc-objs := ocelot-core.o ocelot-spi.o > diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c > new file mode 100644 > index 000000000000..82d378ee47ed > --- /dev/null > +++ b/drivers/mfd/nct6694.c > @@ -0,0 +1,386 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2025 Nuvoton Technology Corp. > + * > + * Nuvoton NCT6694 core driver using USB interface to provide > + * access to the NCT6694 hardware monitoring and control features. > + * > + * The NCT6694 is an integrated controller that provides GPIO, I2C, > + * CAN, WDT, HWMON and RTC management. > + */ > + > +#include <linux/bits.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/irqdomain.h> > +#include <linux/kernel.h> > +#include <linux/mfd/core.h> > +#include <linux/mfd/nct6694.h> > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/usb.h> > + > +static const struct mfd_cell nct6694_devs[] = { > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), > + > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), > + > + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1), > + > + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1), > + > + MFD_CELL_NAME("nct6694-hwmon"), > + > + MFD_CELL_NAME("nct6694-rtc"), > +}; > + > +static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status) > +{ > + switch (err_status) { > + case NCT6694_NO_ERROR: > + return 0; > + case NCT6694_NOT_SUPPORT_ERROR: > + dev_err(nct6694->dev, "Command is not supported!\n"); > + break; > + case NCT6694_NO_RESPONSE_ERROR: > + dev_warn(nct6694->dev, "Command received no response!\n"); > + break; > + case NCT6694_TIMEOUT_ERROR: > + dev_warn(nct6694->dev, "Command timed out!\n"); > + break; > + case NCT6694_PENDING: > + dev_err(nct6694->dev, "Command is pending!\n"); > + break; > + default: > + return -EINVAL; > + } > + > + return -EIO; > +} > + > +/** > + * nct6694_read_msg() - Read message from NCT6694 device > + * @nct6694: NCT6694 device pointer > + * @cmd_hd: command header structure > + * @buf: buffer to store the response data > + * > + * Sends a command to the NCT6694 device and reads the response. > + * The command header is specified in @cmd_hd, and the response > + * data is stored in @buf. > + * > + * Return: Negative value on error or 0 on success. > + */ > +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) > +{ > + union nct6694_usb_msg *msg = nct6694->usb_msg; > + struct usb_device *udev = nct6694->udev; > + int tx_len, rx_len, ret; > + > + guard(mutex)(&nct6694->access_lock); > + > + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); > + msg->cmd_header.hctrl = NCT6694_HCTRL_GET; > + > + /* Send command packet to USB device */ > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, > + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + /* Receive response packet from USB device */ > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, > + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + /* Receive data packet from USB device */ > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, > + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + if (rx_len != le16_to_cpu(cmd_hd->len)) { > + dev_err(nct6694->dev, "Expected received length %d, but got %d\n", > + le16_to_cpu(cmd_hd->len), rx_len); > + return -EIO; > + } > + > + return nct6694_response_err_handling(nct6694, msg->response_header.sts); > +} > +EXPORT_SYMBOL_GPL(nct6694_read_msg); > + > +/** > + * nct6694_write_msg() - Write message to NCT6694 device > + * @nct6694: NCT6694 device pointer > + * @cmd_hd: command header structure > + * @buf: buffer containing the data to be sent > + * > + * Sends a command to the NCT6694 device and writes the data > + * from @buf. The command header is specified in @cmd_hd. > + * > + * Return: Negative value on error or 0 on success. > + */ > +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) > +{ > + union nct6694_usb_msg *msg = nct6694->usb_msg; > + struct usb_device *udev = nct6694->udev; > + int tx_len, rx_len, ret; > + > + guard(mutex)(&nct6694->access_lock); > + > + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); > + msg->cmd_header.hctrl = NCT6694_HCTRL_SET; > + > + /* Send command packet to USB device */ > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, > + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + /* Send data packet to USB device */ > + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf, > + le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + /* Receive response packet from USB device */ > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, > + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + /* Receive data packet from USB device */ > + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, > + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); > + if (ret) > + return ret; > + > + if (rx_len != le16_to_cpu(cmd_hd->len)) { > + dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n", > + le16_to_cpu(cmd_hd->len), rx_len); > + return -EIO; > + } > + > + return nct6694_response_err_handling(nct6694, msg->response_header.sts); > +} > +EXPORT_SYMBOL_GPL(nct6694_write_msg); > + > +static void usb_int_callback(struct urb *urb) > +{ > + struct nct6694 *nct6694 = urb->context; > + unsigned int *int_status = urb->transfer_buffer; > + int ret; > + > + switch (urb->status) { > + case 0: > + break; > + case -ECONNRESET: > + case -ENOENT: > + case -ESHUTDOWN: > + return; > + default: > + goto resubmit; > + } > + > + while (*int_status) { > + int irq = __ffs(*int_status); > + > + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); > + *int_status &= ~BIT(irq); > + } Does modifying the byte have any benefit? > +resubmit: > + ret = usb_submit_urb(urb, GFP_ATOMIC); > + if (ret) > + dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret)); > +} > + > +static void nct6694_irq_lock(struct irq_data *data) > +{ > + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); > + > + mutex_lock(&nct6694->irq_lock); > +} Why? Does this do anything but make it _harder_ to tell that you cannot take the lock in interrupt? Regards Oliver
On 6/4/25 03:11, Oliver Neukum wrote: > On 04.06.25 06:14, a0282524688@gmail.com wrote: >> From: Ming Yu <tmyu0@nuvoton.com> >> >> 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 <tmyu0@nuvoton.com> >> --- ... >> +static void usb_int_callback(struct urb *urb) >> +{ >> + struct nct6694 *nct6694 = urb->context; >> + unsigned int *int_status = urb->transfer_buffer; >> + int ret; >> + >> + switch (urb->status) { >> + case 0: >> + break; >> + case -ECONNRESET: >> + case -ENOENT: >> + case -ESHUTDOWN: >> + return; >> + default: >> + goto resubmit; >> + } >> + >> + while (*int_status) { >> + int irq = __ffs(*int_status); >> + >> + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); >> + *int_status &= ~BIT(irq); >> + } > > Does modifying the byte have any benefit? > Not sure if I understand the question, and assuming your question is regarding *int_status: *int_status!=0 is the loop invariant, so, yes, modifying it does have a benefit. I'd be more concerned that transfer_buffer is the raw buffer, and that data read from it is not endianness converted. That makes me wonder if and how the code would work on a big endian system. Guenter
Dear Guenter, Thank you for reviewing, Guenter Roeck <linux@roeck-us.net> 於 2025年6月4日 週三 下午8:40寫道: > > >> +static void usb_int_callback(struct urb *urb) > >> +{ > >> + struct nct6694 *nct6694 = urb->context; > >> + unsigned int *int_status = urb->transfer_buffer; > >> + int ret; > >> + > >> + switch (urb->status) { > >> + case 0: > >> + break; > >> + case -ECONNRESET: > >> + case -ENOENT: > >> + case -ESHUTDOWN: > >> + return; > >> + default: > >> + goto resubmit; > >> + } > >> + > >> + while (*int_status) { > >> + int irq = __ffs(*int_status); > >> + > >> + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); > >> + *int_status &= ~BIT(irq); > >> + } > > > > Does modifying the byte have any benefit? > > > > Not sure if I understand the question, and assuming your question is regarding > *int_status: *int_status!=0 is the loop invariant, so, yes, modifying it does > have a benefit. > > I'd be more concerned that transfer_buffer is the raw buffer, and that data > read from it is not endianness converted. That makes me wonder if and how the > code would work on a big endian system. > I will update the code in the next patch to use __le32 for the variable to ensure proper endianness handling across architectures. Best regards, Ming
On Wed, 04 Jun 2025, a0282524688@gmail.com wrote: > From: Ming Yu <tmyu0@nuvoton.com> > > 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 <tmyu0@nuvoton.com> > --- > Changes since version 11: > - Modify the irq_domain_add_simple() to irq_domain_create_simple() > - Fix mfd_cell back to v9, and use Use platform_device's id to replace IDA > in sub-drivers > > Changes since version 10: > - Add change log for the patch > - Fix mfd_cell to MFD_CELL_NAME() > - Remove unnecessary blank line > > Changes since version 9: > - Add KernelDoc to exported functions > > Changes since version 8: > - Modify the signed-off-by with my work address > - Rename all MFD cell names to "nct6694-xxx" > - Fix some comments in nct6694.c and in nct6694.h > > Changes since version 7: > - Add error handling for devm_mutex_init() > > Changes since version 6: > > Changes since version 5: > - Fix mfd_cell to MFD_CELL_NAME() and MFD_CELL_BASIC() > - Drop unnecessary macros > > Changes since version 4: > - Modify arguments in read/write function to a pointer to cmd_header > > Changes since version 3: > - Modify array buffer to structure > - Fix defines and comments > - Add header <linux/bits.h> and use BIT macro > - Modify mutex_init() to devm_mutex_init() > > Changes since version 2: > > Changes since version 1: > - Implement IRQ domain to handle IRQ demux > - Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API > - Add command structure > - Fix USB functions > - Sort each driver's header files alphabetically > > MAINTAINERS | 6 + > drivers/mfd/Kconfig | 15 ++ > drivers/mfd/Makefile | 2 + > drivers/mfd/nct6694.c | 386 ++++++++++++++++++++++++++++++++++++ > include/linux/mfd/nct6694.h | 98 +++++++++ > 5 files changed, 507 insertions(+) > create mode 100644 drivers/mfd/nct6694.c > create mode 100644 include/linux/mfd/nct6694.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 98201e1f4ab5..29d2d05bac22 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -17679,6 +17679,12 @@ F: drivers/nubus/ > F: include/linux/nubus.h > F: include/uapi/linux/nubus.h > > +NUVOTON NCT6694 MFD DRIVER > +M: Ming Yu <tmyu0@nuvoton.com> > +S: Supported > +F: drivers/mfd/nct6694.c > +F: include/linux/mfd/nct6694.h > + > NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER > M: Antonino Daplas <adaplas@gmail.com> > L: linux-fbdev@vger.kernel.org > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig > index 96992af22565..489c1950f1ac 100644 > --- a/drivers/mfd/Kconfig > +++ b/drivers/mfd/Kconfig > @@ -1078,6 +1078,21 @@ config MFD_MENF21BMC > This driver can also be built as a module. If so the module > will be called menf21bmc. > > +config MFD_NCT6694 > + tristate "Nuvoton NCT6694 support" > + select MFD_CORE > + depends on USB > + help > + This enables support for the Nuvoton USB device NCT6694, which shares > + peripherals. > + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, > + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, > + PWM, and RTC. > + This driver provides core APIs to access the NCT6694 hardware > + monitoring and control features. > + Additional drivers must be enabled to utilize the specific > + functionalities of the device. > + > config MFD_OCELOT > tristate "Microsemi Ocelot External Control Support" > depends on SPI_MASTER > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile > index 5e5cc279af60..a96204d938fc 100644 > --- a/drivers/mfd/Makefile > +++ b/drivers/mfd/Makefile > @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o > obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o > obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o > > +obj-$(CONFIG_MFD_NCT6694) += nct6694.o > + > obj-$(CONFIG_MFD_CORE) += mfd-core.o > > ocelot-soc-objs := ocelot-core.o ocelot-spi.o > diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c > new file mode 100644 > index 000000000000..82d378ee47ed > --- /dev/null > +++ b/drivers/mfd/nct6694.c > @@ -0,0 +1,386 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2025 Nuvoton Technology Corp. > + * > + * Nuvoton NCT6694 core driver using USB interface to provide > + * access to the NCT6694 hardware monitoring and control features. > + * > + * The NCT6694 is an integrated controller that provides GPIO, I2C, > + * CAN, WDT, HWMON and RTC management. > + */ > + > +#include <linux/bits.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/irqdomain.h> > +#include <linux/kernel.h> > +#include <linux/mfd/core.h> > +#include <linux/mfd/nct6694.h> > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/usb.h> > + > +static const struct mfd_cell nct6694_devs[] = { > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), > + > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), Why have we gone back to this silly numbering scheme? What happened to using IDA in the child driver? > + > + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1), > + > + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0), > + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1), > + > + MFD_CELL_NAME("nct6694-hwmon"), > + > + MFD_CELL_NAME("nct6694-rtc"), > +};
On Thu, 12 Jun 2025, Ming Yu wrote: > Dear Lee, > > Thank you for reviewing, > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道: > > > ... > > > +static const struct mfd_cell nct6694_devs[] = { > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), > > > + > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), > > > > Why have we gone back to this silly numbering scheme? > > > > What happened to using IDA in the child driver? > > > > In a previous version, I tried to maintain a static IDA in each > sub-driver. However, I didn’t consider the case where multiple NCT6694 > devices are bound to the same driver — in that case, the IDs are not > fixed and become unusable for my purpose. Not sure I understand. > I’ve since realized that using pdev->id avoids the need for cell->id, > so I reverted to the earlier approach. > > That said, do you think it would be a better solution to manage all > the IDAs centrally within the driver? For example: > in nct6694.c > struct nct6694 { > struct device *dev; > > struct ida gpio_ida; > struct ida i2c_ida; > struct ida can_ida; > struct ida wdt_ida; > }; > > static int nct6694_probe(struct platform_device *pdev) > { > ida_init(&nct6694->gpio_ida); > ... > } > > in gpio-nct6694.c > static int nct6694_gpio_probe(struct platform_device *pdev) > { > id = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL); > } No that would be way worse.
On Fri, 13 Jun 2025, Ming Yu wrote: > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道: > > > > On Thu, 12 Jun 2025, Ming Yu wrote: > > > > > Dear Lee, > > > > > > Thank you for reviewing, > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道: > > > > > > > ... > > > > > +static const struct mfd_cell nct6694_devs[] = { > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), > > > > > + > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), > > > > > > > > Why have we gone back to this silly numbering scheme? > > > > > > > > What happened to using IDA in the child driver? > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each > > > sub-driver. However, I didn’t consider the case where multiple NCT6694 > > > devices are bound to the same driver — in that case, the IDs are not > > > fixed and become unusable for my purpose. > > > > Not sure I understand. > > > > As far as I know, if I maintain the IDA in the sub-drivers and use > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first > NCT6694 device bound to the GPIO driver will receive IDs 0~15. > However, when a second NCT6694 device is connected to the system, it > will receive IDs 16~31. > Because of this behavior, I switched back to using platform_device->id. Each of the devices will probe once. The first one will be given 0, the second will be given 1, etc. Why would you give multiple IDs to a single device bound to a driver?
Lee Jones <lee@kernel.org> 於 2025年6月13日 週五 下午9:11寫道: > > On Fri, 13 Jun 2025, Ming Yu wrote: > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午11:23寫道: > > > > > > On Thu, 12 Jun 2025, Ming Yu wrote: > > > > > > > Dear Lee, > > > > > > > > Thank you for reviewing, > > > > > > > > Lee Jones <lee@kernel.org> 於 2025年6月12日 週四 下午10:00寫道: > > > > > > > > > ... > > > > > > +static const struct mfd_cell nct6694_devs[] = { > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), > > > > > > + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), > > > > > > + > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), > > > > > > + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), > > > > > > > > > > Why have we gone back to this silly numbering scheme? > > > > > > > > > > What happened to using IDA in the child driver? > > > > > > > > > > > > > In a previous version, I tried to maintain a static IDA in each > > > > sub-driver. However, I didn’t consider the case where multiple NCT6694 > > > > devices are bound to the same driver — in that case, the IDs are not > > > > fixed and become unusable for my purpose. > > > > > > Not sure I understand. > > > > > > > As far as I know, if I maintain the IDA in the sub-drivers and use > > multiple MFD_CELL_NAME("nct6694-gpio") entries in the MFD, the first > > NCT6694 device bound to the GPIO driver will receive IDs 0~15. > > However, when a second NCT6694 device is connected to the system, it > > will receive IDs 16~31. > > Because of this behavior, I switched back to using platform_device->id. > > Each of the devices will probe once. > > The first one will be given 0, the second will be given 1, etc. > > Why would you give multiple IDs to a single device bound to a driver? > The device exposes multiple peripherals — 16 GPIO controllers, 6 I2C adapters, 2 CAN FD controllers, and 2 watchdog timers. Each peripheral is independently addressable, has its own register region, and can operate in isolation. The IDs are used to distinguish between these instances. For example, the GPIO driver will be probed 16 times, allocating 16 separate gpio_chip instances to control 8 GPIO lines each. If another device binds to this driver, it is expected to expose peripherals with the same structure and behavior. Thanks, Ming
diff --git a/MAINTAINERS b/MAINTAINERS index 98201e1f4ab5..29d2d05bac22 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17679,6 +17679,12 @@ F: drivers/nubus/ F: include/linux/nubus.h F: include/uapi/linux/nubus.h +NUVOTON NCT6694 MFD DRIVER +M: Ming Yu <tmyu0@nuvoton.com> +S: Supported +F: drivers/mfd/nct6694.c +F: include/linux/mfd/nct6694.h + NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER M: Antonino Daplas <adaplas@gmail.com> L: linux-fbdev@vger.kernel.org diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 96992af22565..489c1950f1ac 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1078,6 +1078,21 @@ config MFD_MENF21BMC This driver can also be built as a module. If so the module will be called menf21bmc. +config MFD_NCT6694 + tristate "Nuvoton NCT6694 support" + select MFD_CORE + depends on USB + help + This enables support for the Nuvoton USB device NCT6694, which shares + peripherals. + The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips, + 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC, + PWM, and RTC. + This driver provides core APIs to access the NCT6694 hardware + monitoring and control features. + Additional drivers must be enabled to utilize the specific + functionalities of the device. + config MFD_OCELOT tristate "Microsemi Ocelot External Control Support" depends on SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5e5cc279af60..a96204d938fc 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -120,6 +120,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o +obj-$(CONFIG_MFD_NCT6694) += nct6694.o + obj-$(CONFIG_MFD_CORE) += mfd-core.o ocelot-soc-objs := ocelot-core.o ocelot-spi.o diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c new file mode 100644 index 000000000000..82d378ee47ed --- /dev/null +++ b/drivers/mfd/nct6694.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Nuvoton Technology Corp. + * + * Nuvoton NCT6694 core driver using USB interface to provide + * access to the NCT6694 hardware monitoring and control features. + * + * The NCT6694 is an integrated controller that provides GPIO, I2C, + * CAN, WDT, HWMON and RTC management. + */ + +#include <linux/bits.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/mfd/core.h> +#include <linux/mfd/nct6694.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> + +static const struct mfd_cell nct6694_devs[] = { + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14), + MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15), + + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0), + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1), + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2), + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3), + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4), + MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5), + + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0), + MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1), + + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0), + MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1), + + MFD_CELL_NAME("nct6694-hwmon"), + + MFD_CELL_NAME("nct6694-rtc"), +}; + +static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status) +{ + switch (err_status) { + case NCT6694_NO_ERROR: + return 0; + case NCT6694_NOT_SUPPORT_ERROR: + dev_err(nct6694->dev, "Command is not supported!\n"); + break; + case NCT6694_NO_RESPONSE_ERROR: + dev_warn(nct6694->dev, "Command received no response!\n"); + break; + case NCT6694_TIMEOUT_ERROR: + dev_warn(nct6694->dev, "Command timed out!\n"); + break; + case NCT6694_PENDING: + dev_err(nct6694->dev, "Command is pending!\n"); + break; + default: + return -EINVAL; + } + + return -EIO; +} + +/** + * nct6694_read_msg() - Read message from NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer to store the response data + * + * Sends a command to the NCT6694 device and reads the response. + * The command header is specified in @cmd_hd, and the response + * data is stored in @buf. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg = nct6694->usb_msg; + struct usb_device *udev = nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl = NCT6694_HCTRL_GET; + + /* Send command packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len != le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected received length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_read_msg); + +/** + * nct6694_write_msg() - Write message to NCT6694 device + * @nct6694: NCT6694 device pointer + * @cmd_hd: command header structure + * @buf: buffer containing the data to be sent + * + * Sends a command to the NCT6694 device and writes the data + * from @buf. The command header is specified in @cmd_hd. + * + * Return: Negative value on error or 0 on success. + */ +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf) +{ + union nct6694_usb_msg *msg = nct6694->usb_msg; + struct usb_device *udev = nct6694->udev; + int tx_len, rx_len, ret; + + guard(mutex)(&nct6694->access_lock); + + memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd)); + msg->cmd_header.hctrl = NCT6694_HCTRL_SET; + + /* Send command packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header, + sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Send data packet to USB device */ + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf, + le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive response packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header, + sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + /* Receive data packet from USB device */ + ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf, + le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT); + if (ret) + return ret; + + if (rx_len != le16_to_cpu(cmd_hd->len)) { + dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n", + le16_to_cpu(cmd_hd->len), rx_len); + return -EIO; + } + + return nct6694_response_err_handling(nct6694, msg->response_header.sts); +} +EXPORT_SYMBOL_GPL(nct6694_write_msg); + +static void usb_int_callback(struct urb *urb) +{ + struct nct6694 *nct6694 = urb->context; + unsigned int *int_status = urb->transfer_buffer; + int ret; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto resubmit; + } + + while (*int_status) { + int irq = __ffs(*int_status); + + generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq)); + *int_status &= ~BIT(irq); + } + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret)); +} + +static void nct6694_irq_lock(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + + mutex_lock(&nct6694->irq_lock); +} + +static void nct6694_irq_sync_unlock(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + + mutex_unlock(&nct6694->irq_lock); +} + +static void nct6694_irq_enable(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + nct6694->irq_enable |= BIT(hwirq); +} + +static void nct6694_irq_disable(struct irq_data *data) +{ + struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + nct6694->irq_enable &= ~BIT(hwirq); +} + +static const struct irq_chip nct6694_irq_chip = { + .name = "nct6694-irq", + .flags = IRQCHIP_SKIP_SET_WAKE, + .irq_bus_lock = nct6694_irq_lock, + .irq_bus_sync_unlock = nct6694_irq_sync_unlock, + .irq_enable = nct6694_irq_enable, + .irq_disable = nct6694_irq_disable, +}; + +static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +{ + struct nct6694 *nct6694 = d->host_data; + + irq_set_chip_data(irq, nct6694); + irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq); + + return 0; +} + +static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops nct6694_irq_domain_ops = { + .map = nct6694_irq_domain_map, + .unmap = nct6694_irq_domain_unmap, +}; + +static int nct6694_usb_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_endpoint_descriptor *int_endpoint; + struct usb_host_interface *interface; + struct device *dev = &iface->dev; + struct nct6694 *nct6694; + int pipe, maxp; + int ret; + + nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL); + if (!nct6694) + return -ENOMEM; + + pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP); + maxp = usb_maxpacket(udev, pipe); + + nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL); + if (!nct6694->usb_msg) + return -ENOMEM; + + nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL); + if (!nct6694->int_buffer) + return -ENOMEM; + + nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!nct6694->int_in_urb) + return -ENOMEM; + + nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0, + &nct6694_irq_domain_ops, + nct6694); + if (!nct6694->domain) { + ret = -ENODEV; + goto err_urb; + } + + nct6694->dev = dev; + nct6694->udev = udev; + + ret = devm_mutex_init(dev, &nct6694->access_lock); + if (ret) + goto err_irq; + + ret = devm_mutex_init(dev, &nct6694->irq_lock); + if (ret) + goto err_irq; + + interface = iface->cur_altsetting; + + int_endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(int_endpoint)) { + ret = -ENODEV; + goto err_irq; + } + + usb_fill_int_urb(nct6694->int_in_urb, udev, pipe, nct6694->int_buffer, maxp, + usb_int_callback, nct6694, int_endpoint->bInterval); + + ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL); + if (ret) + goto err_irq; + + usb_set_intfdata(iface, nct6694); + + ret = devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL); + if (ret) + goto err_mfd; + + return 0; + +err_mfd: + usb_kill_urb(nct6694->int_in_urb); +err_irq: + irq_domain_remove(nct6694->domain); +err_urb: + usb_free_urb(nct6694->int_in_urb); + return ret; +} + +static void nct6694_usb_disconnect(struct usb_interface *iface) +{ + struct nct6694 *nct6694 = usb_get_intfdata(iface); + + usb_kill_urb(nct6694->int_in_urb); + irq_domain_remove(nct6694->domain); + usb_free_urb(nct6694->int_in_urb); +} + +static const struct usb_device_id nct6694_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) }, + { } +}; +MODULE_DEVICE_TABLE(usb, nct6694_ids); + +static struct usb_driver nct6694_usb_driver = { + .name = "nct6694", + .id_table = nct6694_ids, + .probe = nct6694_usb_probe, + .disconnect = nct6694_usb_disconnect, +}; +module_usb_driver(nct6694_usb_driver); + +MODULE_DESCRIPTION("Nuvoton NCT6694 core driver"); +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h new file mode 100644 index 000000000000..5e172609be3f --- /dev/null +++ b/include/linux/mfd/nct6694.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Nuvoton Technology Corp. + * + * Nuvoton NCT6694 USB transaction and data structure. + */ + +#ifndef __MFD_NCT6694_H +#define __MFD_NCT6694_H + +#define NCT6694_VENDOR_ID 0x0416 +#define NCT6694_PRODUCT_ID 0x200B +#define NCT6694_INT_IN_EP 0x81 +#define NCT6694_BULK_IN_EP 0x02 +#define NCT6694_BULK_OUT_EP 0x03 + +#define NCT6694_HCTRL_SET 0x40 +#define NCT6694_HCTRL_GET 0x80 + +#define NCT6694_URB_TIMEOUT 1000 + +enum nct6694_irq_id { + NCT6694_IRQ_GPIO0 = 0, + NCT6694_IRQ_GPIO1, + NCT6694_IRQ_GPIO2, + NCT6694_IRQ_GPIO3, + NCT6694_IRQ_GPIO4, + NCT6694_IRQ_GPIO5, + NCT6694_IRQ_GPIO6, + NCT6694_IRQ_GPIO7, + NCT6694_IRQ_GPIO8, + NCT6694_IRQ_GPIO9, + NCT6694_IRQ_GPIOA, + NCT6694_IRQ_GPIOB, + NCT6694_IRQ_GPIOC, + NCT6694_IRQ_GPIOD, + NCT6694_IRQ_GPIOE, + NCT6694_IRQ_GPIOF, + NCT6694_IRQ_CAN0, + NCT6694_IRQ_CAN1, + NCT6694_IRQ_RTC, + NCT6694_NR_IRQS, +}; + +enum nct6694_response_err_status { + NCT6694_NO_ERROR = 0, + NCT6694_FORMAT_ERROR, + NCT6694_RESERVED1, + NCT6694_RESERVED2, + NCT6694_NOT_SUPPORT_ERROR, + NCT6694_NO_RESPONSE_ERROR, + NCT6694_TIMEOUT_ERROR, + NCT6694_PENDING, +}; + +struct __packed nct6694_cmd_header { + u8 rsv1; + u8 mod; + union __packed { + __le16 offset; + struct __packed { + u8 cmd; + u8 sel; + }; + }; + u8 hctrl; + u8 rsv2; + __le16 len; +}; + +struct __packed nct6694_response_header { + u8 sequence_id; + u8 sts; + u8 reserved[4]; + __le16 len; +}; + +union __packed nct6694_usb_msg { + struct nct6694_cmd_header cmd_header; + struct nct6694_response_header response_header; +}; + +struct nct6694 { + struct device *dev; + struct irq_domain *domain; + struct mutex access_lock; + struct mutex irq_lock; + struct urb *int_in_urb; + struct usb_device *udev; + union nct6694_usb_msg *usb_msg; + unsigned char *int_buffer; + unsigned int irq_enable; +}; + +int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); +int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf); + +#endif