summaryrefslogtreecommitdiff
path: root/drivers/mfd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig25
-rw-r--r--drivers/mfd/Makefile2
-rw-r--r--drivers/mfd/nct6694.c388
-rw-r--r--drivers/mfd/stmpe.c6
-rw-r--r--drivers/mfd/vexpress-sysreg.c25
5 files changed, 436 insertions, 10 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 58b1c2900d59..4d21dbf73a34 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1134,6 +1134,21 @@ config MFD_MENF21BMC
This driver can also be built as a module. If so the module
will be called menf21bmc.
+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
@@ -1539,8 +1554,8 @@ config MFD_DB8500_PRCMU
through a register map.
config MFD_STMPE
- bool "STMicroelectronics STMPE"
- depends on I2C=y || SPI_MASTER=y
+ tristate "STMicroelectronics STMPE"
+ depends on I2C || SPI_MASTER
depends on OF
select MFD_CORE
help
@@ -1568,14 +1583,14 @@ menu "STMicroelectronics STMPE Interface Drivers"
depends on MFD_STMPE
config STMPE_I2C
- bool "STMicroelectronics STMPE I2C Interface"
- depends on I2C=y
+ tristate "STMicroelectronics STMPE I2C Interface"
+ depends on I2C
default y
help
This is used to enable I2C interface of STMPE
config STMPE_SPI
- bool "STMicroelectronics STMPE SPI Interface"
+ tristate "STMicroelectronics STMPE SPI Interface"
depends on SPI_MASTER
help
This is used to enable SPI interface of STMPE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c81c6a8473e1..3dd7a43c44c0 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -121,6 +121,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..308b2fda3055
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 core driver using USB interface to provide
+ * access to the NCT6694 hardware monitoring and control features.
+ *
+ * The NCT6694 is an integrated controller that provides GPIO, I2C,
+ * CAN, WDT, HWMON and RTC management.
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/idr.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/spinlock.h>
+#include <linux/usb.h>
+
+static const struct mfd_cell nct6694_devs[] = {
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+ MFD_CELL_NAME("nct6694-gpio"),
+
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+ MFD_CELL_NAME("nct6694-i2c"),
+
+ MFD_CELL_NAME("nct6694-canfd"),
+ MFD_CELL_NAME("nct6694-canfd"),
+
+ MFD_CELL_NAME("nct6694-wdt"),
+ MFD_CELL_NAME("nct6694-wdt"),
+
+ MFD_CELL_NAME("nct6694-hwmon"),
+
+ MFD_CELL_NAME("nct6694-rtc"),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(nct6694->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(nct6694->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(nct6694->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+/**
+ * nct6694_read_msg() - Read message from NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer to store the response data
+ *
+ * Sends a command to the NCT6694 device and reads the response.
+ * The command header is specified in @cmd_hd, and the response
+ * data is stored in @buf.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+
+ /* Send command packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+ sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+ sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+ le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
+ le16_to_cpu(cmd_hd->len), rx_len);
+ return -EIO;
+ }
+
+ return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_read_msg);
+
+/**
+ * nct6694_write_msg() - Write message to NCT6694 device
+ * @nct6694: NCT6694 device pointer
+ * @cmd_hd: command header structure
+ * @buf: buffer containing the data to be sent
+ *
+ * Sends a command to the NCT6694 device and writes the data
+ * from @buf. The command header is specified in @cmd_hd.
+ *
+ * Return: Negative value on error or 0 on success.
+ */
+int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+{
+ union nct6694_usb_msg *msg = nct6694->usb_msg;
+ struct usb_device *udev = nct6694->udev;
+ int tx_len, rx_len, ret;
+
+ guard(mutex)(&nct6694->access_lock);
+
+ memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+ msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+
+ /* Send command packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
+ sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Send data packet to USB device */
+ ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
+ le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive response packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
+ sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Receive data packet from USB device */
+ ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
+ le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ if (rx_len != le16_to_cpu(cmd_hd->len)) {
+ dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
+ le16_to_cpu(cmd_hd->len), rx_len);
+ return -EIO;
+ }
+
+ return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL_GPL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+ struct nct6694 *nct6694 = urb->context;
+ __le32 *status_le = urb->transfer_buffer;
+ u32 int_status;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default:
+ goto resubmit;
+ }
+
+ int_status = le32_to_cpu(*status_le);
+
+ 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_enable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ 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);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-irq",
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+ .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 ret;
+
+ nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+ if (!nct6694)
+ return -ENOMEM;
+
+ 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, sizeof(*nct6694->int_buffer), 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;
+
+ ida_init(&nct6694->gpio_ida);
+ ida_init(&nct6694->i2c_ida);
+ ida_init(&nct6694->canfd_ida);
+ ida_init(&nct6694->wdt_ida);
+
+ spin_lock_init(&nct6694->irq_lock);
+
+ ret = devm_mutex_init(dev, &nct6694->access_lock);
+ if (ret)
+ goto err_ida;
+
+ interface = iface->cur_altsetting;
+
+ int_endpoint = &interface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(int_endpoint)) {
+ ret = -ENODEV;
+ goto err_ida;
+ }
+
+ usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
+ nct6694, int_endpoint->bInterval);
+
+ ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ if (ret)
+ goto err_ida;
+
+ usb_set_intfdata(iface, nct6694);
+
+ ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
+ if (ret)
+ goto err_mfd;
+
+ return 0;
+
+err_mfd:
+ usb_kill_urb(nct6694->int_in_urb);
+err_ida:
+ ida_destroy(&nct6694->wdt_ida);
+ ida_destroy(&nct6694->canfd_ida);
+ ida_destroy(&nct6694->i2c_ida);
+ ida_destroy(&nct6694->gpio_ida);
+ irq_domain_remove(nct6694->domain);
+err_urb:
+ usb_free_urb(nct6694->int_in_urb);
+ return ret;
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+ struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+ mfd_remove_devices(nct6694->dev);
+ usb_kill_urb(nct6694->int_in_urb);
+ ida_destroy(&nct6694->wdt_ida);
+ ida_destroy(&nct6694->canfd_ida);
+ ida_destroy(&nct6694->i2c_ida);
+ ida_destroy(&nct6694->gpio_ida);
+ irq_domain_remove(nct6694->domain);
+ usb_free_urb(nct6694->int_in_urb);
+}
+
+static const struct usb_device_id nct6694_ids[] = {
+ { 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/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
index 819d19dc9b4a..0cb7af11ea60 100644
--- a/drivers/mfd/stmpe.c
+++ b/drivers/mfd/stmpe.c
@@ -1482,6 +1482,7 @@ int stmpe_probe(struct stmpe_client_info *ci, enum stmpe_partnum partnum)
return ret;
}
+EXPORT_SYMBOL_GPL(stmpe_probe);
void stmpe_remove(struct stmpe *stmpe)
{
@@ -1494,6 +1495,7 @@ void stmpe_remove(struct stmpe *stmpe)
mfd_remove_devices(stmpe->dev);
}
+EXPORT_SYMBOL_GPL(stmpe_remove);
static int stmpe_suspend(struct device *dev)
{
@@ -1517,3 +1519,7 @@ static int stmpe_resume(struct device *dev)
EXPORT_GPL_SIMPLE_DEV_PM_OPS(stmpe_dev_pm_ops,
stmpe_suspend, stmpe_resume);
+
+MODULE_DESCRIPTION("STMPE Core driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c
index fc2daffc4352..9399eb850ca2 100644
--- a/drivers/mfd/vexpress-sysreg.c
+++ b/drivers/mfd/vexpress-sysreg.c
@@ -5,6 +5,7 @@
*/
#include <linux/gpio/driver.h>
+#include <linux/gpio/generic.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mfd/core.h>
@@ -96,9 +97,11 @@ static struct mfd_cell vexpress_sysreg_cells[] = {
static int vexpress_sysreg_probe(struct platform_device *pdev)
{
+ struct gpio_generic_chip *mmc_gpio_chip;
+ struct gpio_generic_chip_config config;
struct resource *mem;
void __iomem *base;
- struct gpio_chip *mmc_gpio_chip;
+ int ret;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem)
@@ -116,10 +119,22 @@ static int vexpress_sysreg_probe(struct platform_device *pdev)
GFP_KERNEL);
if (!mmc_gpio_chip)
return -ENOMEM;
- bgpio_init(mmc_gpio_chip, &pdev->dev, 0x4, base + SYS_MCI,
- NULL, NULL, NULL, NULL, 0);
- mmc_gpio_chip->ngpio = 2;
- devm_gpiochip_add_data(&pdev->dev, mmc_gpio_chip, NULL);
+
+ config = (typeof(config)){
+ .dev = &pdev->dev,
+ .sz = 4,
+ .dat = base + SYS_MCI,
+ };
+
+ ret = gpio_generic_chip_init(mmc_gpio_chip, &config);
+ if (ret)
+ return ret;
+
+ mmc_gpio_chip->gc.ngpio = 2;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &mmc_gpio_chip->gc, NULL);
+ if (ret)
+ return ret;
return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
vexpress_sysreg_cells,