Devicetree
 help / color / mirror / Atom feed
From: Oleksij Rempel <o.rempel@pengutronix.de>
To: Guenter Roeck <linux@roeck-us.net>, Lee Jones <lee@kernel.org>,
	Linus Walleij <linusw@kernel.org>, Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, Peter Rosin <peda@axentia.se>
Cc: Oleksij Rempel <o.rempel@pengutronix.de>,
	kernel@pengutronix.de, linux-kernel@vger.kernel.org,
	devicetree@vger.kernel.org, linux-hwmon@vger.kernel.org,
	linux-gpio@vger.kernel.org, David Jander <david@protonic.nl>
Subject: [PATCH v15 3/6] mfd: mc33978: Add NXP MC33978/MC34978 MSDI support
Date: Fri, 10 Jul 2026 12:13:52 +0200	[thread overview]
Message-ID: <20260710101358.2606941-4-o.rempel@pengutronix.de> (raw)
In-Reply-To: <20260710101358.2606941-1-o.rempel@pengutronix.de>

Add the Multi-Function Device (MFD) driver for the NXP MC33978 and
MC34978 Multiple Switch Detection Interfaces (MSDI).

This driver binds to the SPI device, sequences the VDDQ (logic) and VBATP
(battery) regulators, delegates bus, regmap and irq_domain setup to
mc33978_core_init() from the core module added in the previous patch, and
instantiates the child devices (pinctrl, hwmon, mux) from match data.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
This patch depends on the preceding "platform: misc: add NXP MC33978/MC34978
core driver" patch, which provides mc33978_core_init() and the register
definitions in include/linux/mfd/mc33978.h. Without it this driver neither
builds nor links.

The child devices registered here (pinctrl, hwmon, mux) are added by the
follow-up patches in this series, so the devices stay unbound until those
are applied.

changes v15:
- Split from the core driver patch, as requested by Lee Jones. This patch
  now carries only the SPI binding, regulator power sequencing and child
  device registration.
- Follow the drivers/mfd/ subject convention: "mfd: <driver>: <Description>".
- Drop the "mfd" infix from the probe function, match tables and spi_driver
  names: mc33978_mfd_probe() -> mc33978_probe(), mc33978_mfd_of_match ->
  mc33978_of_match, mc33978_mfd_spi_id -> mc33978_spi_id and
  mc33978_mfd_driver -> mc33978_driver.
---
 drivers/mfd/Kconfig   |  16 ++++
 drivers/mfd/Makefile  |   2 +
 drivers/mfd/mc33978.c | 195 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 213 insertions(+)
 create mode 100644 drivers/mfd/mc33978.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782..f5eb315d705b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2559,6 +2559,22 @@ config MFD_UPBOARD_FPGA
 	  To compile this driver as a module, choose M here: the module will be
 	  called upboard-fpga.
 
+config MFD_MC33978
+	tristate "NXP MC33978/MC34978 MFD driver"
+	depends on OF && SPI
+	select MFD_CORE
+	select MC33978_CORE
+	help
+	  MFD driver for NXP MC33978/MC34978 industrial input controllers.
+
+	  The MC33978/MC34978 devices provide 22 switch detection inputs,
+	  analog multiplexing (AMUX), and comprehensive hardware fault
+	  detection. This driver provides IRQ domain, event processing,
+	  and interfaces for child devices (pinctrl, hwmon, mux).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called mc33978.
+
 config MFD_MAX7360
 	tristate "Maxim MAX7360 I2C IO Expander"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dd4bb7e77c33..690838edbe77 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -122,6 +122,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_MC33978)	+= mc33978.o
+
 obj-$(CONFIG_MFD_PF1550)	+= pf1550.o
 
 obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
diff --git a/drivers/mfd/mc33978.c b/drivers/mfd/mc33978.c
new file mode 100644
index 000000000000..541a391152ef
--- /dev/null
+++ b/drivers/mfd/mc33978.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 David Jander <david@protonic.nl>, Protonic Holland
+ * Copyright (C) 2026 Oleksij Rempel <kernel@pengutronix.de>, Pengutronix
+ *
+ * MC33978/MC34978 MFD Driver - Device binding and power sequencing only.
+ * Core logic (regmap/IRQ/events) lives in separate mc33978-core module to
+ * isolate complex SPI protocol from simple MFD device registration.
+ */
+
+#include <linux/delay.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/mc33978.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+/*
+ * MC33978 vs MC34978: Identical register map and pin configuration; only
+ * temperature range differs (MC33978: -40°C to +125°C, MC34978: -40°C to
+ * +105°C). Both variants share IRQ layout and resources. Device type
+ * preserved for potential errata handling and hwmon-specific attributes.
+ */
+enum mc33978_device_type {
+	MC33978 = 1,  /* Must be non-zero: used as match_data pointer value */
+	MC34978 = 2,
+};
+
+static const struct resource mc33978_hwmon_resources[] = {
+	DEFINE_RES_IRQ(MC33978_HWIRQ_FAULT),
+};
+
+static const struct mfd_cell mc33978_cells[] = {
+	MFD_CELL_NAME("mc33978-pinctrl"),
+	MFD_CELL_RES("mc33978-hwmon", mc33978_hwmon_resources),
+	MFD_CELL_NAME("mc33978-mux"),
+};
+
+static const struct mfd_cell mc34978_cells[] = {
+	MFD_CELL_NAME("mc34978-pinctrl"),
+	MFD_CELL_RES("mc34978-hwmon", mc33978_hwmon_resources),
+	MFD_CELL_NAME("mc34978-mux"),
+};
+
+struct mc33978_ddata {
+	struct irq_domain *domain;
+};
+
+static int mc33978_probe(struct spi_device *spi)
+{
+	unsigned long type;
+	const struct mfd_cell *cells;
+	struct device *dev = &spi->dev;
+	struct mc33978_ddata *ddata;
+	int num_cells;
+	int ret;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, ddata);
+
+	/* Power up chip: VDDQ first per datasheet sequencing requirements */
+	ret = devm_regulator_get_enable(dev, "vddq");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable VDDQ supply\n");
+
+	ret = devm_regulator_get_enable(dev, "vbatp");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable VBATP supply\n");
+
+	/*
+	 * Wait for power-on reset to complete. Datasheet specifies t_ACTIVE
+	 * (time from power-up to device operational) as max 450µs.
+	 */
+	fsleep(450);
+
+	/*
+	 * Core module creates regmap/IRQ domain/event handling. Separated
+	 * because custom SPI protocol needs complex regmap_bus implementation.
+	 *
+	 * API contract: mc33978_core_init() initializes domain_out to NULL on
+	 * entry. On success (return 0), domain_out is guaranteed to point to a
+	 * valid IRQ domain. On error, domain_out remains NULL. This allows
+	 * safe dereference of ddata->domain below without NULL check after
+	 * verifying ret == 0.
+	 */
+	ret = mc33978_core_init(dev, spi, &ddata->domain);
+	if (ret)
+		return ret;
+
+	/*
+	 * Validate IRQ domain size before passing to child devices.
+	 * hwirq_max is exclusive (represents size), we need MC33978_NUM_IRQS
+	 * entries (0..22, total 23).
+	 */
+	if (ddata->domain->hwirq_max < MC33978_NUM_IRQS)
+		return dev_err_probe(dev, -EINVAL,
+				     "IRQ domain has insufficient hwirqs (need %d)\n",
+				     MC33978_NUM_IRQS);
+
+	/*
+	 * Device type stored as pointer value in OF/SPI ID tables.
+	 * Explicit zero-check: spi_get_device_match_data() won't return NULL
+	 * since probe matched, but .data could be NULL if table misconfigured.
+	 */
+	type = (unsigned long)spi_get_device_match_data(spi);
+	if (!type)
+		return dev_err_probe(dev, -EINVAL, "missing device match data\n");
+
+	switch (type) {
+	case MC33978:
+		cells = mc33978_cells;
+		num_cells = ARRAY_SIZE(mc33978_cells);
+		break;
+	case MC34978:
+		cells = mc34978_cells;
+		num_cells = ARRAY_SIZE(mc34978_cells);
+		break;
+	default:
+		return dev_err_probe(dev, -ENODEV, "unknown device type\n");
+	}
+
+	/*
+	 * Child devices inherit IRQ domain for platform_get_irq(). devm cleanup
+	 * order is critical (LIFO - Last In, First Out):
+	 *
+	 * Teardown sequence:
+	 * 1. THIS: devm_mfd_add_devices() cleanup
+	 *    - Child devices removed, child devm_request_irq() freed
+	 * 2. Core: devm_request_threaded_irq() cleanup in mc33978_core_init()
+	 *    - Parent IRQ handler freed, no new events triggered
+	 * 3. Core: devm_add_action(mc33978_teardown) cleanup
+	 *    - event_work canceled via cancel_work_sync()
+	 * 4. Core: devm_regmap_init() cleanup
+	 *    - Regmap destroyed (safe: work stopped, IRQ freed)
+	 * 5. Core: devm_add_action(mc33978_irq_domain_remove) in mc33978_irq_init()
+	 *    - IRQ domain removed (safe: children gone, work stopped)
+	 *
+	 * Step 3 is critical: event_work accesses both regmap and IRQ domain.
+	 * cancel_work_sync() in mc33978_teardown() ensures the worker completes
+	 * before steps 4-5 destroy the resources it uses.
+	 *
+	 * Core module manually calls irq_dispose_mapping() for all hwirqs before
+	 * irq_domain_remove() because free_irq() doesn't dispose mappings, and
+	 * irq_domain_remove() expects an empty radix tree.
+	 */
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO,
+				   cells, num_cells,
+				   NULL, 0, ddata->domain);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to add MFD child devices\n");
+
+	return 0;
+}
+
+static const struct of_device_id mc33978_of_match[] = {
+	{ .compatible = "nxp,mc33978", .data = (void *)MC33978 },
+	{ .compatible = "nxp,mc34978", .data = (void *)MC34978 },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mc33978_of_match);
+
+static const struct spi_device_id mc33978_spi_id[] = {
+	{ .name = "mc33978", .driver_data = MC33978 },
+	{ .name = "mc34978", .driver_data = MC34978 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, mc33978_spi_id);
+
+static struct spi_driver mc33978_driver = {
+	.driver = {
+		.name = "mc33978",
+		.of_match_table = mc33978_of_match,
+		/*
+		 * Suppress bind/unbind via sysfs. The pinctrl child driver has
+		 * suppress_bind_attrs=true due to a pinctrl subsystem bug, which
+		 * means this MFD parent cannot be unbound either (device links
+		 * require all children to unbind first).
+		 */
+		.suppress_bind_attrs = true,
+	},
+	.probe = mc33978_probe,
+	.id_table = mc33978_spi_id,
+};
+module_spi_driver(mc33978_driver);
+
+MODULE_AUTHOR("David Jander <david@protonic.nl>");
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
+MODULE_DESCRIPTION("NXP MC33978/MC34978 MFD driver");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: mc33978-core");
-- 
2.47.3


  parent reply	other threads:[~2026-07-10 10:14 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-10 10:13 [PATCH v15 0/6] mfd: Add support for NXP MC33978/MC34978 MSDI Oleksij Rempel
2026-07-10 10:13 ` [PATCH v15 1/6] dt-bindings: pinctrl: add " Oleksij Rempel
2026-07-10 10:19   ` sashiko-bot
2026-07-10 10:13 ` [PATCH v15 2/6] platform: misc: add NXP MC33978/MC34978 core driver Oleksij Rempel
2026-07-10 10:24   ` sashiko-bot
2026-07-10 10:13 ` Oleksij Rempel [this message]
2026-07-10 10:25   ` [PATCH v15 3/6] mfd: mc33978: Add NXP MC33978/MC34978 MSDI support sashiko-bot
2026-07-10 10:13 ` [PATCH v15 4/6] pinctrl: add NXP MC33978/MC34978 pinctrl driver Oleksij Rempel
2026-07-10 10:27   ` sashiko-bot
2026-07-10 15:22   ` Alvin Šipraga
2026-07-11  5:21     ` Oleksij Rempel
2026-07-10 10:13 ` [PATCH v15 5/6] hwmon: add NXP MC33978/MC34978 driver Oleksij Rempel
2026-07-10 10:23   ` sashiko-bot
2026-07-10 10:13 ` [PATCH v15 6/6] mux: add NXP MC33978/MC34978 AMUX driver Oleksij Rempel
2026-07-10 10:25   ` sashiko-bot
2026-07-10 12:53   ` Alvin Šipraga

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260710101358.2606941-4-o.rempel@pengutronix.de \
    --to=o.rempel@pengutronix.de \
    --cc=conor+dt@kernel.org \
    --cc=david@protonic.nl \
    --cc=devicetree@vger.kernel.org \
    --cc=kernel@pengutronix.de \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=linusw@kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=peda@axentia.se \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox