* Re: [PATCH v9 1/6] dt-bindings: pinctrl: add NXP MC33978/MC34978 MSDI
From: Rob Herring (Arm) @ 2026-03-31 18:32 UTC (permalink / raw)
To: Oleksij Rempel
Cc: Linus Walleij, linux-gpio, kernel, Conor Dooley, Peter Rosin,
devicetree, Krzysztof Kozlowski, David Jander, linux-kernel,
linux-hwmon, Guenter Roeck, Lee Jones
In-Reply-To: <20260331171612.102018-2-o.rempel@pengutronix.de>
On Tue, 31 Mar 2026 19:16:07 +0200, Oleksij Rempel wrote:
> Add device tree binding documentation for the NXP MC33978 and MC34978
> Multiple Switch Detection Interface (MSDI) devices.
>
> The MC33978 and MC34978 differ primarily in their operating temperature
> ranges. While not software-detectable, providing specific compatible
> strings allows the hwmon subsystem to correctly interpret thermal
> thresholds and hardware faults.
>
> These ICs monitor up to 22 mechanical switch contacts in automotive and
> industrial environments. They provide configurable wetting currents to
> break through contact oxidation and feature extensive hardware
> protection against thermal overload and voltage transients (load
> dumps/brown-outs).
>
> The device interfaces via SPI. While it provides multiple functions, its
> primary hardware purpose is pin/switch control. To accurately represent
> the hardware as a single physical integrated circuit without unnecessary
> DT overhead, all functions are flattened into a single pinctrl node:
> - pinctrl: Exposing the 22 switch inputs (SG/SP pins) as a GPIO controller
> and managing their pin configurations.
> - hwmon: Exposing critical hardware faults (OT, OV, UV) and static
> voltage/temperature thresholds.
> - mux: Controlling the 24-to-1 analog multiplexer to route pin voltages,
> internal temperature, or battery voltage to an external SoC ADC.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
> Reviewed-by: Linus Walleij <linusw@kernel.org>
> ---
> changes v9:
> - no changes
> changes v8:
> - Update IRQ_TYPE_* macros include path reference in documentation from
> interrupt-controller.h to dt-bindings/interrupt-controller/irq.h.
> - Add bias-disable, drive-open-drain, drive-open-source, and drive-strength
> to the list of supported pin configuration properties.
> changes v7:
> - no changes
> changes v6:
> - add Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
> - add Reviewed-by: Linus Walleij <linusw@kernel.org>
> changes v5:
> - Commit Message: Added justification for distinct compatible strings
> based on temperature ranges.
> - Restricted pins property to an explicit enum of valid hardware pins
> changes v4:
> - Drop the standalone mfd/nxp,mc33978.yaml schema entirely.
> - Move the unified device binding to bindings/pinctrl/nxp,mc33978.yaml,
> - Remove the dedicated child node compatible strings (nxp,mc33978-pinctrl).
> - Flatten the pinctrl/gpio properties directly into the main SPI device
> node.
> changes v3:
> - Drop regular expression pattern from pinctrl child node and define
> it as a standard property
> - Reorder required properties list in MFD binding
> - Remove stray blank line from the MFD binding devicetree example
> - Replace unevaluatedProperties with additionalProperties in the pinctrl
> binding
> changes v2:
> - Squashed MFD, pinctrl, hwmon, and mux bindings into a single patch
> - Removed the empty hwmon child node
> - Folded the mux-controller node into the parent MFD node
> - Added vbatp-supply and vddq-supply to the required properties block
> - Changed the example node name from mc33978@0 to gpio@0
> - Removed unnecessary literal block scalars (|) from descriptions
> - Documented SG, SP, and SB pin acronyms in the pinctrl description
> - Added consumer polarity guidance (GPIO_ACTIVE_LOW/HIGH) for SG/SB
> inputs, with a note on output circuit dependency
> - Updated commit message
> ---
> .../bindings/pinctrl/nxp,mc33978.yaml | 158 ++++++++++++++++++
> 1 file changed, 158 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pinctrl/nxp,mc33978.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/pinctrl/nxp,mc33978.example.dtb: gpio@0 (nxp,mc33978): $nodename:0: 'gpio@0' does not match '^mux-controller(@.*|-([0-9]|[1-9][0-9]+))?$'
from schema $id: http://devicetree.org/schemas/mux/mux-controller.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260331171612.102018-2-o.rempel@pengutronix.de
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* Re: [PATCH v4 3/3] ARM: dts: qcom: msm8960: expressatt: Add camera flash
From: Dmitry Baryshkov @ 2026-03-31 18:24 UTC (permalink / raw)
To: guptarud
Cc: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bjorn Andersson, Konrad Dybcio,
Liam Girdwood, Mark Brown, linux-leds, devicetree, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg, Konrad Dybcio
In-Reply-To: <20260331-expressatt_camera_flash-v4-3-f1e99f474513@gmail.com>
On Tue, Mar 31, 2026 at 10:08:09AM -0700, Rudraksha Gupta via B4 Relay wrote:
> From: Rudraksha Gupta <guptarud@gmail.com>
>
> Add camera flash support for the Samsung Galaxy Express (expressatt).
>
> The flash IC uses a one-wire pulse-count protocol on GPIO 3, powered
> by a GPIO-controlled fixed regulator on PMIC MPP 4. The regulator is
> modeled as a regulator-fixed node and supplied to the flash IC via
> vin-supply.
>
> Downstream references:
> Link: https://github.com/LineageOS/android_kernel_samsung_d2/blob/stable/cm-12.0-YNG4N/drivers/leds/Makefile#L51
> Link: https://github.com/LineageOS/android_kernel_samsung_d2/blob/stable/cm-12.0-YNG4N/arch/arm/mach-msm/board-apexq-camera.c#L591
>
> Assisted-by: Claude:claude-opus-4.6
> Reviewed-by: David Heidelberg <david@ixit.cz>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> Signed-off-by: Rudraksha Gupta <guptarud@gmail.com>
> ---
> .../dts/qcom/qcom-msm8960-samsung-expressatt.dts | 43 ++++++++++++++++++++++
> 1 file changed, 43 insertions(+)
>
> diff --git a/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts b/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
> index c4b98af6955d..35514fd53e3d 100644
> --- a/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
> +++ b/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
> @@ -1,5 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0
> #include <dt-bindings/input/input.h>
> +#include <dt-bindings/leds/common.h>
> #include <dt-bindings/reset/qcom,gcc-msm8960.h>
>
> #include "qcom-msm8960.dtsi"
> @@ -61,6 +62,32 @@ touchkey_enable: touchkey-enable {
> regulator-boot-on;
> };
>
> + vreg_flash: regulator-flash {
> + compatible = "regulator-fixed";
> + regulator-name = "VREG_FLASH_3P3";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + gpio = <&pm8921_mpps 4 GPIO_ACTIVE_HIGH>;
> + enable-active-high;
> + pinctrl-0 = <&flash_led_unlock>;
> + pinctrl-names = "default";
> + };
> +
> + led-controller {
It looks like the nodes are not sorted. Could you please make sure that
they are sorted alphanumerically (if there is no node address)?
> + compatible = "richtek,rt8515";
> + enf-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>;
> + vin-supply = <&vreg_flash>;
> + richtek,rfs-ohms = <16000>;
> + pinctrl-0 = <&cam_flash_en>;
> + pinctrl-names = "default";
> +
> + led {
> + function = LED_FUNCTION_FLASH;
> + color = <LED_COLOR_ID_WHITE>;
> + flash-max-timeout-us = <250000>;
> + };
> + };
> +
> i2c-gpio-touchkey {
> compatible = "i2c-gpio";
> #address-cells = <1>;
> @@ -172,6 +199,13 @@ touchscreen@4a {
> };
>
> &tlmm {
> + cam_flash_en: cam-flash-en-state {
> + pins = "gpio3";
> + function = "gpio";
> + drive-strength = <16>;
> + bias-pull-down;
> + };
> +
> spi1_default: spi1-default-state {
> mosi-pins {
> pins = "gpio6";
> @@ -572,3 +606,12 @@ magnetometer@2e {
> /* TODO: Figure out Mount Matrix */
> };
> };
> +
> +&pm8921_mpps {
> + flash_led_unlock: flash-led-unlock-state {
> + pins = "mpp4";
> + function = "digital";
> + output-low;
> + power-source = <PM8921_GPIO_S4>;
> + };
> +};
>
> --
> 2.53.0
>
>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH 7/8] mfd: omap-usb-host: Add pbias regulator support
From: Thomas Richard @ 2026-03-31 18:19 UTC (permalink / raw)
To: Lee Jones
Cc: Aaro Koskinen, Andreas Kemnade, Kevin Hilman, Roger Quadros,
Tony Lindgren, Liam Girdwood, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thomas Petazzoni, linux-omap,
linux-kernel, devicetree
In-Reply-To: <20260331165537.GJ3795166@google.com>
Hello Lee,
On 3/31/26 6:55 PM, Lee Jones wrote:
> On Mon, 23 Mar 2026, Thomas Richard wrote:
>
>> Add pbias regulator support to enable SIM_VDDS supply and unlock USB I/O
>> cell. Previously, this was handled by the bootloader, now the kernel can
>> take responsibility for managing the PBIAS regulator, ensuring correct
>> operation regardless of the bootloader.
>>
>> Signed-off-by: Thomas Richard <thomas.richard@bootlin.com>
>> ---
>> drivers/mfd/omap-usb-host.c | 41 ++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 40 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c
>> index ac974285be341fa579ef198d1893b77af428b5f8..9e254e00183e940b775d5bde6e891f0d26af27b0 100644
>> --- a/drivers/mfd/omap-usb-host.c
>> +++ b/drivers/mfd/omap-usb-host.c
>> @@ -15,6 +15,9 @@
>> #include <linux/pm_runtime.h>
>> #include <linux/of.h>
>> #include <linux/of_platform.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/string_choices.h>
>> +
>
> ?
>
>>
>> #include "omap-usb.h"
>>
>> @@ -95,6 +98,8 @@ struct usbhs_hcd_omap {
>> struct usbhs_omap_platform_data *pdata;
>>
>> u32 usbhs_rev;
>> +
>> + struct regulator *pbias;
>> };
>> /*-------------------------------------------------------------------------*/
>>
>> @@ -270,6 +275,25 @@ static bool is_ohci_port(enum usbhs_omap_port_mode pmode)
>> }
>> }
>>
>> +static int omap_usbhs_set_pbias(struct device *dev, bool power_on)
>> +{
>> + struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
>> + int ret;
>> +
>> + if (!omap->pbias)
>> + return 0;
>> +
>> + if (power_on)
>> + ret = regulator_enable(omap->pbias);
>> + else
>> + ret = regulator_disable(omap->pbias);
>> +
>> + if (ret)
>> + dev_err(dev, "pbias reg %s failed\n", str_enable_disable(power_on));
>> +
>> + return ret;
>> +}
>> +
>> static int usbhs_runtime_resume(struct device *dev)
>> {
>> struct usbhs_hcd_omap *omap = dev_get_drvdata(dev);
>> @@ -278,6 +302,10 @@ static int usbhs_runtime_resume(struct device *dev)
>>
>> dev_dbg(dev, "usbhs_runtime_resume\n");
>>
>> + r = omap_usbhs_set_pbias(dev, true);
>> + if (r)
>> + return r;
>> +
>> omap_tll_enable(pdata);
>>
>> if (!IS_ERR(omap->ehci_logic_fck))
>> @@ -355,7 +383,7 @@ static int usbhs_runtime_suspend(struct device *dev)
>>
>> omap_tll_disable(pdata);
>>
>> - return 0;
>> + return omap_usbhs_set_pbias(dev, false);
>> }
>>
>> static unsigned omap_usbhs_rev1_hostconfig(struct usbhs_hcd_omap *omap,
>> @@ -564,6 +592,11 @@ static int usbhs_omap_probe(struct platform_device *pdev)
>>
>> omap->pdata = pdata;
>>
>> + omap->pbias = devm_regulator_get_optional(dev, "pbias");
>> + if (IS_ERR(omap->pbias))
>> + return dev_err_probe(dev, PTR_ERR(omap->pbias),
>> + "unable to get pbias regulator\n");
>
> You need to check for '-ENODEV' here or you are ignoring the optional part.
>
>> +
>> /* Initialize the TLL subsystem */
>> omap_tll_init(pdata);
>>
>> @@ -759,6 +792,10 @@ static int usbhs_omap_probe(struct platform_device *pdev)
>> }
>>
>> initialize:
>> + ret = omap_usbhs_set_pbias(dev, true);
>> + if (ret)
>> + goto err_mem;
>
> Since this regulator is also managed by 'usbhs_runtime_resume' and
> 'usbhs_runtime_suspend', could manually enabling it here in probe and
> disabling it in remove interfere with the reference counting during runtime
> PM transitions? Should we consider relying entirely on runtime PM to manage
> its state?
>
Thanks a lot for the review. I already sent a v2, do not spend your time
on this version. This comment is also valid for v2 (and the headers
one), but I fixed -ENODEV in v2.
Best Regards,
Thomas
^ permalink raw reply
* Re: [PATCH v4 2/3] leds: flash: rt8515: Support single-GPIO flash ICs with vin supply
From: Linus Walleij @ 2026-03-31 18:17 UTC (permalink / raw)
To: guptarud
Cc: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Konrad Dybcio, Liam Girdwood,
Mark Brown, linux-leds, devicetree, linux-kernel, linux-arm-msm,
phone-devel
In-Reply-To: <20260331-expressatt_camera_flash-v4-2-f1e99f474513@gmail.com>
On Tue, Mar 31, 2026 at 7:08 PM Rudraksha Gupta via B4 Relay
<devnull+guptarud.gmail.com@kernel.org> wrote:
> From: Rudraksha Gupta <guptarud@gmail.com>
>
> Extend the RT8515 driver to support flash ICs that use only a single
> GPIO for both flash and torch modes (no separate ENT pin), with an
> optional vin regulator that gates power to the flash IC.
>
> When vin-supply is provided, the driver enables the regulator before
> activating the LED and disables it when turning off.
>
> Make ent-gpios optional and validate at probe time that exactly one of
> ent-gpios or vin-supply is provided. When ent-gpios is absent, the
> driver uses enf-gpios for both flash and torch brightness control.
>
> Assisted-by: Claude:claude-opus-4.6
> Signed-off-by: Rudraksha Gupta <guptarud@gmail.com>
Excellent this looks good to me!
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply
* Re: [PATCH v6 00/10] Add GPCDMA support in Tegra264
From: Jon Hunter @ 2026-03-31 18:06 UTC (permalink / raw)
To: Akhil R, Vinod Koul, Frank Li, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Thierry Reding, Laxman Dewangan, Philipp Zabel,
dmaengine, devicetree, linux-tegra, linux-kernel
In-Reply-To: <20260331102303.33181-1-akhilrajeev@nvidia.com>
On 31/03/2026 11:22, Akhil R wrote:
> This series adds support for GPCDMA in Tegra264 with additional
> support for separate stream ID for each channel. Tegra264 GPCDMA
> controller has changes in the register offsets and uses 41-bit
> addressing for memory. Add changes in the tegra186-gpc-dma driver
> to support these.
>
> v5->v6:
> - Replace dev_err() with dev_err_probe() in the probe function for fixed
> return values also.
> v4->v5:
> - Use dev_err_probe() when returning error from the probe function.
> - Remove tegra194 and tegra234 compatible from the reset 'if' condition
> in the bindings as suggested in v2 (which I missed).
> v3->v4:
> - Split device tree changes to two patches.
> - Reordered patches to have fixes first.
> - Added fixes tag to dt-bindings and device tree changes.
> v2->v3:
> - Add description for iommu-map property and update commit descriptions.
> - Use enum for compatible string instead of const.
> - Remove unused registers from struct tegra_dma_channel_regs.
> - Use devm_of_dma_controller_register() to register the DMA controller.
> - Remove return value check for mask setting in the driver as the bitmask
> value is always greater than 32.
> v1->v2:
> - Fix dt_bindings_check warnings
> - Drop fallback compatible "nvidia,tegra186-gpcdma" from Tegra264 DT
> - Use dma_addr_t for sg_req src/dst fields and drop separate high_add
> variable and check for the addr_bits only when programming the
> registers.
> - Update address width to 39 bits for Tegra234 and before since the SMMU
> supports only up to 39 bits till Tegra234.
> - Add a patch to do managed DMA controller registration.
> - Describe the second iteration in the probe.
> - Update commit descriptions.
>
> Akhil R (10):
> dt-bindings: dma: nvidia,tegra186-gpc-dma: Make reset optional
> arm64: tegra: Remove fallback compatible for GPCDMA
> dt-bindings: dma: nvidia,tegra186-gpc-dma: Add iommu-map property
> dmaengine: tegra: Make reset control optional
> dmaengine: tegra: Use struct for register offsets
> dmaengine: tegra: Support address width > 39 bits
> dmaengine: tegra: Use managed DMA controller registration
> dmaengine: tegra: Use iommu-map for stream ID
> dmaengine: tegra: Add Tegra264 support
> arm64: tegra: Enable GPCDMA in Tegra264 and add iommu-map
>
> .../bindings/dma/nvidia,tegra186-gpc-dma.yaml | 32 +-
> .../arm64/boot/dts/nvidia/tegra264-p3834.dtsi | 4 +
> arch/arm64/boot/dts/nvidia/tegra264.dtsi | 3 +-
> drivers/dma/tegra186-gpc-dma.c | 429 +++++++++++-------
> 4 files changed, 284 insertions(+), 184 deletions(-)
>
For the series ...
Reviewed-by: Jon Hunter <jonathanh@nvidia.com>
Thanks!
Jon
--
nvpublic
^ permalink raw reply
* Re: [PATCH 2/2] arm64: dts: qcom: glymur: Add qfprom efuse node
From: Dmitry Baryshkov @ 2026-03-31 18:02 UTC (permalink / raw)
To: Pankaj Patil
Cc: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Bjorn Andersson, Konrad Dybcio, linux-arm-msm,
devicetree, linux-kernel
In-Reply-To: <20260331-glymur-qfprom-v1-2-5b4284d23c80@oss.qualcomm.com>
On Tue, Mar 31, 2026 at 07:24:21PM +0530, Pankaj Patil wrote:
> Add the qfprom (Qualcomm Fuse ROM) efuse node and gpu speed bin child
> node for Glymur SoC
>
> Signed-off-by: Pankaj Patil <pankaj.patil@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/glymur.dtsi | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH RFC 2/2] arm64: dts: qcom: eliza-mtp: Enable DSI display panel
From: Dmitry Baryshkov @ 2026-03-31 18:01 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, devicetree, linux-kernel, Abel Vesa
In-Reply-To: <20260331-dts-qcom-eliza-display-v1-2-856f0b66b282@oss.qualcomm.com>
On Tue, Mar 31, 2026 at 04:02:50PM +0200, Krzysztof Kozlowski wrote:
> Enable display on Eliza MTP board with Visionox VTDR6130 panel.
>
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/eliza-mtp.dts | 63 ++++++++++++++++++++++++++++++++++
> 1 file changed, 63 insertions(+)
>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH RFC 1/2] arm64: dts: qcom: eliza: Add display (MDSS) with Display CC
From: Dmitry Baryshkov @ 2026-03-31 18:01 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, devicetree, linux-kernel, Abel Vesa
In-Reply-To: <20260331-dts-qcom-eliza-display-v1-1-856f0b66b282@oss.qualcomm.com>
On Tue, Mar 31, 2026 at 04:02:49PM +0200, Krzysztof Kozlowski wrote:
> Add device nodes for almost entire display: MDSS, DPU, DSI, DSI PHYs,
> DisplayPort and Display Clock Controller.
>
> Missing pieces are HDMI PHY and HDMI controller.
>
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH v20 06/10] power: reset: Add psci-reboot-mode driver
From: Shivendra Pratap @ 2026-03-31 18:00 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Arnd Bergmann, Bjorn Andersson, Sebastian Reichel, Rob Herring,
Souvik Chakravarty, Krzysztof Kozlowski, Andy Yan,
Matthias Brugger, Mark Rutland, Conor Dooley, Konrad Dybcio,
John Stultz, Moritz Fischer, Bartosz Golaszewski, Sudeep Holla,
Florian Fainelli, Krzysztof Kozlowski, Dmitry Baryshkov,
Mukesh Ojha, Andre Draszik, Kathiravan Thirumoorthy, linux-pm,
linux-kernel, linux-arm-kernel, linux-arm-msm, devicetree,
Srinivas Kandagatla
In-Reply-To: <acaMPgRALnoUIHMC@lpieralisi>
On 27-03-2026 19:25, Lorenzo Pieralisi wrote:
> On Wed, Mar 04, 2026 at 11:33:06PM +0530, Shivendra Pratap wrote:
>> PSCI supports different types of resets like COLD reset, ARCH WARM
>> reset, vendor-specific resets. Currently there is no common driver that
>> handles all supported psci resets at one place. Additionally, there is
>> no common mechanism to issue the supported psci resets from userspace.
>>
>> Add a PSCI reboot mode driver and define two types of PSCI resets in the
>> driver as reboot-modes: predefined resets controlled by Linux
>> reboot_mode and customizable resets defined by SoC vendors in their
>> device tree under the psci:reboot-mode node.
>>
>> Register the driver with the reboot-mode framework to interface these
>> resets to userspace. When userspace initiates a supported command, pass
>> the reset arguments to the PSCI driver to enable command-based reset.
>>
>> This change allows userspace to issue supported PSCI reset commands
>> using the standard reboot system calls while enabling SoC vendors to
>> define their specific resets for PSCI.
>>
>> Signed-off-by: Shivendra Pratap <shivendra.pratap@oss.qualcomm.com>
>> ---
>> drivers/power/reset/Kconfig | 10 +++
>> drivers/power/reset/Makefile | 1 +
>> drivers/power/reset/psci-reboot-mode.c | 119 +++++++++++++++++++++++++++++++++
>> 3 files changed, 130 insertions(+)
>>
>> diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
>> index f6c1bcbb57deff3568d6b1b326454add3b3bbf06..529d6c7d3555601f7b7e6199acd29838030fcef2 100644
>> --- a/drivers/power/reset/Kconfig
>> +++ b/drivers/power/reset/Kconfig
>> @@ -348,6 +348,16 @@ config NVMEM_REBOOT_MODE
>> then the bootloader can read it and take different
>> action according to the mode.
>>
>> +config PSCI_REBOOT_MODE
>> + bool "PSCI reboot mode driver"
>> + depends on OF && ARM_PSCI_FW
>> + select REBOOT_MODE
>> + help
>> + Say y here will enable PSCI reboot mode driver. This gets
>> + the PSCI reboot mode arguments and passes them to psci
>> + driver. psci driver uses these arguments for issuing
>> + device reset into different boot states.
>> +
>> config POWER_MLXBF
>> tristate "Mellanox BlueField power handling driver"
>> depends on (GPIO_MLXBF2 || GPIO_MLXBF3) && ACPI
>> diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
>> index 0e4ae6f6b5c55729cf60846d47e6fe0fec24f3cc..49774b42cdf61fd57a5b70f286c65c9d66bbc0cb 100644
>> --- a/drivers/power/reset/Makefile
>> +++ b/drivers/power/reset/Makefile
>> @@ -40,4 +40,5 @@ obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
>> obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
>> obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
>> obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o
>> +obj-$(CONFIG_PSCI_REBOOT_MODE) += psci-reboot-mode.o
>> obj-$(CONFIG_POWER_MLXBF) += pwr-mlxbf.o
>> diff --git a/drivers/power/reset/psci-reboot-mode.c b/drivers/power/reset/psci-reboot-mode.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..86bef195228b0924704c2936b99f6801c14ff1b1
>> --- /dev/null
>> +++ b/drivers/power/reset/psci-reboot-mode.c
>> @@ -0,0 +1,119 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +
>> +#include <linux/device/faux.h>
>> +#include <linux/device.h>
>
> Nit: swap the two.
Ack. thanks.
>> +#include <linux/err.h>
>> +#include <linux/of.h>
>> +#include <linux/psci.h>
>> +#include <linux/reboot.h>
>> +#include <linux/reboot-mode.h>
>> +#include <linux/types.h>
>> +
>> +/*
>> + * Predefined reboot-modes are defined as per the values
>> + * of enum reboot_mode defined in the kernel: reboot.c.
>> + */
>> +static struct mode_info psci_resets[] = {
>> + { .mode = "warm", .magic = REBOOT_WARM},
>> + { .mode = "soft", .magic = REBOOT_SOFT},
>> + { .mode = "cold", .magic = REBOOT_COLD},
>> +};
>> +
>> +static void psci_reboot_mode_set_predefined_modes(struct reboot_mode_driver *reboot)
>> +{
>> + INIT_LIST_HEAD(&reboot->predefined_modes);
>> + for (u32 i = 0; i < ARRAY_SIZE(psci_resets); i++) {
>> + /* Prepare the magic with arg1 as 0 and arg2 as per pre-defined mode */
>> + psci_resets[i].magic = REBOOT_MODE_MAGIC(0, psci_resets[i].magic);
>
> This looks weird to me, why can't we just initialize the array with the values
> directly ?
Ack. The idea was to avoid Typecasting. Will check on this.
>> + INIT_LIST_HEAD(&psci_resets[i].list);
>> + list_add_tail(&psci_resets[i].list, &reboot->predefined_modes);
>> + }
>> +}
>> +
>> +/*
>> + * arg1 is reset_type(Low 32 bit of magic).
>> + * arg2 is cookie(High 32 bit of magic).
>> + * If reset_type is 0, cookie will be used to decide the reset command.
>> + */
>> +static int psci_reboot_mode_write(struct reboot_mode_driver *reboot, u64 magic)
>> +{
>> + u32 reset_type = REBOOT_MODE_ARG1(magic);
>> + u32 cookie = REBOOT_MODE_ARG2(magic);
>> +
>> + if (reset_type == 0) {
>> + if (cookie == REBOOT_WARM || cookie == REBOOT_SOFT)
>> + psci_set_reset_cmd(true, 0, 0);
>> + else
>> + psci_set_reset_cmd(false, 0, 0);
>> + } else {
>> + psci_set_reset_cmd(true, reset_type, cookie);
>> + }
>
> I don't think that psci_set_reset_cmd() has the right interface (and this
> nested if is too complicated for my taste). All we need to pass is reset-type
> and cookie (and if the reset is one of the predefined ones, reset-type is 0
> and cookie is the REBOOT_* cookie).
>
> Then the PSCI firmware driver will take the action according to what
> resets are available.
>
> How does it sound ?
So we mean these checks will move to the psci driver? Sorry for
re-iterating the question.
>> +
>> + return NOTIFY_DONE;
>> +}
>> +
>> +static int psci_reboot_mode_register_device(struct faux_device *fdev)
>> +{
>> + struct reboot_mode_driver *reboot;
>> + int ret;
>> +
>> + reboot = devm_kzalloc(&fdev->dev, sizeof(*reboot), GFP_KERNEL);
>> + if (!reboot)
>> + return -ENOMEM;
>> +
>> + psci_reboot_mode_set_predefined_modes(reboot);
>> + reboot->write = psci_reboot_mode_write;
>> + reboot->dev = &fdev->dev;
>> +
>> + ret = devm_reboot_mode_register(&fdev->dev, reboot);
>> + if (ret) {
>> + dev_err_probe(&fdev->dev, ret, "devm_reboot_mode_register failed %d\n", ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int __init psci_reboot_mode_init(void)
>> +{
>> + struct device_node *psci_np;
>> + struct faux_device *fdev;
>> + struct device_node *np;
>> + int ret;
>> +
>> + psci_np = of_find_compatible_node(NULL, NULL, "arm,psci-1.0");
>> + if (!psci_np)
>> + return -ENODEV;
>> + /*
>> + * Look for reboot-mode in the psci node. Even if the reboot-mode
>> + * node is not defined in psci, continue to register with the
>> + * reboot-mode driver and let the dev.ofnode be set as NULL.
>> + */
>> + np = of_find_node_by_name(psci_np, "reboot-mode");
>> +
>> + fdev = faux_device_create("psci-reboot-mode", NULL, NULL);
>
> Same comment as Bartosz (have you picked up his work and working towards
> a solution) ?
Working on this via MFD. Some issue as again MFD framework does not
allows a fwnode based driver registration. Will update.
thanks,
Shivendra
^ permalink raw reply
* [PATCH] ARM: dts: qcom: msm8960: expressatt: Add MAX17048 fuel gauge
From: Rudraksha Gupta via B4 Relay @ 2026-03-31 18:00 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, Rudraksha Gupta
From: Rudraksha Gupta <guptarud@gmail.com>
Add MAX17048 fuel gauge support.
Tested by comparing battery capacity readings between upstream (mainline
max17040 driver) and downstream (Samsung max17048_fuelgauge driver)
across a full discharge cycle. Upstream reads ~3% lower throughout. Both
track the discharge curve correctly:
Upstream: 95 92 88 87 86 87 83 82 80 68 60 55 50 45 40 35 30 20 16 10 10 5 5 1
Downstream: 95 94 92 91 91 89 87 86 84 73 64 59 51 48 43 38 33 23 17 14 12 8 6 3
Each pair of readings was collected by checking the upstream capacity
first, then moving the battery to a second expressatt running downstream
Android to check its capacity. The battery was then moved back to the
upstream device for the next reading. This swap occasionally caused the
upstream capacity to read slightly higher than the previous value
(e.g. 86 -> 87). When this happened, the reading was retaken after the
value settled.
Link: https://github.com/LineageOS/android_kernel_samsung_d2/blob/stable/cm-11.0-XNG3C/arch/arm/mach-msm/board-apexq-battery.c
Link: https://github.com/LineageOS/android_kernel_samsung_d2/blob/stable/cm-11.0-XNG3C/drivers/battery/Makefile#L5
Link: https://github.com/LineageOS/android_kernel_samsung_d2/blob/stable/cm-11.0-XNG3C/arch/arm/mach-msm/Makefile#L308
Signed-off-by: Rudraksha Gupta <guptarud@gmail.com>
---
Tested by doing `cat /sys/class/power_supply/battery/capacity` in
upstream Linux and comparing the value with downstream Linux. Booted
on upstream Linux first, as the upstream Linux seems to use a lot
more battery than downstream, and then put the battery into another
Expressatt running downstream Android to compare values. There are
some slight differences, but overall seems to line up pretty well with
downstream.
---
.../dts/qcom/qcom-msm8960-samsung-expressatt.dts | 40 ++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts b/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
index 82f8a4e10c6f..638124fb3922 100644
--- a/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
+++ b/arch/arm/boot/dts/qcom/qcom-msm8960-samsung-expressatt.dts
@@ -143,6 +143,32 @@ vdd_haptics: vdd-haptics-regulator {
pinctrl-names = "default";
pinctrl-0 = <&haptics_pwr_en>;
};
+
+ /* Fuel gauge (MAX17048) on i2c-gpio 24/25. Alert on GPIO 67. */
+ i2c-fuelgauge {
+ compatible = "i2c-gpio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ sda-gpios = <&tlmm 24 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&tlmm 25 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ pinctrl-0 = <&fuelgauge_i2c_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ i2c-gpio,delay-us = <2>;
+
+ fuel-gauge@36 {
+ compatible = "maxim,max17048";
+ reg = <0x36>;
+ maxim,double-soc;
+ maxim,rcomp = /bits/ 8 <0x62>;
+ maxim,alert-low-soc-level = <2>;
+ interrupt-parent = <&tlmm>;
+ interrupts = <67 IRQ_TYPE_EDGE_FALLING>;
+ pinctrl-0 = <&fuelgauge_alert_pin>;
+ pinctrl-names = "default";
+ wakeup-source;
+ };
+ };
};
&gsbi2 {
@@ -281,6 +307,13 @@ touchscreen: touchscreen-int-state {
drive-strength = <2>;
};
+ fuelgauge_i2c_pins: fuelgauge-i2c-state {
+ pins = "gpio24", "gpio25";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
nfc_default: nfc-default-state {
irq-pins {
pins = "gpio106";
@@ -325,6 +358,13 @@ touchkey_irq_pin: touchkey-irq-state {
drive-strength = <2>;
bias-disable;
};
+
+ fuelgauge_alert_pin: fuelgauge-alert-state {
+ pins = "gpio67";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
};
&pm8921 {
---
base-commit: e9ec05addd1a067fc7cb218f20ecdc1b1b0898c0
change-id: 20260331-expressatt_fuel_guage-465dfb3f87ab
prerequisite-message-id: <20260331-expressatt_camera_flash-v4-0-f1e99f474513@gmail.com>
prerequisite-patch-id: ab8b8d87fd2d518c4c5b5dace3f22238d1abbe49
prerequisite-patch-id: 47e32e653e520a27770bb05d99135694b0128ba0
prerequisite-patch-id: 7ef7df61e7ef6476a35811d765f522f793d9ecc7
Best regards,
--
Rudraksha Gupta <guptarud@gmail.com>
^ permalink raw reply related
* Re: [PATCH 2/2] riscv: dts: sophgo: Add dma-coherent to SG2042 PCIe controllers
From: Conor Dooley @ 2026-03-31 17:57 UTC (permalink / raw)
To: Han Gao
Cc: Bjorn Helgaas, Lorenzo Pieralisi, Krzysztof Wilczyński,
Manivannan Sadhasivam, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen Wang, Inochi Amaoto, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Zixian Zeng,
linux-pci, devicetree, sophgo, linux-kernel, linux-riscv, Han Gao,
stable
In-Reply-To: <20260331171248.973014-3-gaohan@iscas.ac.cn>
[-- Attachment #1: Type: text/plain, Size: 1821 bytes --]
On Wed, Apr 01, 2026 at 01:12:48AM +0800, Han Gao wrote:
> SG2042's PCIe root complexes are cache-coherent with the CPU. Mark all
> four PCIe controller nodes (pcie_rc0 through pcie_rc3) as dma-coherent
> so the kernel uses coherent DMA mappings instead of non-coherent bounce
> buffering.
Worth pointing out I guess that this property is needed, despite riscv
being coherent by default, because the whole bus is marked
dma-noncoherent.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Han Gao <gaohan@iscas.ac.cn>
> ---
> arch/riscv/boot/dts/sophgo/sg2042.dtsi | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/arch/riscv/boot/dts/sophgo/sg2042.dtsi b/arch/riscv/boot/dts/sophgo/sg2042.dtsi
> index 9fddf3f0b3b9..3af770549742 100644
> --- a/arch/riscv/boot/dts/sophgo/sg2042.dtsi
> +++ b/arch/riscv/boot/dts/sophgo/sg2042.dtsi
> @@ -417,6 +417,7 @@ pcie_rc0: pcie@7060000000 {
> vendor-id = <0x1f1c>;
> device-id = <0x2042>;
> cdns,no-bar-match-nbits = <48>;
> + dma-coherent;
> msi-parent = <&msi>;
> status = "disabled";
> };
> @@ -439,6 +440,7 @@ pcie_rc1: pcie@7060800000 {
> vendor-id = <0x1f1c>;
> device-id = <0x2042>;
> cdns,no-bar-match-nbits = <48>;
> + dma-coherent;
> msi-parent = <&msi>;
> status = "disabled";
> };
> @@ -461,6 +463,7 @@ pcie_rc2: pcie@7062000000 {
> vendor-id = <0x1f1c>;
> device-id = <0x2042>;
> cdns,no-bar-match-nbits = <48>;
> + dma-coherent;
> msi-parent = <&msi>;
> status = "disabled";
> };
> @@ -483,6 +486,7 @@ pcie_rc3: pcie@7062800000 {
> vendor-id = <0x1f1c>;
> device-id = <0x2042>;
> cdns,no-bar-match-nbits = <48>;
> + dma-coherent;
> msi-parent = <&msi>;
> status = "disabled";
> };
> --
> 2.47.3
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v4 7/9] regulator: Add MediaTek MT6392 regulator
From: kernel test robot @ 2026-03-31 17:55 UTC (permalink / raw)
To: Luca Leonardo Scorcia, linux-mediatek
Cc: oe-kbuild-all, Fabien Parent, Val Packett, Luca Leonardo Scorcia,
AngeloGioacchino Del Regno, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sen Chu, Sean Wang,
Macpaul Lin, Lee Jones, Matthias Brugger, Linus Walleij,
Liam Girdwood, Mark Brown, Julien Massot, Louis-Alexis Eyraud,
Gary Bisson, Chen Zhong, linux-input, devicetree, linux-kernel,
linux-pm, linux-arm-kernel, linux-gpio
In-Reply-To: <20260330083429.359819-8-l.scorcia@gmail.com>
Hi Luca,
kernel test robot noticed the following build warnings:
[auto build test WARNING on lee-mfd/for-mfd-next]
[also build test WARNING on broonie-regulator/for-next linusw-pinctrl/devel linusw-pinctrl/for-next lee-mfd/for-mfd-fixes linus/master v7.0-rc6 next-20260330]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Luca-Leonardo-Scorcia/dt-bindings-mfd-mt6397-Add-MT6392-PMIC/20260331-081127
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link: https://lore.kernel.org/r/20260330083429.359819-8-l.scorcia%40gmail.com
patch subject: [PATCH v4 7/9] regulator: Add MediaTek MT6392 regulator
config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20260401/202604010103.FzAGRPye-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260401/202604010103.FzAGRPye-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604010103.FzAGRPye-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/regulator/mt6392-regulator.c:181:18: warning: 'ldo_volt_table1b' defined but not used [-Wunused-const-variable=]
181 | static const u32 ldo_volt_table1b[] = {
| ^~~~~~~~~~~~~~~~
vim +/ldo_volt_table1b +181 drivers/regulator/mt6392-regulator.c
180
> 181 static const u32 ldo_volt_table1b[] = {
182 1500000, 1800000, 2500000, 2800000,
183 };
184
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] arm64: dts: qcom: sm8750: Fix DSI1 phy reference clock rate
From: Dmitry Baryshkov @ 2026-03-31 17:54 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260331165645.233965-2-krzysztof.kozlowski@oss.qualcomm.com>
On Tue, Mar 31, 2026 at 06:56:46PM +0200, Krzysztof Kozlowski wrote:
> The DSI PHY CXO clock input is the SoC CXO divided by two. DSI0 already
> uses correct one, but DSI1 got copy-paste from SM8650. Wrong clock
> parent will cause incorrect DSI1 PHY PLL frequencies to be used making
> the DSI panel non-working, although there is no upstream user of DSI1.
>
> Fixes: 818ae2b389bc ("arm64: dts: qcom: sm8750: Add display (MDSS) with Display CC")
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>
> ---
>
> Fix for next branch.
> ---
> arch/arm64/boot/dts/qcom/sm8750.dtsi | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH RFC v2 08/17] RISC-V: QoS: add resctrl interface for CBQRI controllers
From: Radim Krčmář @ 2026-03-31 17:48 UTC (permalink / raw)
To: Drew Fustini, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Samuel Holland, Adrien Ricciardi, Nicolas Pitre,
Kornel Dulęba, Atish Patra, Atish Kumar Patra,
Vasudevan Srinivasan, Ved Shanbhogue, yunhui cui, Chen Pei,
Liu Zhiwei, Weiwei Li, guo.wenjia23, liu.qingtao2,
Reinette Chatre, Tony Luck, Babu Moger, Peter Newman, Fenghua Yu,
James Morse, Ben Horgan, Dave Martin, linux-kernel, linux-riscv,
x86, Rob Herring, Rafael J. Wysocki, Len Brown, Robert Moore,
Sunil V L, Krzysztof Kozlowski, Conor Dooley
Cc: Paul Walmsley, linux-acpi, acpica-devel, devicetree, linux-riscv
In-Reply-To: <20260128-ssqosid-cbqri-v2-8-dca586b091b9@kernel.org>
2026-01-28T12:27:29-08:00, Drew Fustini <fustini@kernel.org>:
> Add interface for CBQRI controller drivers to make use of the resctrl
> filesystem.
>
> Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
Hi Drew, I have just a few minor comments as I noticed that you plan to
send a new version soon, so I'll try for a review then...
> diff --git a/arch/riscv/kernel/qos/qos_resctrl.c b/arch/riscv/kernel/qos/qos_resctrl.c
> +static int cbqri_probe_controller(struct cbqri_controller_info *ctrl_info,
> + struct cbqri_controller *ctrl)
> +{
> + int err = 0, status;
> + u64 reg;
[...]
> + ctrl->base = ioremap(ctrl_info->addr, ctrl_info->size);
> + if (!ctrl->base) {
> + pr_warn("%s(): goto err_release_mem_region", __func__);
Missing "err = -E...".
> + goto err_release_mem_region;
> + }
[...]
> + ctrl->ver_minor = reg & CBQRI_CC_CAPABILITIES_VER_MINOR_MASK;
> + ctrl->ver_major = reg & CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK;
Major version is shifted.
> +
> + ctrl->cc.supports_alloc_op_flush_rcid = (reg >> CBQRI_CC_CAPABILITIES_FRCID_SHIFT)
> + & CBQRI_CC_CAPABILITIES_FRCID_MASK;
FIELD_GET() could be used to make the mask+shift pattern nicer when
defined by GENMASK().
Thanks.
^ permalink raw reply
* Re: [PATCH v5 1/2] thermal/qcom/lmh: support SDM670 and its CPU clusters
From: Richard Acayan @ 2026-03-31 17:44 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Amit Kucheria, Thara Gopinath, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, linux-arm-msm, devicetree,
linux-pm
In-Reply-To: <ca6c13c2-4e46-483c-bc22-0ebb6db704fc@oss.qualcomm.com>
On Tue, Mar 31, 2026 at 10:30:29AM +0200, Konrad Dybcio wrote:
> On 3/30/26 6:52 PM, Richard Acayan wrote:
> > The LMh driver was made for Qualcomm SoCs with clusters of 4 CPUs, but
> > some SoCs divide the CPUs into different sizes of clusters. In SDM670,
> > the first 6 CPUs are in the little cluster and the next 2 are in the big
> > cluster. Define the clusters in the match data and define the different
> > cluster configuration for SDM670.
> >
> > Currently, this tolerates linking to any CPU in a given cluster.
> >
> > Signed-off-by: Richard Acayan <mailingradian@gmail.com>
> > ---
>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> [...]
> > + if (cpu_id < 0) {
> > + dev_err(dev, "Wrong CPU id associated with LMh node\n");
> > + return -EINVAL;
> > + }
>
> nit: try to use 'return dev_err_probe(dev, ret, "....") in the future
Does "in the future" apply to the inevitable next revision? This would
be the first occurrence of dev_err_probe in this driver and the error
path was just cut-and-paste.
^ permalink raw reply
* RE: [PATCH v9 00/21] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers
From: Dayananda, Vivekananda @ 2026-03-31 17:43 UTC (permalink / raw)
To: Ceclan Dumitru, dumitru.ceclan@analog.com, Tomi Valkeinen,
Mauro Carvalho Chehab, Sakari Ailus, Laurent Pinchart,
Julien Massot, Rob Herring, Niklas Söderlund,
Greg Kroah-Hartman, Cosmin Tanislav
Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org, linux-staging@lists.linux.dev,
linux-gpio@vger.kernel.org, Niklas Söderlund,
martin.hecht@avnet.eu, Tomi Valkeinen, Cosmin Tanislav,
Cory Keitz
In-Reply-To: <86a96690-1307-4a6f-8265-1d6d30ce6d6c@gmail.com>
[Public]
There is one other patch that is required in addition, which enables the remote-control channel links from port 1 using REG3. Adding the patch below
----
From 36936732c6ecd599f1a26744bef3031e41194229 Mon Sep 17 00:00:00 2001
From: Vivekananda Dayananda <vivekana@amd.com>
Date: Tue, 31 Mar 2026 08:12:20 -0700
Subject: [PATCH] media: i2c: maxim-serdes: max96724: allow selecting CC port
Add a DT property that lets platforms choose which control-channel port the MAX96724 exposes to the upstream I2C host. Document the new property for the MAX96724 compatibles and default to port 0 so existing device trees retain their behaviour.
The driver caches the chosen port and reprograms register 0x03 after every reset, restoring control-channel access regardless of whether the deserializer was power-cycled. Boards that need to talk through port 1 can now opt in by setting "maxim,control-channel-port = <1>;" in the device tree.
Signed-off-by: Vivekananda Dayananda <vivekana@amd.com>
---
.../bindings/media/i2c/maxim,max96712.yaml | 8 +++
drivers/media/i2c/maxim-serdes/max96724.c | 59 ++++++++++++++++++-
2 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index efee188a100d..04429439d2f6 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -33,6 +33,14 @@ properties:
enable-gpios: true
+ maxim,control-channel-port:
+ description:
+ Selects which deserializer control-channel port is connected to the
+ upstream I2C segment when the device resets. 0 selects port 0, 1 selects
+ port 1. Defaults to 0 when omitted.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+
i2c-alias-pool:
maxItems: 4
diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
index 65f2a8493a40..180815634c67 100644
--- a/drivers/media/i2c/maxim-serdes/max96724.c
+++ b/drivers/media/i2c/maxim-serdes/max96724.c
@@ -12,15 +12,37 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of_graph.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include "max_des.h"
#define MAX96724_REG0 0x0
+#define MAX96724_REG3 0x3
+#define MAX96724_REG3_CC_PORT_SEL(n) GENMASK((n) * 2 + 1, (n) * 2)
+#define MAX96724_REG3_CC_PORT_SEL_MASK (MAX96724_REG3_CC_PORT_SEL(0) | \
+ MAX96724_REG3_CC_PORT_SEL(1) | \
+ MAX96724_REG3_CC_PORT_SEL(2) | \
+ MAX96724_REG3_CC_PORT_SEL(3))
+#define MAX96724_REG3_CC_PORT_SEL_PORT0 0x2
+#define MAX96724_REG3_CC_PORT_SEL_PORT1 0x1
+#define MAX96724_REG3_CC_PORT_CFG(sel) \
+ (FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(0), (sel)) | \
+ FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(1), (sel)) | \
+ FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(2), (sel)) | \
+ FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(3), (sel)))
+#define MAX96724_REG3_CC_PORT_CFG_PORT0 \
+ MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT0)
+#define MAX96724_REG3_CC_PORT_CFG_PORT1 \
+ MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT1)
+
#define MAX96724_REG6 0x6
#define MAX96724_REG6_LINK_EN GENMASK(3, 0)
+#define MAX96724_REG7 0x7
+#define MAX96724_REG7_CC_CROSSOVER_SEL GENMASK(7, 4)
+
#define MAX96724_DEBUG_EXTRA 0x9
#define MAX96724_DEBUG_EXTRA_PCLK_SRC GENMASK(1, 0)
#define MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ 0b00
@@ -223,6 +245,7 @@ struct max96724_priv {
struct regmap *regmap;
struct gpio_desc *gpiod_enable;
+ unsigned int cc_port_cfg;
};
struct max96724_chip_info {
@@ -274,7 +297,14 @@ static int max96724_reset(struct max96724_priv *priv)
fsleep(10000);
- return max96724_wait_for_device(priv);
+ ret = max96724_wait_for_device(priv);
+ if (ret)
+ return ret;
+
+ /* Restore I2C control-channel access after a reset. */
+ return regmap_update_bits(priv->regmap, MAX96724_REG3,
+ MAX96724_REG3_CC_PORT_SEL_MASK,
+ priv->cc_port_cfg);
}
static int __maybe_unused max96724_reg_read(struct max_des *des, unsigned int reg,
@@ -461,6 +491,12 @@ static int max96724_init(struct max_des *des)
return ret;
}
+ /* Enable I2C control ports crossover. */
+ ret = regmap_set_bits(priv->regmap, MAX96724_REG7,
+ MAX96724_REG7_CC_CROSSOVER_SEL);
+ if (ret)
+ return ret;
+
/* Set PHY mode. */
ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY0,
MAX96724_MIPI_PHY0_PHY_CONFIG,
@@ -1096,6 +1132,7 @@ static int max96724_probe(struct i2c_client *client)
struct max96724_priv *priv;
struct max_des_info *info;
struct max_des_ops *ops;
+ u32 cc_port;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -1139,6 +1176,26 @@ static int max96724_probe(struct i2c_client *client)
usleep_range(4000, 5000);
}
+ priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
+
+ ret = device_property_read_u32(dev, "maxim,control-channel-port",
+ &cc_port);
+ if (!ret) {
+ switch (cc_port) {
+ case 0:
+ priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
+ break;
+ case 1:
+ priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT1;
+ break;
+ default:
+ dev_err(dev, "Invalid control-channel port %u\n", cc_port);
+ return -EINVAL;
+ }
+ } else if (ret != -ENODATA && ret != -ENOENT && ret != -EINVAL) {
+ return ret;
+ }
+
*info = max96724_des_info;
info->versions = priv->info->versions;
info->modes = priv->info->modes;
--
2.34.1
> -----Original Message-----
> From: Ceclan Dumitru <mitrutzceclan@gmail.com>
> Sent: Monday, March 30, 2026 12:55 AM
> To: Dayananda, Vivekananda <vivekananda.dayananda@amd.com>;
> dumitru.ceclan@analog.com; Tomi Valkeinen
> <tomi.valkeinen+renesas@ideasonboard.com>; Mauro Carvalho Chehab
> <mchehab@kernel.org>; Sakari Ailus <sakari.ailus@linux.intel.com>; Laurent
> Pinchart <laurent.pinchart@ideasonboard.com>; Julien Massot
> <julien.massot@collabora.com>; Rob Herring <robh@kernel.org>; Niklas
> Söderlund <niklas.soderlund@ragnatech.se>; Greg Kroah-Hartman
> <gregkh@linuxfoundation.org>; Cosmin Tanislav <cosmin.tanislav@analog.com>
> Cc: linux-media@vger.kernel.org; linux-kernel@vger.kernel.org;
> devicetree@vger.kernel.org; linux-staging@lists.linux.dev; linux-
> gpio@vger.kernel.org; Niklas Söderlund
> <niklas.soderlund+renesas@ragnatech.se>; martin.hecht@avnet.eu; Tomi
> Valkeinen <tomi.valkeinen@ideasonboard.com>; Cosmin Tanislav
> <demonsingur@gmail.com>; Cory Keitz <ckeitz@amazon.com>
> Subject: Re: [PATCH v9 00/21] media: i2c: add Maxim GMSL2/3 serializer and
> deserializer drivers
>
>
>
> On 3/26/26 7:00 PM, Dayananda, Vivekananda wrote:
> > [AMD Official Use Only - AMD Internal Distribution Only]
> >
> > Hi Dumitru, Sakari,
> >
> > Thank you for the latest patch series. We have been validating this on
> > the following test infrastructure:
> >
> > - IMX219 image sensor
> > - MAX96724 deserializer
> > - MAX96717 serializer
> >
> > With this setup the v9 drivers operate as expected and we are able to
> > exercise streaming end-to-end.
> >
> > One item worth noting: our deserializer sits on a custom daughter card
> > where the I2C bus is routed through port 1. The current MAX96724
> > driver only supports I2C on port 0. It would be valuable to extend the
> > driver to support additional I2C port configurations so that setups
> > like ours can be accommodated.
> >
> > Vivek
>
> Hi Vivek,
>
> I have found the EZ thread where the I2C port1 issue was raised and resolved.
> Would the inclusion of those 2 commits from Cosmin suffice or were there more
> changes required for your setup to work?
>
>
> >> -----Original Message-----
> >> From: Dumitru Ceclan via B4 Relay
> >> <devnull+dumitru.ceclan.analog.com@kernel.org>
> >> Sent: Wednesday, March 11, 2026 12:17 AM
> >> To: Tomi Valkeinen <tomi.valkeinen+renesas@ideasonboard.com>; Mauro
> >> Carvalho Chehab <mchehab@kernel.org>; Sakari Ailus
> >> <sakari.ailus@linux.intel.com>; Laurent Pinchart
> >> <laurent.pinchart@ideasonboard.com>; Julien Massot
> >> <julien.massot@collabora.com>; Rob Herring <robh@kernel.org>; Niklas
> >> Söderlund <niklas.soderlund@ragnatech.se>; Greg Kroah-Hartman
> >> <gregkh@linuxfoundation.org>; Cosmin Tanislav
> >> <cosmin.tanislav@analog.com>
> >> Cc: mitrutzceclan@gmail.com; linux-media@vger.kernel.org; linux-
> >> kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-
> >> staging@lists.linux.dev; linux-gpio@vger.kernel.org; Niklas Söderlund
> >> <niklas.soderlund+renesas@ragnatech.se>; Martin Hecht
> >> <Martin.Hecht@avnet.eu>; Tomi Valkeinen
> >> <tomi.valkeinen@ideasonboard.com>; Cosmin Tanislav
> >> <demonsingur@gmail.com>; Cory Keitz <ckeitz@amazon.com>
> >> Subject: [PATCH v9 00/21] media: i2c: add Maxim GMSL2/3 serializer
> >> and deserializer drivers
> >>
> >> This series adds new drivers for multiple Maxim GMSL2 and GMSL3
> >> devices, replacing the few GMSL2 drivers already in upstream, and
> >> introducing a common framework that can be used to implement such
> >> GMSL chips, which avoids code duplication while also adding support for
> previously unsupported features.
> >>
> >> While the normally acceptable and polite way would be to extend the
> >> current mainline drivers, the choice was made here to add a totally new set of
> drivers.
> >> The current drivers support only a small subset of the possible
> >> features, and only a few devices, so the end result after extending
> >> them would in any case be essentially fully rewritten, new drivers.
> >>
> >> This series depends on support for internal pads, for which a patch
> >> has been added.
> >>
> >> The previous version is at:
> >> https://lore.kernel.org/all/20250718152500.2656391-1-
> >> demonsingur@gmail.com/
> >>
> >> Since the previous series, Cosmin has left Analog Devices.
> >> Because included changes from previous version are trivial, his
> >> sign-off and tags were retained.
> >>
> >> The following deserializers are supported:
> >> * MAX96712 (already exists in staging)
> >> * MAX96714 (already exists)
> >> * MAX96714F (already exists)
> >> * MAX96714R (GMSL2)
> >> * MAX96716 (GMSL2)
> >> * MAX96724 (already exists as part of existing MAX96712 driver)
> >> * MAX96724F (GMSL2)
> >> * MAX96724R (GMSL2)
> >> * MAX9296A (GMSL2)
> >> * MAX96792A (GMSL3)
> >>
> >> The following serializers are supported:
> >> * MAX96717 (already exists)
> >> * MAX9295A (GMSL2)
> >> * MAX96793 (GMSL3)
> >>
> >> The following list enumerates new features that are supported by the
> >> common framework and their respective chip-specific drivers:
> >> * Full Streams API support. Most deserializers have support for more
> >> than one link, and more than one PHY. Streams support allows
> >> configuration of routing between these links and PHYs.
> >>
> >> * .get_frame_desc() support. Both the serializers and deserializers
> >> implement this to query and provide frame descriptor data. This is
> >> used in features explained in- depth below.
> >>
> >> * .get_mbus_config() support. The deserializers implement this to
> >> allow upstream devices to query the link frequency of its pads.
> >>
> >> * Address translation with I2C ATR for the serializers.
> >>
> >> * I2C ATR translation - some deserializers cannot do muxing since I2C
> >> communication channel masking is not available per-link, and the only
> >> other way to select links is to turn them off, causing link resets.
> >> For such cases, I2C ATR is used to change the address of the
> >> serializers at probe time.
> >>
> >> * Automatic GMSL link version negotiation between GMSL3, GMSL2 6Gbps,
> >> GMSL2 3Gbps.
> >>
> >> * Automatic stream id selection for deserializers which need
> >> serializers to stream on unique stream ids.
> >>
> >> * Automatic VC remapping on the deserializers. VCs are picked so that
> >> if they were unique on the sink pad, they will end up as unique on
> >> the source pad they are routed to too, prioritizing using the same VC
> >> ID as the sink pad, to facilitate the possibility of using tunnel mode.
> >>
> >> * Automatic pixel mode / tunnel mode selection. Tunnel mode is used
> >> when VC IDs do not need to be changed and all hardware supports
> >> tunnel mode, otherwise, pixel mode is used. The serializers are
> >> automatically switched between the two by using a private API.
> >>
> >> * Automatic double mode selection. In pixel mode, double mode can be
> >> used to pack two pixels into a single data unit, optimizing bandwidth
> >> usage. The serializers are automatically set up to support the double
> >> modes determined by the deserializers using a private API.
> >>
> >> * Automatic data padding. In pixel mode, if the data being
> >> transferred uses two different BPPs, data needs to be padded. The
> >> serializers automatically set this up depending on the configured double mode
> settings and incoming data types.
> >>
> >> * Logging. Both the deserializers and serializers implement the V4L2
> >> .log_status() ops to allow debugging of the internal state and
> >> important chip status registers.
> >>
> >> * PHY modes. Deserializer chips commonly have more than a single PHY.
> >> The firmware ports are parsed to determine the modes in which to
> >> configure the PHYs (2x4, 4x2, 1x4+2x2, 2x2+1x4, and variations using fewer
> lanes).
> >>
> >> * Serializer pinctrl. Serializers implement pinctrl to allow setting
> >> configs which would otherwise be inaccessible through GPIO: TX/RX via
> >> GMSL link, pull-up & pull-down (with strength), open-drain & push-pull, slew
> rate, RCLK pin selection.
> >>
> >> * TPG with selectable formats, resolutions and framerates for both
> >> serializers and deserializers.
> >>
> >> The drivers have been tested on the following hardware combinations,
> >> but further testing is welcome to ensure no / minimal breakage:
> >> * Raspberry Pi 5 + MAX9296A + 2xMAX96717 + 2xIMX219
> >> * Raspberry Pi 5 + MAX96714 + 1xMAX96717 + 1xIMX219
> >> * Raspberry Pi 5 + MAX96716A + 2xMAX96717 + 2xIMX219
> >> * Raspberry Pi 5 + MAX96712 + 4xMAX96717 + 4xIMX219
> >> * Raspberry Pi 5 + MAX96724 + 4xMAX96717 + 4xIMX219
> >> * Raspberry Pi 5 + MAX96792A + 1xMAX96793 + 1xMAX96717 + 2xIMX219
> >> * Raspberry Pi 5 + MAX96792A + 2xMAX96717 + 2xIMX219
> >> * Renesas V4H + MAX96712 + 2xMAX96717 + 2xIMX219
> >>
> >> Analog Devices is taking responsibility for the maintenance of these
> >> drivers and common framework, and plans to add support for new
> >> broad-market chips on top of them.
> >>
> >> Special thanks go to Tomi Valkeinen <
> >> tomi.valkeinen+renesas@ideasonboard.com>
> >> for testing the drivers, helping debug and coming up with ideas /
> >> implementations for various features.
> >>
> >> The following v4l2-compliance test still fails:
> >> fail: v4l2-test-subdevs.cpp(371): fmt.code == 0 || fmt.code == ~0U
> >> fail: v4l2-test-subdevs.cpp(418): checkMBusFrameFmt(node,
> fmt.format)
> >> test Active VIDIOC_SUBDEV_G/S_FMT: FAIL
> >>
> >> As the serializers and deserializers are format agnostic and the
> >> values set are not used to configure anything in the chips, this test
> >> does not make much sense in this context. If needed, a check for the specific
> ~0U value can be added.
> >>
> >> V9:
> >> * split max_des_ops into *_info and *_ops
> >> * use read_poll_timeout macro in *_wait_for_device()
> >> * return read_poll_timeout error -ETIMEDOUT in *_wait_for_device()
> >> * remove use_atr duplicate from max9296a_chip_info, present in
> >> max_des_info
> >> * fix max9296a DPLL register offset
> >> * fix C-PHY DPLL frequency in max9296a and max96724
> >> reported by: Cory Keitz <ckeitz@amazon.com>
> >> * use MAX9296A_COMMON_INFO and MAX9296A_COMMON_OPS to
> simplify
> >> probe ops init
> >> * fix borked patches in previous version, actually remove MAX96717 and
> >> MAX96714 drivers
> >>
> >> V8:
> >> * max96717: use the renamed PIN_CONFIG_OUTPUT to _LEVEL
> >> * max96717: use the renamed set_rv ops from struct gpio_chip
> >> * dt-bindings: set minItems lane-polarities to 2
> >> * dt-bindings: "add myself as maintainer" commits were removed
> >> * max_des & max_ser: use a default format for set_routing
> >> * max_des & max_ser: return ENNOTTY in *_frame_interval for non-TPG
> >> pads
> >>
> >> V7:
> >> * dt-bindings: max9296a: use full max96717 compatible
> >> * max9296a: make max96714_rlms_reg_sequence static
> >> * explicitly include linux/bitfield.h
> >> * explicitly depend on I2C and PINCTRL
> >> * sort media_entity_operations
> >> * add has_pad_interdep to media_entity_operations
> >>
> >> V6:
> >> * max9296a: put rlms sequence in max9296a_chip_info
> >> * max_des: reflow stream id a comment
> >> * max_ser: remove exported symbols not used in other modules
> >> * max_ser: init mode to a supported value
> >> * add default routing
> >> * MAX_SERDES_GMSL_3 -> MAX_SERDES_GMSL_3_12GBPS
> >> * guard reg_read/write with CONFIG_VIDEO_ADV_DEBUG
> >> * put exported symbols in MAXIM_SERDES namespace
> >>
> >> V5:
> >> * dt-bindings: max96717: restrict RCLKOUT to pins 2 & 4
> >> * dt-bindings: max96717: remove confusing rclksel pinconf property
> >> * dt-bindings: max96717: remove maxim,gmsl-tx/rx pinconf property
> >> * dt-bindings: max96717: remove gmsl prefix from
> >> maxim,gmsl-tx-id/rx-id
> >> * dt-bindings: max96717: remove minimum: 0
> >> * dt-bindings: max96717: better document slew-rate
> >> * dt-bindings: max96717: better document maxim,jitter-compensation
> >> * dt-bindings: max96717: better document maxim,tx-id/rx-id
> >>
> >> * max_serdes: add default TPG values
> >> * max_serdes: remove MAX_MIPI_FMT macro
> >> * max_serdes: EXPORT_SYMBOL -> EXPORT_SYMBOL_GPL
> >> * max_serdes: remove EXPORT_SYMBOL_GPL from symbols not used in other
> >> modules
> >> * max_serdes: rename symbols/macros/types to have max_serdes prefix
> >> * max_serdes: slim down TPG functions
> >>
> >> * max_des: fix may be used uninitialized errors
> >> * max_des: fix misplaced TPG validation
> >> * max_des: fix setting pipe PHY in tunnel mode for chips that support
> >> both
> >> set_pipe_phy() and set_pipe_tunnel_phy()
> >> * max_des: move doubled_bpp/sink_bpps variables to usage place
> >> * max_des: do not dynamically control PHY enable, letting lanes be in
> >> LP-11 when not streaming
> >> * max_des: refactor get/set_pipe_stream_id() logic
> >> * max_des: remove explicit ret = 0
> >>
> >> * max_ser: make VC remaps not pipe-specific, allocate dynamically
> >>
> >> * max9296a: add missing 1080p30 TPG entry
> >> * max9296a: move BIT() left shift into macro
> >> * max9296a: move BIT() ternary into macro
> >> * max9296a: reuse max_des_ops for chip-specific ops\
> >> * max9296a: document and compress RLMS register writes
> >>
> >> * max96717: restrict RCLKOUT to pins 2 & 4 because of hardware
> >> capabilities
> >> * max96717: add support for XTAL/1, XTAL/2, XTAL/4 clocks
> >> * max96717: set RX_EN/TX_EN automatically
> >> * max96717: reorder custom pinconf flags
> >> * max96717: drop OF dependency
> >>
> >> * drop of_match_ptr
> >> * re-do some indentation
> >> * implement TPG pattern control
> >> * remove pr_info() usage
> >> * inline lane polarity val = 0
> >> * inline returns
> >> * rewrite some Kconfig docs
> >> * split up patches for easier review
> >>
> >> V4:
> >> * max_des: fix infinite version loop
> >> * max_des: fix pipe link id when there are more pipes than links
> >> * max_des: implement setting pipe link
> >> * max_des: do not pass routing to phy update
> >> * max_des: move GMSL version strings to max_serdes
> >> * max_des: split finding existing VC remap from adding a new one
> >> * max_des: add tracking for in-use pipes
> >> * max_des: skip unused pipes when finding / setting pixel/tunnel mode
> >> * max_des: simplify remap code
> >> * max_des: split set_pipe_phy() into set_pipe_tunnel_phy()
> >>
> >> * max_ser: clean up i2c_xlates printing
> >> * max_ser: fix changing serializer address
> >> * max_ser: move non-continuous mode check into max96717 driver
> >>
> >> * max96724: use regmap_set_bits for STREAM_SEL_ALL
> >> * max96724: match surrounding indent for MAX96724_PHY1_ALT_CLOCK
> >> * max96724: fix setting invalid PHY to 1 when PHY 0 is in 4-lane mode
> >> * max96724: remove support for setting pipe phy from max96712
> >> * max96724: fix setting double mode on pipes 4-7
> >> * max96724: drop powerdown gpios
> >>
> >> * max96717: use gpio_chip's set_rv
> >>
> >> * max9296a: switch versions to unsigned int
> >> * max9296a: remove parantheses from MAX9296A_MIPI_PHY18/20
> >> * max9296a: fix printing of PHY packet counts
> >> * max9296a: fix phy_hw_ids size
> >>
> >> * remove usage of cammel case in defines
> >> * move field_get/prep to max_serdes.h
> >> * rework stream id setup
> >> * rework tunnel/pixel mode finding
> >> * rework bpps retrieval
> >> * pass whole subdev state around
> >> * add helper for retrieving a route's hw components / frame desc
> >> * update pipe enable based on active routes
> >> * add support for tunnel-only chips and VC remaps in tunnel mode
> >> * simplify max_get_streams_masks()
> >> * add support for TPG
> >>
> >> V3:
> >> * dt-bindings: drop reflow text patches
> >>
> >> * dt-bindings: max96717: move pinctrl configuration into main file
> >> * dt-bindings: max96717: allow a single level of pins configuration
> >> * dt-bindings: max96717: use regex for matching pins nodes
> >> * dt-bindings: max96717: drop extra allOf in pinctrl configuration
> >> * dt-bindings: max96717: fix i2c-atr channel name regex
> >> * dt-bindings: max96717: limit pinctrl functions to gpio / rclkout
> >> * dt-bindings: max96717: limit pins for gpio / rclkout
> >> * dt-bindings: max96717: add description for bias-pull-up/down
> >> * dt-bindings: max96717: require pins and function properties
> >> * dt-bindings: max96717: turn single compatible strings into an enum
> >>
> >> * dt-bindings: max9296a: include indices in port descriptions
> >> * dt-bindings: max9296a: remove property-less schema from input ports
> >> * dt-bindings: max9296a: use ATR for MAX96716A too, removing MUX
> >> entirely
> >>
> >> * dt-bindings: max96712: include indices in port descriptions
> >> * dt-bindings: max96712: deprecate enable-gpios in favor of
> >> powerdown-gpios
> >> * dt-bindings: max96712: switch from MUX to ATR
> >>
> >> * dt-bindings: max96714: add support for MAX96714R
> >>
> >> * max_des: fix POC NULL check
> >> * max_des: remove index var in POC enable
> >> * max_des: fix writing empty remaps
> >> * max_des: skip mode setting in tunnel mode
> >> * max_des: remove a duplicate source->sd NULL check
> >> * max_des: set pipe tunnel mode even for disabled links
> >>
> >> * max_ser: apply TX ID changes irrespective of serializer ID
> >>
> >> * max9296a: fix typo in BACKTOP22
> >> * max9296a: make register macros more consistent
> >> * max9296a: switch MAX96716 from MUX to ATR
> >> * max9296a: deduplicate max9296a_phy_id() logic
> >> * max9296a: use proper PHY id in remaps
> >> * max9296a: fix DPLL reset clear
> >> * max9296a: limit MAX96714F to GMSL2 3Gbps
> >> * max9296a: add support for MAX96714R
> >> * max9296a: do not write GMSL3 link select registers in GMSL2 devices
> >> * max9296a: use field_prep when setting RX_RATE
> >> * max9296a: simplify setting SEL_STREAM for MAX96714
> >> * max9296a: max96716_set_pipe_phy -> max96716a_set_pipe_phy
> >> * max9296a: fix off-by-one in lane polarity when using
> >> polarity_on_physical_lanes
> >>
> >> * max96724: fix typo in BACKTOP22
> >> * max96724: switch from MUX to ATR
> >> * max96724: add support for powerdown GPIO
> >> * max96724: remove support for tunneling from MAX96712
> >> * max96724: only set tunnel-related bits when in tunnel mode
> >> * max96724: add support for MAX96724F/R
> >> * max96724: oneshot reset links after link selection
> >>
> >> * remove GMSL2 version defaults, set all supported versions
> >> explicitly
> >> * reorder GMSL versions to start from 0
> >> * add support for GMSL2 3Gbps
> >> * support GMSL version finding for devices using MUX / GATE
> >> * add support for deserializers which don't have individual control
> >> of each link's GMSL version
> >> * add support for deserializers that need unique stream ids across
> >> all serializers
> >> * select_link_version -> set_link_version
> >> * select_resets_link -> use_atr
> >>
> >> V2:
> >> * add missing compatible for MAX96717F
> >> * fix embarrassing dt-bindings mistakes
> >> * move MAX9296A/MAX96716/MAX96792A to a separate file as they have
> >> two links / PHYs, and adding those conditionally seems impossible
> >> ---
> >> Cosmin Tanislav (20):
> >> dt-bindings: media: i2c: max96717: add support for I2C ATR
> >> dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf
> >> dt-bindings: media: i2c: max96717: add support for MAX9295A
> >> dt-bindings: media: i2c: max96717: add support for MAX96793
> >> dt-bindings: media: i2c: max96712: use pattern properties for ports
> >> dt-bindings: media: i2c: max96712: add support for I2C ATR
> >> dt-bindings: media: i2c: max96712: add support for POC supplies
> >> dt-bindings: media: i2c: max96712: add support for MAX96724F/R
> >> dt-bindings: media: i2c: max96714: add support for MAX96714R
> >> dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A
> >> media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
> >> media: i2c: add Maxim GMSL2/3 serializer framework
> >> media: i2c: add Maxim GMSL2/3 deserializer framework
> >> media: i2c: maxim-serdes: add MAX96717 driver
> >> media: i2c: maxim-serdes: add MAX96724 driver
> >> media: i2c: maxim-serdes: add MAX9296A driver
> >> arm64: defconfig: disable deprecated MAX96712 driver
> >> staging: media: remove MAX96712 driver
> >> media: i2c: remove MAX96717 driver
> >> media: i2c: remove MAX96714 driver
> >>
> >> Sakari Ailus (1):
> >> media: mc: Add INTERNAL pad flag
> >>
> >> .../bindings/media/i2c/maxim,max9296a.yaml | 242 ++
> >> .../bindings/media/i2c/maxim,max96712.yaml | 65 +-
> >> .../bindings/media/i2c/maxim,max96714.yaml | 5 +-
> >> .../bindings/media/i2c/maxim,max96717.yaml | 154 +-
> >> .../userspace-api/media/mediactl/media-types.rst | 9 +
> >> MAINTAINERS | 10 +-
> >> arch/arm64/configs/defconfig | 1 -
> >> drivers/media/i2c/Kconfig | 34 +-
> >> drivers/media/i2c/Makefile | 3 +-
> >> drivers/media/i2c/max96714.c | 1017 -------
> >> drivers/media/i2c/max96717.c | 1102 -------
> >> drivers/media/i2c/maxim-serdes/Kconfig | 60 +
> >> drivers/media/i2c/maxim-serdes/Makefile | 6 +
> >> drivers/media/i2c/maxim-serdes/max9296a.c | 1358 +++++++++
> >> drivers/media/i2c/maxim-serdes/max96717.c | 1686 +++++++++++
> >> drivers/media/i2c/maxim-serdes/max96724.c | 1193 ++++++++
> >> drivers/media/i2c/maxim-serdes/max_des.c | 3188
> >> ++++++++++++++++++++
> >> drivers/media/i2c/maxim-serdes/max_des.h | 156 +
> >> drivers/media/i2c/maxim-serdes/max_ser.c | 2138 +++++++++++++
> >> drivers/media/i2c/maxim-serdes/max_ser.h | 147 +
> >> drivers/media/i2c/maxim-serdes/max_serdes.c | 413 +++
> >> drivers/media/i2c/maxim-serdes/max_serdes.h | 183 ++
> >> drivers/media/mc/mc-entity.c | 15 +-
> >> drivers/staging/media/Kconfig | 2 -
> >> drivers/staging/media/Makefile | 1 -
> >> drivers/staging/media/max96712/Kconfig | 14 -
> >> drivers/staging/media/max96712/Makefile | 2 -
> >> drivers/staging/media/max96712/max96712.c | 487 ---
> >> include/uapi/linux/media.h | 1 +
> >> 29 files changed, 11006 insertions(+), 2686 deletions(-)
> >> ---
> >> base-commit: a15a902a91b78f1544760fb52ef0151f83815f81
> >> change-id: 20251107-gmsl2-3_serdes-3f2b885209c3
> >>
> >> Best regards,
^ permalink raw reply related
* Re: [PATCH v20 06/10] power: reset: Add psci-reboot-mode driver
From: Shivendra Pratap @ 2026-03-31 17:40 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Arnd Bergmann, Bjorn Andersson, Sebastian Reichel, Rob Herring,
Souvik Chakravarty, Krzysztof Kozlowski, Andy Yan,
Matthias Brugger, Mark Rutland, Conor Dooley, Konrad Dybcio,
John Stultz, Moritz Fischer, Bartosz Golaszewski, Sudeep Holla,
Florian Fainelli, Krzysztof Kozlowski, Dmitry Baryshkov,
Mukesh Ojha, Andre Draszik, Kathiravan Thirumoorthy, linux-pm,
linux-kernel, linux-arm-kernel, linux-arm-msm, devicetree,
Srinivas Kandagatla
In-Reply-To: <acaQzmVhO50oAbbE@lpieralisi>
On 27-03-2026 19:44, Lorenzo Pieralisi wrote:
> On Wed, Mar 04, 2026 at 11:33:06PM +0530, Shivendra Pratap wrote:
>> PSCI supports different types of resets like COLD reset, ARCH WARM
>> reset, vendor-specific resets. Currently there is no common driver that
>> handles all supported psci resets at one place. Additionally, there is
>> no common mechanism to issue the supported psci resets from userspace.
>>
>> Add a PSCI reboot mode driver and define two types of PSCI resets in the
>> driver as reboot-modes: predefined resets controlled by Linux
>> reboot_mode and customizable resets defined by SoC vendors in their
>> device tree under the psci:reboot-mode node.
>>
>> Register the driver with the reboot-mode framework to interface these
>> resets to userspace. When userspace initiates a supported command, pass
>> the reset arguments to the PSCI driver to enable command-based reset.
>>
>> This change allows userspace to issue supported PSCI reset commands
>> using the standard reboot system calls while enabling SoC vendors to
>> define their specific resets for PSCI.
>>
>> Signed-off-by: Shivendra Pratap <shivendra.pratap@oss.qualcomm.com>
>> ---
>> drivers/power/reset/Kconfig | 10 +++
>> drivers/power/reset/Makefile | 1 +
>> drivers/power/reset/psci-reboot-mode.c | 119 +++++++++++++++++++++++++++++++++
>
> Add an entry into MAINTAINERS.POWER STATE COORDINATION INTERFACE for this
> specific file because I'd like to keep an eye on it, if you don't mind.
>
> Creating a MAINTAINER entry just for this seems overkill to me, it
> does not look like it is done for other reboot mode drivers.
Ack.
thanks,
Shivendra
^ permalink raw reply
* Re: [PATCH v20 04/10] firmware: psci: Introduce command-based reset in psci_sys_reset
From: Shivendra Pratap @ 2026-03-31 17:38 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Arnd Bergmann, Bjorn Andersson, Sebastian Reichel, Rob Herring,
Souvik Chakravarty, Krzysztof Kozlowski, Andy Yan,
Matthias Brugger, Mark Rutland, Conor Dooley, Konrad Dybcio,
John Stultz, Moritz Fischer, Bartosz Golaszewski, Sudeep Holla,
Florian Fainelli, Krzysztof Kozlowski, Dmitry Baryshkov,
Mukesh Ojha, Andre Draszik, Kathiravan Thirumoorthy, linux-pm,
linux-kernel, linux-arm-kernel, linux-arm-msm, devicetree,
Srinivas Kandagatla
In-Reply-To: <acaPCJnX6lb9lxPy@lpieralisi>
On 27-03-2026 19:37, Lorenzo Pieralisi wrote:
> On Wed, Mar 04, 2026 at 11:33:04PM +0530, Shivendra Pratap wrote:
>> PSCI currently supports only COLD reset and ARCH WARM reset based on the
>> Linux reboot_mode variable. The PSCI specification now includes
>> SYSTEM_RESET2 for vendor-specific resets, but there's no mechanism to
>> issue these through psci_sys_reset.
>>
>> Add a command-based reset mechanism that allows external drivers to set
>> the psci reset command via a new psci_set_reset_cmd() function.
>>
>> The psci command-based reset is disabled by default and the
>> psci_sys_reset follows its original flow until a psci_reset command is
>> set. In kernel panic path, psci_reset command is ignored.
>
> If it is function calls you should add parenthesis (eg psci_sys_reset ->
> psci_sys_reset()).
>
> You must explain why the kernel panic path requires separate handling
> here AND in the code - think about looking at this years down the line
> and figure out why kernel panics are special here.
Ack.
>
>> Signed-off-by: Shivendra Pratap <shivendra.pratap@oss.qualcomm.com>
>> ---
>> drivers/firmware/psci/psci.c | 45 ++++++++++++++++++++++++++++++++++++++++++--
>> include/linux/psci.h | 2 ++
>> 2 files changed, 45 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
>> index 38ca190d4a22d6e7e0f06420e8478a2b0ec2fe6f..ae6f7a0aead913d740070080d4b2a3da15b29485 100644
>> --- a/drivers/firmware/psci/psci.c
>> +++ b/drivers/firmware/psci/psci.c
>> @@ -51,6 +51,15 @@ static int resident_cpu = -1;
>> struct psci_operations psci_ops;
>> static enum arm_smccc_conduit psci_conduit = SMCCC_CONDUIT_NONE;
>>
>> +struct psci_sys_reset_params {
>> + u32 system_reset;
>> + u32 reset_type;
>> + u32 cookie;
>> + bool cmd;
>> +};
>> +
>> +static struct psci_sys_reset_params psci_reset;
>> +
>> bool psci_tos_resident_on(int cpu)
>> {
>> return cpu == resident_cpu;
>> @@ -80,6 +89,28 @@ static u32 psci_cpu_suspend_feature;
>> static bool psci_system_reset2_supported;
>> static bool psci_system_off2_hibernate_supported;
>>
>> +/**
>> + * psci_set_reset_cmd - Sets the psci_reset_cmd for command-based
>> + * reset which will be used in psci_sys_reset call.
>> + *
>> + * @cmd_sys_rst2: Set to true for SYSTEM_RESET2 based resets.
>> + * @cmd_reset_type: Set the reset_type argument for psci_sys_reset.
>> + * @cmd_cookie: Set the cookie argument for psci_sys_reset.
>> + */
>> +void psci_set_reset_cmd(bool cmd_sys_rst2, u32 cmd_reset_type, u32 cmd_cookie)
>> +{
>
> I don't think cmd_sys_rst2 is needed, as a replied in a different thread.
Need bit more clarification. The issue is that at some point we need to
decide - sys_rst2 or the reboot_mode based reset. Are you suggesting
that this check should be in psci driver instead of a pre-check in
psci_reboot_mode driver?
>
>> + if (cmd_sys_rst2 && psci_system_reset2_supported) {
>> + psci_reset.system_reset = PSCI_FN_NATIVE(1_1, SYSTEM_RESET2);
>> + psci_reset.reset_type = cmd_reset_type;
>> + psci_reset.cookie = cmd_cookie;
>> + } else {
>> + psci_reset.system_reset = PSCI_0_2_FN_SYSTEM_RESET;
>> + psci_reset.reset_type = 0;
>> + psci_reset.cookie = 0;
>> + }
>> + psci_reset.cmd = true;
>> +}
>> +
>> static inline bool psci_has_ext_power_state(void)
>> {
>> return psci_cpu_suspend_feature &
>> @@ -309,14 +340,24 @@ static int get_set_conduit_method(const struct device_node *np)
>> static int psci_sys_reset(struct notifier_block *nb, unsigned long action,
>> void *data)
>> {
>> - if ((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) &&
>> - psci_system_reset2_supported) {
>> + if (((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) &&
>> + psci_system_reset2_supported) && (panic_in_progress() || !psci_reset.cmd)) {
>> /*
>> * reset_type[31] = 0 (architectural)
>> * reset_type[30:0] = 0 (SYSTEM_WARM_RESET)
>> * cookie = 0 (ignored by the implementation)
>> */
>> invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0);
>> + } else if (!panic_in_progress() && psci_reset.cmd) {
>> + /*
>> + * Commands are being set in psci_set_reset_cmd
>> + * This issues, SYSTEM_RESET2 arch warm reset or
>> + * SYSTEM_RESET2 vendor-specific reset or
>> + * a SYSTEM_RESET cold reset in accordance with
>> + * the reboot-mode command.
>> + */
>> + invoke_psci_fn(psci_reset.system_reset, psci_reset.reset_type,
>> + psci_reset.cookie, 0);
>> } else {
>> invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
>
> This is very hard to parse. IMO, what you should do is:
>
> - Split this into two different paths: reboot_mode vs psci_reset.cmd == true.
> - Document very clearly why a panic needs separate handling.
>
> Something like:
>
> if (psci_reset.cmd)
> handle_reset_cmd();
> else
> handle_reboot_mode();
>
> I don't think we are far from converging but I want to be able to maintain
> this code going forward.
Ack. Will restructure it as suggested.
thanks,
Shivendra
^ permalink raw reply
* Re: [PATCH 2/3] hte: tegra194: Add Tegra264 GTE support
From: Dipen Patel @ 2026-03-31 17:35 UTC (permalink / raw)
To: Krzysztof Kozlowski, Suneel Garapati, jonathanh, thierry.reding,
krzk+dt, conor+dt, amhetre, sheetal, kkarthik, timestamp,
devicetree, linux-tegra, linux-kernel, robh
In-Reply-To: <0cc95f5e-a5c7-498f-ae2e-32017141b619@kernel.org>
On 3/30/26 12:33 PM, Krzysztof Kozlowski wrote:
> On 30/03/2026 21:32, Dipen Patel wrote:
>> On 3/30/26 11:39 AM, Krzysztof Kozlowski wrote:
>>> On 30/03/2026 20:35, Dipen Patel wrote:
>>>>> MODULE_DEVICE_TABLE(of, tegra_hte_of_match);
>>>
>>> Please kindly trim the replies from unnecessary context. It makes it
>>> much easier to find new content.
>>>
>>>> Acked-by: Dipen Patel <dipenp@nvidia.com>
>>>> Signed-off-by: Dipen Patel <dipenp@nvidia.com>
>>>
>>> What are you certifying here with SoB?
>> I was preemptively adding my SoB since I had to integrate this patch into my HTE
>> subsystem tree. But for now, please ignore my SoB.
>
> So you wanted to say that you applied a patch? Why not sending ty
> letters with b4?
Please ignore my SoB and consider that as a mistake.
>
> Best regards,
> Krzysztof
^ permalink raw reply
* [PATCH] dt-bindings: arm: arm,vexpress-scc: convert to DT schema
From: Khushal Chitturi @ 2026-03-31 17:29 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, liviu.dudau, sudeep.holla, lpieralisi,
pawel.moll
Cc: devicetree, linux-arm-kernel, linux-kernel, Khushal Chitturi
Convert the ARM Versatile Express Serial Configuration Controller
bindings to DT schema.
Signed-off-by: Khushal Chitturi <khushalchitturi@gmail.com>
---
Note:
* This patch is part of the GSoC2026 application process for device tree bindings conversions
* https://github.com/LinuxFoundationGSoC/ProjectIdeas/wiki/GSoC-2026-Device-Tree-Bindings
.../bindings/arm/arm,vexpress-scc.yaml | 51 +++++++++++++++++++
.../devicetree/bindings/arm/vexpress-scc.txt | 33 ------------
2 files changed, 51 insertions(+), 33 deletions(-)
create mode 100644 Documentation/devicetree/bindings/arm/arm,vexpress-scc.yaml
delete mode 100644 Documentation/devicetree/bindings/arm/vexpress-scc.txt
diff --git a/Documentation/devicetree/bindings/arm/arm,vexpress-scc.yaml b/Documentation/devicetree/bindings/arm/arm,vexpress-scc.yaml
new file mode 100644
index 000000000000..7870410211a0
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/arm,vexpress-scc.yaml
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/arm,vexpress-scc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ARM Versatile Express Serial Configuration Controller
+
+maintainers:
+ - Pawel Moll <pawel.moll@arm.com>
+
+description: |
+ Test chips for ARM Versatile Express platform implement SCC (Serial
+ Configuration Controller) interface, used to set initial conditions
+ for the test chip.
+
+ In some cases its registers are also mapped in normal address space
+ and can be used to obtain runtime information about the chip internals
+ (like silicon temperature sensors) and as interface to other subsystems
+ like platform configuration control and power management.
+
+properties:
+ compatible:
+ items:
+ - pattern: "^arm,vexpress-scc,[a-z0-9_-]+$"
+ - const: arm,vexpress-scc
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+
+additionalProperties: false
+
+examples:
+ - |
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ scc@7fff0000 {
+ compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
+ reg = <0 0x7fff0000 0 0x1000>;
+ interrupts = <0 95 4>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/arm/vexpress-scc.txt b/Documentation/devicetree/bindings/arm/vexpress-scc.txt
deleted file mode 100644
index ae5043e42e5d..000000000000
--- a/Documentation/devicetree/bindings/arm/vexpress-scc.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-ARM Versatile Express Serial Configuration Controller
------------------------------------------------------
-
-Test chips for ARM Versatile Express platform implement SCC (Serial
-Configuration Controller) interface, used to set initial conditions
-for the test chip.
-
-In some cases its registers are also mapped in normal address space
-and can be used to obtain runtime information about the chip internals
-(like silicon temperature sensors) and as interface to other subsystems
-like platform configuration control and power management.
-
-Required properties:
-
-- compatible value: "arm,vexpress-scc,<model>", "arm,vexpress-scc";
- where <model> is the full tile model name (as used
- in the tile's Technical Reference Manual),
- eg. for Coretile Express A15x2 A7x3 (V2P-CA15_A7):
- compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
-
-Optional properties:
-
-- reg: when the SCC is memory mapped, physical address and size of the
- registers window
-- interrupts: when the SCC can generate a system-level interrupt
-
-Example:
-
- scc@7fff0000 {
- compatible = "arm,vexpress-scc,v2p-ca15_a7", "arm,vexpress-scc";
- reg = <0 0x7fff0000 0 0x1000>;
- interrupts = <0 95 4>;
- };
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2] arm64: dts: qcom: x1e80100-dell-xps13-9345: enable onboard accelerometers
From: Aleksandrs Vinarskis @ 2026-03-31 17:28 UTC (permalink / raw)
To: Val Packett
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, laurentiu.tudor1, linux-arm-msm, devicetree,
linux-kernel, Dmitry Baryshkov
In-Reply-To: <33c0a723-7748-4199-9623-7ed3eed8bfa0@packett.cool>
On Tuesday, March 31st, 2026 at 19:05, Val Packett <val@packett.cool> wrote:
> On 3/31/26 10:36 AM, Aleksandrs Vinarskis wrote:
> > Particular laptop comes with two sets of sensors:
> > 1. Motherboard: accelerometer
> > 2. Display/Camera module: accelerometer, ambient ligth (and more)
> > sensor
> >
> > Both i2c busses are bound to Snapdragon Sensor Core (SSC) and are
> > typically controlled by (A)DSP thus allowing for great power
> > efficiency. This however requires DSP libraries matching ADSP firmware,
> > sensors descriptions (must be extracted from Windows) and other
> > potentially closed-source libraries. Opensource tooling includes
> > `libssc` and `hexagonrpcd`, but they were not verified to be working.
> >
> > Until SSC support for X1E lands, bitbang both i2c busses to enable
> > accelerometer functionality. In the future if/when sensors on this
> > platform can be used from DSP directly, this commit can be reverted.
> >
> > [..]
>
> WDYM by "support lands"? It's a userspace setup thing, nothing new
> should be required in the kernel.
Hi Val,
In v1 discussion [1] it was mentioned that libssc was never tested on
X1E and is likely missing required libraries. I have briefly looked
into getting `hexagonrpcd` to run without much success (though I have
to admit, I only spend a few hours on it). It seems just having
`hexagonrpcd` and sensors .json files (extracted from Windows) is not
enough.
>
> It is amazing that this bitbanging works here, I don't think it was
> expected to ever work on anything newer than msm89x7 o.0
Been running it for a few weeks now without issues.
>
> But this is likely inefficient… and "stealing" GPIOs from ADSP like this
> sounds rather scary. And would definitely break SSC initialization for
> anyone wanting to bring up hexagonrpcd/iio-sensor-proxy.
Do you have any experience with `hexagonrpcd` on X1/X1E specifically?
Personally, I think it still better to go with always-working bit-bang
approach over correctly implemented but 'needs userspace customization
and firmware' approach, as in practice it means very few people will
get it to work. However, if its possible to get SSC to run X1/X1E it
would be a very good argument to drop this patch, up to maintainers'
discretion.
Speaking more broadly, while the accelerometer is not such a highly
needed feature, ALS is, as it can be used for automatic screen brightness,
keyboard backlight controls. I am planning to port ALS's driver to get
those features next, depending on sensor model other laptops would be
able to benefit from that as well, iff bit-banging approach is chosen.
Alex
[1] https://lore.kernel.org/all/20260228-dell-xps-9345-accel-v1-1-daf9e3b3b5ee@vinarskis.com/
>
> ~val
>
>
^ permalink raw reply
* [PATCH v9 2/6] mfd: add NXP MC33978/MC34978 core driver
From: Oleksij Rempel @ 2026-03-31 17:16 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Peter Rosin, Linus Walleij
Cc: Oleksij Rempel, kernel, linux-kernel, devicetree, linux-hwmon,
linux-gpio, David Jander
In-Reply-To: <20260331171612.102018-1-o.rempel@pengutronix.de>
Add core Multi-Function Device (MFD) driver for the NXP MC33978 and
MC34978 Multiple Switch Detection Interfaces (MSDI).
The MC33978/MC34978 devices provide 22 switch detection inputs, analog
multiplexing (AMUX), and comprehensive hardware fault detection.
This core driver handles:
- SPI communications via a custom regmap bus to support the device's
pipelined two-frame MISO response requirement.
- Power sequencing for the VDDQ (logic) and VBATP (battery) regulators.
- Interrupt demultiplexing, utilizing an irq_domain to provide 22 virtual
IRQs for switch state changes and 1 virtual IRQ for hardware faults.
- Inline status harvesting from the SPI MSB to detect and trigger events
without requiring dedicated status register polling.
Child devices (pinctrl, hwmon, mux) are instantiated by the core driver
from match data.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v9:
- Fix null irq_domain dereference from debugfs race by initializing IRQ domain
early before regmap initialization.
- Refactor mc33978_handle_fault_condition() to improve readability by keeping
variable declarations at the top and adding inline comments.
- Fix spurious transient fault events caused by redundant STAT_FAULT flags
during event loop.
- Fix spurious interrupt loops by explicitly returning -ENODATA in
mc33978_rx_decode() for registers without status bits.
- Validate hwirq bounds in mc33978_irq_domain_alloc() to prevent corruption
of irq_rise/irq_fall bitmasks by malformed device tree inputs.
- set DOMAIN_BUS_NEXUS
- Protect work on teardown
- remove IRQF_SHARED
changes v8:
- Fix TOCTOU race condition in SPI event harvesting loop by grabbing
harvested_flags before hardware reads.
- Fix broken hierarchical IRQ allocation by replacing
irq_domain_set_hwirq_and_chip() with irq_domain_set_info() and passing
the handle_simple_irq flow handler.
- Fix out-of-bounds stack read and endianness bug in for_each_set_bit() by
typing fired_pins as unsigned long instead of casting u32.
- Prevent DMA cacheline corruption by explicitly aligning rx_frame with
____cacheline_aligned to separate it from tx_frame.
- Prevent spurious IRQs by verifying irq_find_mapping() returns non-zero
before calling handle_nested_irq().
- Prevent missed transient hardware faults by explicitly evaluating
hw_flags in mc33978_handle_fault_condition().
- Fix missing memory barrier in mc33978_harvest_status() with
smp_mb__after_atomic() to ensure harvested_flags visibility.
- Fix devres use-after-free teardown race by using INIT_WORK and a custom
cancel action after the IRQ domain is destroyed, instead of
devm_work_autocancel.
- Prevent spurious pin interrupts on boot by priming cached_pin_state via
a regmap_read() during probe before enabling IRQs.
- Implement .irq_set_wake callback to support system wake from
hardware faults and switch state changes.
changes v7:
- Fix event handling race condition with smp_mb()
- Replace INIT_WORK() with devm_work_autocancel()
changes v6:
- Remove the hardcoded bypass in irq_set_type to allow child drivers to
configure the FAULT line for edge-triggering.
- Implement software edge-detection for FAULT interrupt.
- Add MC33978_FAULT_ALARM_MASK to the shared header for child devices
- Use READ_ONCE() and WRITE_ONCE() for lockless shared state variables
(cached_pin_mask, irq_rise, irq_fall, bus_fault_active,
cached_fault_active) accessed across the SPI harvesting context and
the event worker.
- Add an if (hwirq < MC33978_NUM_PINS) guard in irq_mask() and
irq_unmask() to prevent the FAULT hwirq (22) from altering the
physical pin mask registers.
- Lowercase the error strings in dev_err_probe()
- Add inline comments explaining the irq_map fallback behavior
changes v5:
- no changes
changes v4:
- Removed .of_compatible strings from the mfd_cell arrays
changes v3:
- Select IRQ_DOMAIN_HIERARCHY in Kconfig
- Add .alloc and .free callbacks to irq_domain_ops to support hierarchical
IRQ domains
- Set IRQ_DOMAIN_FLAG_HIERARCHY flag on the core MFD irq_domain
- replace manual lock/unlock with guard()
changes v2:
- Rewrite the driver header comment
- Explicitly reject IRQ_TYPE_LEVEL_HIGH and IRQ_TYPE_LEVEL_LOW in
mc33978_irq_set_type() to correctly reflect the hardware's edge-only
interrupt capabilities.
- Pass the hardware fault IRQ to the hwmon child driver via mfd_cell
resources, rather than requiring the child to parse the parent's irq_domain.
- Ensure the Kconfig strictly depends on OF and SPI
---
drivers/mfd/Kconfig | 15 +
drivers/mfd/Makefile | 2 +
drivers/mfd/mc33978.c | 1061 +++++++++++++++++++++++++++++++++++
include/linux/mfd/mc33978.h | 92 +++
4 files changed, 1170 insertions(+)
create mode 100644 drivers/mfd/mc33978.c
create mode 100644 include/linux/mfd/mc33978.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..6dc9554822c9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2566,6 +2566,21 @@ 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 industrial input controller core"
+ depends on OF
+ depends on SPI
+ select IRQ_DOMAIN_HIERARCHY
+ select MFD_CORE
+ select REGMAP
+ help
+ Support for the NXP MC33978/MC34978 industrial input controllers
+ using the SPI interface.
+
+ This driver provides common support for accessing the device.
+ Additional drivers must be enabled in order to use the functionality
+ of the device.
+
config MFD_MAX7360
tristate "Maxim MAX7360 I2C IO Expander"
depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..dcd99315f683 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..c017d295503f
--- /dev/null
+++ b/drivers/mfd/mc33978.c
@@ -0,0 +1,1061 @@
+// 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 Multiple Switch Detection Interface - MFD Core Driver
+ *
+ * Driver Architecture:
+ * This is the core MFD driver handling the physical SPI interface, power
+ * management, and central interrupt routing. It instantiates the following
+ * child devices:
+ * - pinctrl: For GPIO read/write and wetting current configuration.
+ * - hwmon: For hardware fault monitoring (tLIM, over/under-voltage).
+ * - mux: For the 24-to-1 analog multiplexer (AMUX).
+ *
+ * Custom SPI Regmap & Event Harvesting:
+ * The device uses a non-standard pipelined SPI protocol where the MISO
+ * response logically lags the MOSI command by one frame. Furthermore, the
+ * hardware embeds volatile global status bits (INT_flg, FAULT_STAT) into the
+ * high byte of almost every SPI response (with specific exceptions handled by
+ * the decoder). This core implements a custom regmap_bus to handle the
+ * 2-frame dummy fetches and transparently "harvests" these status bits in
+ * the background to schedule event processing.
+ *
+ * Interrupt Quirks & Limitations:
+ * - Clear-on-Read: The physical INT_B line is directly tied to the INT_flg
+ * bit. The hardware deasserts INT_B immediately upon *any* SPI transfer
+ * that returns INT_flg. Harvesting this bit from all SPI traffic is the
+ * ONLY way to know this device triggered an interrupt (crucial for shared
+ * IRQ lines).
+ * - Stateless Pin Edge Detection: The hardware lacks per-pin interrupt status
+ * registers. To determine which pin triggered an event, the driver must
+ * read the current pin states and XOR them against a previously cached state.
+ * - Missed Short Pulses: Because pin interrupts are state-derived rather than
+ * hardware-latched, very short physical pulses (shorter than the SPI read
+ * latency) will be missed entirely if the pin reverts to its original state
+ * before the READ_IN register is sampled by the IRQ thread.
+ * - Edge-Only Pin Interrupts: The hardware only asserts INT_B on a state
+ * change. It cannot continuously assert an interrupt while a pin is held at a
+ * specific logic level. Consequently, the driver strictly emulates edge
+ * interrupts (RISING/FALLING) and explicitly rejects LEVEL interrupt
+ * configurations to prevent consumer misalignment.
+ */
+
+#include <linux/array_size.h>
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cache.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/devm-helpers.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/string.h>
+
+#include <linux/mfd/mc33978.h>
+
+#define MC33978_DRV_NAME "mc33978"
+
+/* Device identification signature returned by CHECK register */
+#define MC33978_CHECK_SIGNATURE 0x123456
+
+/*
+ * Pipelined two-frame SPI transfer:
+ * [REQ] - Transmits command/write-data, receives dummy/previous response
+ * [PIPE] - Transmits dummy CHECK, receives actual response to current command
+ */
+enum mc33978_frame_index {
+ MC33978_FRAME_REQ = 0,
+ MC33978_FRAME_PIPE,
+ MC33978_FRAME_COUNT
+};
+
+/* SPI frame byte offsets (transmitted MSB first) */
+enum mc33978_frame_offset {
+ MC33978_FRAME_CMD = 0,
+ MC33978_FRAME_DATA_HI,
+ MC33978_FRAME_DATA_MID,
+ MC33978_FRAME_DATA_LO
+};
+
+#define MC33978_FRAME_LEN 4
+
+/* Regmap internal value buffer offsets */
+enum mc33978_payload_offset {
+ MC33978_PAYLOAD_HI = 0,
+ MC33978_PAYLOAD_MID,
+ MC33978_PAYLOAD_LO
+};
+
+#define MC33978_PAYLOAD_LEN 3
+
+/*
+ * SPI Command Byte (FRAME_CMD).
+ * Maps to frame bit [24] in the datasheet.
+ */
+#define MC33978_CMD_BYTE_WRITE BIT(0)
+
+/* High Payload Byte Masks (FRAME_DATA_HI / PAYLOAD_HI). */
+#define MC33978_HI_BYTE_STAT_FAULT BIT(7) /* Maps to frame bit [23] */
+#define MC33978_HI_BYTE_STAT_INT BIT(6) /* Maps to frame bit [22] */
+
+#define MC33978_HI_BYTE_STATUS_MASK (MC33978_HI_BYTE_STAT_FAULT | \
+ MC33978_HI_BYTE_STAT_INT)
+
+/* Maps to frame bits [21:16] */
+#define MC33978_HI_BYTE_DATA_MASK GENMASK(5, 0)
+
+#define MC33978_CACHE_SG_PIN_MASK GENMASK(13, 0)
+#define MC33978_CACHE_SP_PIN_MASK GENMASK(21, 14)
+
+#define MC33978_SG_PIN_MASK GENMASK(13, 0)
+#define MC33978_SP_PIN_MASK GENMASK(7, 0)
+
+struct mc33978_data {
+ const struct mfd_cell *cells;
+ int num_cells;
+};
+
+struct mc33978_mfd_priv {
+ /* Immutable after initialization (no lock needed) */
+ struct spi_device *spi;
+ struct regmap *map;
+ struct regulator *vddq;
+ struct regulator *vbatp;
+ struct irq_domain *domain;
+
+ /* Pre-built SPI messages (immutable after init) */
+ struct spi_message msg_read;
+ struct spi_message msg_write;
+ struct spi_transfer xfer_read[MC33978_FRAME_COUNT];
+ struct spi_transfer xfer_write;
+
+ /* Protected by event_lock */
+ struct mutex event_lock;
+ u32 cached_pin_state; /* Previous pin state for edge detection */
+
+ /* Protected by irq_lock */
+ struct mutex irq_lock;
+ u32 cached_pin_mask; /* IRQ mask for 22 pins */
+ u32 irq_rise; /* Rising edge IRQ enable mask */
+ u32 irq_fall; /* Falling edge IRQ enable mask */
+
+ /* Protected by teardown_lock */
+ spinlock_t teardown_lock;
+ bool tearing_down; /* Prevents work scheduling during teardown */
+
+ /* Atomic operations (no lock needed) */
+ atomic_t harvested_flags; /* Status bits from SPI responses */
+
+ /*
+ * Cross-context lockless access (READ_ONCE/WRITE_ONCE).
+ * Accessed from regmap callbacks (unpredictable context) and event work.
+ * Cannot use lock in regmap callback. Benign race acceptable.
+ */
+ bool bus_fault_active; /* Latest physical fault state on bus */
+ bool cached_fault_active; /* Cached fault state from previous event */
+
+ /*
+ * Work scheduling protected by teardown_lock.
+ * Work execution serialized by workqueue subsystem.
+ */
+ struct work_struct event_work;
+
+ /*
+ * DMA buffers protected by SPI subsystem + regmap serialization.
+ * Modified before spi_sync(), read after it returns.
+ * Must be at end for ____cacheline_aligned.
+ */
+ u8 tx_frame[MC33978_FRAME_COUNT][MC33978_FRAME_LEN] ____cacheline_aligned;
+ u8 rx_frame[MC33978_FRAME_COUNT][MC33978_FRAME_LEN] ____cacheline_aligned;
+};
+
+static void mc33978_irq_mask(struct irq_data *data)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (hwirq < MC33978_NUM_PINS)
+ mc->cached_pin_mask &= ~BIT(hwirq);
+}
+
+static void mc33978_irq_unmask(struct irq_data *data)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (hwirq < MC33978_NUM_PINS)
+ mc->cached_pin_mask |= BIT(hwirq);
+}
+
+static void mc33978_irq_bus_lock(struct irq_data *data)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&mc->irq_lock);
+}
+
+/**
+ * mc33978_irq_bus_sync_unlock() - Sync cached IRQ mask to hardware and unlock
+ * @data: IRQ data
+ *
+ * Writes the cached interrupt mask to the hardware IE_SG and IE_SP registers,
+ * then releases the IRQ lock. This is where the actual hardware update occurs
+ * after mask/unmask operations.
+ */
+static void mc33978_irq_bus_sync_unlock(struct irq_data *data)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+ u32 sg_mask, sp_mask, cached_mask;
+ int ret;
+
+ cached_mask = mc->cached_pin_mask;
+
+ /*
+ * Split the cached 22-bit pin mask into hardware register format:
+ * - SG pins: bits [13:0] (14 pins, mask 0x3FFF)
+ * - SP pins: bits [21:14] (8 pins, mask 0xFF)
+ */
+ sg_mask = FIELD_GET(MC33978_CACHE_SG_PIN_MASK, cached_mask);
+ sp_mask = FIELD_GET(MC33978_CACHE_SP_PIN_MASK, cached_mask);
+
+ ret = regmap_update_bits(mc->map, MC33978_REG_IE_SG,
+ MC33978_SG_PIN_MASK, sg_mask);
+ if (ret)
+ goto unlock;
+
+ ret = regmap_update_bits(mc->map, MC33978_REG_IE_SP,
+ MC33978_SP_PIN_MASK, sp_mask);
+unlock:
+ if (ret)
+ dev_err(&mc->spi->dev, "failed to sync IRQ mask to hardware: %d\n",
+ ret);
+
+ mutex_unlock(&mc->irq_lock);
+}
+
+static int mc33978_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+ u32 mask = BIT(hwirq);
+
+ if (type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
+ return -EINVAL;
+
+ /*
+ * No locking needed here - irq_bus_lock/irq_bus_sync_unlock
+ * already provide serialization via mc->irq_lock mutex.
+ */
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ mc->irq_rise |= mask;
+ else
+ mc->irq_rise &= ~mask;
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ mc->irq_fall |= mask;
+ else
+ mc->irq_fall &= ~mask;
+
+ return 0;
+}
+
+static int mc33978_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct mc33978_mfd_priv *mc = irq_data_get_irq_chip_data(data);
+
+ return irq_set_irq_wake(mc->spi->irq, on);
+}
+
+static struct irq_chip mc33978_irq_chip = {
+ .name = MC33978_DRV_NAME,
+ .irq_mask = mc33978_irq_mask,
+ .irq_unmask = mc33978_irq_unmask,
+ .irq_bus_lock = mc33978_irq_bus_lock,
+ .irq_bus_sync_unlock = mc33978_irq_bus_sync_unlock,
+ .irq_set_type = mc33978_irq_set_type,
+ .irq_set_wake = mc33978_irq_set_wake,
+};
+
+static int mc33978_irq_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct mc33978_mfd_priv *mc = d->host_data;
+
+ irq_set_chip_data(virq, mc);
+ irq_set_chip_and_handler(virq, &mc33978_irq_chip, handle_simple_irq);
+
+ irq_set_nested_thread(virq, 1);
+ irq_clear_status_flags(virq, IRQ_NOREQUEST | IRQ_NOPROBE);
+
+ return 0;
+}
+
+static int mc33978_irq_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct mc33978_mfd_priv *mc = domain->host_data;
+ struct irq_fwspec *fwspec = arg;
+ irq_hw_number_t hwirq;
+ int i;
+
+ if (fwspec->param_count < 1)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+
+ if (hwirq >= MC33978_HWIRQ_FAULT + 1 ||
+ nr_irqs > MC33978_HWIRQ_FAULT + 1 - hwirq)
+ return -EINVAL;
+
+ for (i = 0; i < nr_irqs; i++) {
+ irq_domain_set_info(domain, virq + i, hwirq + i,
+ &mc33978_irq_chip, mc,
+ handle_simple_irq, NULL, NULL);
+ irq_set_nested_thread(virq + i, 1);
+ irq_clear_status_flags(virq + i, IRQ_NOREQUEST | IRQ_NOPROBE);
+ }
+
+ return 0;
+}
+
+static void mc33978_irq_domain_free(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs)
+{
+ int i;
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_reset_irq_data(irq_domain_get_irq_data(domain,
+ virq + i));
+}
+
+static const struct irq_domain_ops mc33978_irq_domain_ops = {
+ .map = mc33978_irq_map,
+ .alloc = mc33978_irq_domain_alloc,
+ .free = mc33978_irq_domain_free,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void mc33978_irq_domain_remove(void *data)
+{
+ struct irq_domain *domain = data;
+
+ irq_domain_remove(domain);
+}
+
+static bool mc33978_handle_pin_changes(struct mc33978_mfd_priv *mc,
+ unsigned int pin_state)
+{
+ unsigned long fired_pins = 0;
+ u32 changed_pins;
+ u32 rise, fall, pin_mask;
+ int i;
+
+ changed_pins = pin_state ^ mc->cached_pin_state;
+ if (!changed_pins)
+ return false;
+
+ mc->cached_pin_state = pin_state;
+
+ scoped_guard(mutex, &mc->irq_lock) {
+ pin_mask = mc->cached_pin_mask;
+ rise = mc->irq_rise;
+ fall = mc->irq_fall;
+ }
+
+ changed_pins &= pin_mask;
+
+ if (!changed_pins)
+ return false;
+
+ fired_pins |= (changed_pins & pin_state) & rise;
+ fired_pins |= (changed_pins & ~pin_state) & fall;
+
+ for_each_set_bit(i, &fired_pins, MC33978_NUM_PINS) {
+ int virq = irq_find_mapping(mc->domain, i);
+
+ if (virq)
+ handle_nested_irq(virq);
+ }
+
+ return true;
+}
+
+static bool mc33978_handle_fault_condition(struct mc33978_mfd_priv *mc,
+ u8 hw_flags)
+{
+ bool fault_active, cached_fault, transient, changed;
+ bool handled = false;
+ u32 rise, fall;
+ int virq;
+
+ /* Read the absolute latest physical state seen on the bus */
+ fault_active = READ_ONCE(mc->bus_fault_active);
+
+ /* Read the cached fault state from the previous event loop */
+ cached_fault = READ_ONCE(mc->cached_fault_active);
+
+ /* Check if the fault state has changed since the last event loop */
+ changed = fault_active ^ cached_fault;
+ if (changed)
+ WRITE_ONCE(mc->cached_fault_active, fault_active);
+
+ /*
+ * A transient fault is a pulse that was caught by the clear-on-read
+ * status flags, but is no longer physically active on the bus.
+ */
+ transient = !changed && !fault_active &&
+ (hw_flags & MC33978_HI_BYTE_STAT_FAULT);
+
+ if (!changed && !transient)
+ return false;
+
+ scoped_guard(mutex, &mc->irq_lock) {
+ rise = mc->irq_rise;
+ fall = mc->irq_fall;
+ }
+
+ virq = irq_find_mapping(mc->domain, MC33978_HWIRQ_FAULT);
+ if (!virq)
+ return false;
+
+ if (transient) {
+ /* Transient pulse: trigger both edges if enabled */
+ if (rise & BIT(MC33978_HWIRQ_FAULT)) {
+ handle_nested_irq(virq);
+ handled = true;
+ }
+ if (fall & BIT(MC33978_HWIRQ_FAULT)) {
+ handle_nested_irq(virq);
+ handled = true;
+ }
+ } else if ((fault_active && (rise & BIT(MC33978_HWIRQ_FAULT))) ||
+ (!fault_active && (fall & BIT(MC33978_HWIRQ_FAULT)))) {
+ /* Normal edge */
+ handle_nested_irq(virq);
+ handled = true;
+ }
+
+ return handled;
+}
+
+static bool mc33978_process_single_event(struct mc33978_mfd_priv *mc)
+{
+ unsigned int pin_state;
+ bool handled = false;
+ u8 hw_flags;
+ int ret;
+
+ /*
+ * Grab harvested_flags BEFORE reading the hardware. If the read itself
+ * or a concurrent SPI transfer harvests new flags, they will remain set
+ * in harvested_flags and correctly trigger another pass of the event
+ * loop.
+ *
+ * Note on Performance: This architecture intentionally forces a second
+ * (redundant) SPI read of READ_IN during almost every interrupt event.
+ * While SPI framework overhead (CS toggling, DMA setup, context
+ * switches) makes this 4-byte transfer relatively costly, it is
+ * mathematically necessary to guarantee no edge events are permanently
+ * lost when a concurrent regmap access races with the IRQ thread, due
+ * to the hardware's clear-on-read global INT_flg design.
+ */
+ hw_flags = atomic_xchg(&mc->harvested_flags, 0);
+
+ ret = regmap_read(mc->map, MC33978_REG_READ_IN, &pin_state);
+ if (ret)
+ return false;
+
+ if (mc33978_handle_pin_changes(mc, pin_state))
+ handled = true;
+
+ if (mc33978_handle_fault_condition(mc, hw_flags))
+ handled = true;
+
+ if (hw_flags & MC33978_HI_BYTE_STAT_INT)
+ handled = true;
+
+ return handled;
+}
+
+static bool mc33978_handle_events(struct mc33978_mfd_priv *mc)
+{
+ bool handled = false;
+
+ guard(mutex)(&mc->event_lock);
+
+ do {
+ if (mc33978_process_single_event(mc))
+ handled = true;
+ } while (atomic_read(&mc->harvested_flags) != 0);
+
+ return handled;
+}
+
+static irqreturn_t mc33978_irq_thread(int irq, void *data)
+{
+ return mc33978_handle_events(data) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void mc33978_teardown(void *data)
+{
+ struct mc33978_mfd_priv *mc = data;
+
+ /*
+ * During the devres LIFO teardown window, the workqueue is canceled
+ * before the regmap is destroyed. A concurrent debugfs regmap read
+ * can trigger mc33978_harvest_status() and wrongly reschedule the
+ * workqueue after it was already canceled.
+ *
+ * Flag the teardown state under a lock so the harvester atomically
+ * checks and ignores status bits before scheduling new work.
+ */
+ scoped_guard(spinlock_irqsave, &mc->teardown_lock) {
+ mc->tearing_down = true;
+ }
+
+ cancel_work_sync(&mc->event_work);
+}
+
+static int mc33978_irq_init(struct mc33978_mfd_priv *mc,
+ struct fwnode_handle *fwnode)
+{
+ struct device *dev = &mc->spi->dev;
+ int ret;
+
+ mutex_init(&mc->irq_lock);
+
+ /*
+ * Create IRQ domain with 23 interrupts:
+ * - hwirq 0-21: Pin change interrupts (22 pins)
+ * - hwirq 22: Fault interrupt (for hwmon driver)
+ */
+ mc->domain = irq_domain_create_linear(fwnode, MC33978_NUM_PINS + 1,
+ &mc33978_irq_domain_ops, mc);
+ if (!mc->domain)
+ return dev_err_probe(dev, -ENOMEM, "failed to create IRQ domain\n");
+
+ /*
+ * Use DOMAIN_BUS_NEXUS to distinguish this intermediate demux domain
+ * from child domains sharing the same fwnode. Matches the pattern used
+ * by other MFD drivers (e.g., crystalcove).
+ */
+ irq_domain_update_bus_token(mc->domain, DOMAIN_BUS_NEXUS);
+
+ mc->domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
+
+ ret = devm_add_action_or_reset(dev, mc33978_irq_domain_remove,
+ mc->domain);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void mc33978_event_work(struct work_struct *work)
+{
+ struct mc33978_mfd_priv *mc =
+ container_of(work, struct mc33978_mfd_priv, event_work);
+
+ mc33978_handle_events(mc);
+}
+
+/**
+ * mc33978_harvest_status() - Collect status flags from SPI responses
+ * @mc: Device private data
+ * @status: Status bits (FAULT_STAT and INT_flg) from MISO frame
+ *
+ * Accumulates status flags harvested from SPI responses and schedules
+ * event processing if not already in progress. Called by the SPI
+ * read/write functions when status bits are detected in responses.
+ */
+static void mc33978_harvest_status(struct mc33978_mfd_priv *mc, int status)
+{
+ bool fault_active;
+
+ fault_active = !!(status & MC33978_HI_BYTE_STAT_FAULT);
+
+ /* Track the absolute latest physical state seen on the bus */
+ WRITE_ONCE(mc->bus_fault_active, fault_active);
+
+ /*
+ * If the bus state changed from what the IRQ thread last evaluated,
+ * wake it up.
+ */
+ if (fault_active != READ_ONCE(mc->cached_fault_active))
+ atomic_or(MC33978_HI_BYTE_STAT_FAULT, &mc->harvested_flags);
+
+ if (status & MC33978_HI_BYTE_STAT_INT)
+ atomic_or(MC33978_HI_BYTE_STAT_INT, &mc->harvested_flags);
+
+ /* Ensure harvested_flags is visible before checking teardown state */
+ smp_mb__after_atomic();
+
+ scoped_guard(spinlock_irqsave, &mc->teardown_lock) {
+ if (!mc->tearing_down && atomic_read(&mc->harvested_flags))
+ schedule_work(&mc->event_work);
+ }
+}
+
+/**
+ * mc33978_prepare_messages() - Initialize the persistent SPI messages
+ * @mc: Device private data
+ *
+ * Hardware pipelining constraints:
+ * - Write (1 Frame): The device executes write commands immediately upon
+ * CS de-assertion. No fetch frame is required.
+ * - Read (2 Frames): The MISO response logically lags by one frame.
+ * Frame 1 transmits the read request and toggles CS to latch it.
+ * Frame 2 transmits a dummy CHECK command to fetch the actual payload.
+ */
+static void mc33978_prepare_messages(struct mc33978_mfd_priv *mc)
+{
+ /* --- Prepare Write Message (1 Frame) --- */
+ spi_message_init(&mc->msg_write);
+
+ mc->xfer_write.tx_buf = mc->tx_frame[MC33978_FRAME_REQ];
+ mc->xfer_write.rx_buf = mc->rx_frame[MC33978_FRAME_REQ];
+ mc->xfer_write.len = MC33978_FRAME_LEN;
+
+ spi_message_add_tail(&mc->xfer_write, &mc->msg_write);
+
+ /* --- Prepare Read Message (2 Frames) --- */
+ spi_message_init(&mc->msg_read);
+
+ /* Frame 1: Request */
+ mc->xfer_read[MC33978_FRAME_REQ].tx_buf =
+ mc->tx_frame[MC33978_FRAME_REQ];
+ mc->xfer_read[MC33978_FRAME_REQ].rx_buf =
+ mc->rx_frame[MC33978_FRAME_REQ];
+ mc->xfer_read[MC33978_FRAME_REQ].len = MC33978_FRAME_LEN;
+ mc->xfer_read[MC33978_FRAME_REQ].cs_change = 1; /* Latch command */
+
+ /* Frame 2: Fetch (Dummy CHECK) */
+ mc->xfer_read[MC33978_FRAME_PIPE].tx_buf =
+ mc->tx_frame[MC33978_FRAME_PIPE];
+ mc->xfer_read[MC33978_FRAME_PIPE].rx_buf =
+ mc->rx_frame[MC33978_FRAME_PIPE];
+ mc->xfer_read[MC33978_FRAME_PIPE].len = MC33978_FRAME_LEN;
+
+ /* Preload the dummy CHECK command statically */
+ mc->tx_frame[MC33978_FRAME_PIPE][MC33978_FRAME_CMD] = MC33978_REG_CHECK;
+
+ spi_message_add_tail(&mc->xfer_read[MC33978_FRAME_REQ], &mc->msg_read);
+ spi_message_add_tail(&mc->xfer_read[MC33978_FRAME_PIPE], &mc->msg_read);
+}
+
+/**
+ * mc33978_rx_decode() - Decode MISO response frame and extract status
+ * @rx_frame: Received SPI frame buffer (4 bytes)
+ * @val_buf: Output buffer for regmap (exactly 3 bytes, optional)
+ *
+ * Translates the 4-byte SPI response into a 3-byte regmap payload.
+ * Harvests the volatile INTflg and FAULT_STAT bits from the MSB.
+ *
+ * Note: MC33978_REG_CHECK, MC33978_REG_WET_SP, and MC33978_REG_WET_SG0 do not
+ * contain fault status or interrupt flags.
+ *
+ * Return: Status bits if present, negative error code otherwise.
+ */
+static int mc33978_rx_decode(const u8 *rx_frame, u8 *val_buf)
+{
+ u8 cmd = rx_frame[MC33978_FRAME_CMD] & ~MC33978_CMD_BYTE_WRITE;
+ bool has_status;
+ u8 status = 0;
+
+ switch (cmd) {
+ case MC33978_REG_CHECK:
+ case MC33978_REG_WET_SP:
+ case MC33978_REG_WET_SG0:
+ has_status = false;
+ break;
+ default:
+ has_status = true;
+ break;
+ }
+
+ if (has_status)
+ status = rx_frame[MC33978_FRAME_DATA_HI] &
+ MC33978_HI_BYTE_STATUS_MASK;
+
+ if (val_buf) {
+ memcpy(val_buf, &rx_frame[MC33978_FRAME_DATA_HI],
+ MC33978_PAYLOAD_LEN);
+
+ if (has_status)
+ val_buf[MC33978_PAYLOAD_HI] &= MC33978_HI_BYTE_DATA_MASK;
+ }
+
+ return has_status ? status : -ENODATA;
+}
+
+static int mc33978_spi_write(void *ctx, const void *data, size_t count)
+{
+ struct mc33978_mfd_priv *mc = ctx;
+ int status;
+ int ret;
+
+ if (count != MC33978_FRAME_LEN)
+ return -EINVAL;
+
+ memcpy(mc->tx_frame[MC33978_FRAME_REQ], data, MC33978_FRAME_LEN);
+
+ ret = spi_sync(mc->spi, &mc->msg_write);
+ if (ret)
+ return ret;
+
+ status = mc33978_rx_decode(mc->rx_frame[MC33978_FRAME_REQ], NULL);
+ if (status >= 0)
+ mc33978_harvest_status(mc, status);
+
+ return 0;
+}
+
+static int mc33978_spi_read(void *ctx, const void *reg_buf, size_t reg_size,
+ void *val_buf, size_t val_size)
+{
+ struct mc33978_mfd_priv *mc = ctx;
+ int status_req, status_pipe;
+ int ret;
+
+ if (reg_size != 1 || val_size != MC33978_PAYLOAD_LEN)
+ return -EINVAL;
+
+ memset(&mc->tx_frame[MC33978_FRAME_REQ][MC33978_FRAME_DATA_HI], 0,
+ MC33978_PAYLOAD_LEN);
+ mc->tx_frame[MC33978_FRAME_REQ][MC33978_FRAME_CMD] =
+ ((const u8 *)reg_buf)[0];
+
+ ret = spi_sync(mc->spi, &mc->msg_read);
+ if (ret)
+ return ret;
+
+ status_req = mc33978_rx_decode(mc->rx_frame[MC33978_FRAME_REQ], NULL);
+ status_pipe = mc33978_rx_decode(mc->rx_frame[MC33978_FRAME_PIPE],
+ val_buf);
+
+ if (status_req >= 0)
+ mc33978_harvest_status(mc, status_req);
+ if (status_pipe >= 0)
+ mc33978_harvest_status(mc, status_pipe);
+
+ return 0;
+}
+
+static const struct regmap_bus mc33978_regmap_bus = {
+ .read = mc33978_spi_read,
+ .write = mc33978_spi_write,
+};
+
+static const struct regmap_range mc33978_volatile_range[] = {
+ regmap_reg_range(MC33978_REG_READ_IN, MC33978_REG_RESET),
+};
+
+static const struct regmap_access_table mc33978_volatile_table = {
+ .yes_ranges = mc33978_volatile_range,
+ .n_yes_ranges = ARRAY_SIZE(mc33978_volatile_range),
+};
+
+static const struct regmap_range mc33978_precious_range[] = {
+ regmap_reg_range(MC33978_REG_READ_IN, MC33978_REG_RESET),
+};
+
+static const struct regmap_access_table mc33978_precious_table = {
+ .yes_ranges = mc33978_precious_range,
+ .n_yes_ranges = ARRAY_SIZE(mc33978_precious_range),
+};
+
+/*
+ * NOTE: Need to fake REG_IRQ and REG_RESET as readable, so that regcache
+ * will NOT write to them on a cache sync. Sounds counterintuitive, but marking
+ * a reg as "precious" or "volatile" is the only way to avoid this, and that
+ * works only with readable regs.
+ */
+static const struct regmap_range mc33978_readable_range[] = {
+ regmap_reg_range(MC33978_REG_CHECK, MC33978_REG_WET_SG1),
+ regmap_reg_range(MC33978_REG_CWET_SP, MC33978_REG_WDEB_SG),
+ regmap_reg_range(MC33978_REG_AMUX_CTRL, MC33978_REG_RESET),
+};
+
+static const struct regmap_access_table mc33978_readable_table = {
+ .yes_ranges = mc33978_readable_range,
+ .n_yes_ranges = ARRAY_SIZE(mc33978_readable_range),
+};
+
+static const struct regmap_range mc33978_writable_range[] = {
+ regmap_reg_range(MC33978_REG_CONFIG, MC33978_REG_WET_SG1),
+ regmap_reg_range(MC33978_REG_CWET_SP, MC33978_REG_AMUX_CTRL),
+ regmap_reg_range(MC33978_REG_IRQ, MC33978_REG_RESET),
+};
+
+static const struct regmap_access_table mc33978_writable_table = {
+ .yes_ranges = mc33978_writable_range,
+ .n_yes_ranges = ARRAY_SIZE(mc33978_writable_range),
+};
+
+static const struct regmap_config mc33978_regmap_config = {
+ .name = MC33978_DRV_NAME,
+ .reg_bits = 8,
+ .val_bits = 24,
+ .reg_stride = 2,
+ .write_flag_mask = MC33978_CMD_BYTE_WRITE,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .use_single_read = true,
+ .use_single_write = true,
+ .volatile_table = &mc33978_volatile_table,
+ .precious_table = &mc33978_precious_table,
+ .rd_table = &mc33978_readable_table,
+ .wr_table = &mc33978_writable_table,
+ .cache_type = REGCACHE_MAPLE,
+ .max_register = MC33978_REG_RESET,
+};
+
+static int mc33978_power_on(struct mc33978_mfd_priv *mc)
+{
+ struct device *dev = &mc->spi->dev;
+ int ret;
+
+ ret = regulator_enable(mc->vddq);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable VDDQ supply\n");
+
+ ret = regulator_enable(mc->vbatp);
+ if (ret) {
+ regulator_disable(mc->vddq);
+ return dev_err_probe(dev, ret, "failed to enable VBATP supply\n");
+ }
+
+ return 0;
+}
+
+static void mc33978_power_off(void *data)
+{
+ struct mc33978_mfd_priv *mc = data;
+
+ regulator_disable(mc->vbatp);
+ regulator_disable(mc->vddq);
+}
+
+/**
+ * mc33978_check_device() - Verify SPI communication with device
+ * @mc: Device context
+ *
+ * Reads the CHECK register which should return a fixed signature (0x123456).
+ * This verifies that SPI communication is working correctly.
+ *
+ * Note: MC33978_REG_CHECK does not contain fault status or interrupt flags.
+ * See mc33978_rx_decode() for details.
+ *
+ * Return: 0 on success, -ENODEV if signature doesn't match
+ */
+static int mc33978_check_device(struct mc33978_mfd_priv *mc)
+{
+ struct device *dev = &mc->spi->dev;
+ unsigned int check;
+ int ret;
+
+ ret = regmap_read(mc->map, MC33978_REG_CHECK, &check);
+ if (ret)
+ return ret;
+
+ if (check != MC33978_CHECK_SIGNATURE)
+ return dev_err_probe(dev, -ENODEV,
+ "SPI check failed. Expected: 0x%06x, got: 0x%06x\n",
+ MC33978_CHECK_SIGNATURE, check);
+
+ return 0;
+}
+
+static const struct resource mc33978_hwmon_resources[] = {
+ DEFINE_RES_IRQ(MC33978_HWIRQ_FAULT),
+};
+
+static const struct mfd_cell mc33978_cells[] = {
+ { .name = "mc33978-pinctrl" },
+ {
+ .name = "mc33978-hwmon",
+ .resources = mc33978_hwmon_resources,
+ .num_resources = ARRAY_SIZE(mc33978_hwmon_resources),
+ },
+ { .name = "mc33978-mux" },
+};
+
+static const struct mfd_cell mc34978_cells[] = {
+ { .name = "mc34978-pinctrl" },
+ {
+ .name = "mc34978-hwmon",
+ .resources = mc33978_hwmon_resources,
+ .num_resources = ARRAY_SIZE(mc33978_hwmon_resources),
+ },
+ { .name = "mc34978-mux" },
+};
+
+static const struct mc33978_data mc33978_match_data = {
+ .cells = mc33978_cells,
+ .num_cells = ARRAY_SIZE(mc33978_cells),
+};
+
+static const struct mc33978_data mc34978_match_data = {
+ .cells = mc34978_cells,
+ .num_cells = ARRAY_SIZE(mc34978_cells),
+};
+
+static int mc33978_probe(struct spi_device *spi)
+{
+ const struct mc33978_data *match_data;
+ struct device *dev = &spi->dev;
+ struct fwnode_handle *fwnode;
+ struct mc33978_mfd_priv *mc;
+ int ret;
+
+ fwnode = dev_fwnode(dev);
+ if (!fwnode)
+ return dev_err_probe(dev, -ENODEV, "missing firmware node\n");
+
+ match_data = spi_get_device_match_data(spi);
+ if (!match_data)
+ return dev_err_probe(dev, -ENODEV, "no device match data found\n");
+
+ mc = devm_kzalloc(dev, sizeof(*mc), GFP_KERNEL);
+ if (!mc)
+ return -ENOMEM;
+
+ mc->spi = spi;
+ spi_set_drvdata(spi, mc);
+
+ mc->vddq = devm_regulator_get(dev, "vddq");
+ if (IS_ERR(mc->vddq))
+ return dev_err_probe(dev, PTR_ERR(mc->vddq),
+ "failed to get VDDQ regulator\n");
+
+ mc->vbatp = devm_regulator_get(dev, "vbatp");
+ if (IS_ERR(mc->vbatp))
+ return dev_err_probe(dev, PTR_ERR(mc->vbatp),
+ "failed to get VBATP regulator\n");
+
+ ret = mc33978_power_on(mc);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, mc33978_power_off, mc);
+ if (ret)
+ return ret;
+
+ mutex_init(&mc->event_lock);
+ spin_lock_init(&mc->teardown_lock);
+
+ INIT_WORK(&mc->event_work, mc33978_event_work);
+
+ atomic_set(&mc->harvested_flags, 0);
+
+ mc33978_prepare_messages(mc);
+
+ ret = mc33978_irq_init(mc, fwnode);
+ if (ret)
+ return ret;
+
+ mc->map = devm_regmap_init(dev, &mc33978_regmap_bus, mc,
+ &mc33978_regmap_config);
+ if (IS_ERR(mc->map))
+ return dev_err_probe(dev, PTR_ERR(mc->map), "can't init regmap\n");
+
+ /*
+ * Ensure event_work is canceled before regmap and irq_domain teardown,
+ * since the worker dereferences both mc->map and mc->domain.
+ */
+ ret = devm_add_action_or_reset(dev, mc33978_teardown, mc);
+ if (ret)
+ return ret;
+
+ ret = mc33978_check_device(mc);
+ if (ret)
+ return dev_err_probe(dev, ret, "can't use SPI bus\n");
+
+ /* Disable interrupts to prevent storms during priming */
+ ret = regmap_write(mc->map, MC33978_REG_IE_SP, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(mc->map, MC33978_REG_IE_SG, 0);
+ if (ret)
+ return ret;
+
+ /* Prime the cached pin state under lock to prevent spurious events */
+ scoped_guard(mutex, &mc->event_lock) {
+ ret = regmap_read(mc->map, MC33978_REG_READ_IN,
+ &mc->cached_pin_state);
+ }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read initial pin state\n");
+
+ if (mc->spi->irq <= 0)
+ return dev_err_probe(dev, -EINVAL, "no valid IRQ provided for INT_B pin\n");
+
+ /*
+ * Deliberately not using IRQF_SHARED.
+ *
+ * MC33978 clear-on-read interrupt status can make shared wiring with
+ * another MC33978/MC34978 functionally possible, but this handler runs
+ * threaded with IRQF_ONESHOT and may hold the line masked for a long
+ * time on slow SPI. The added latency/jitter makes shared operation
+ * impractical.
+ */
+ ret = devm_request_threaded_irq(dev, mc->spi->irq,
+ NULL,
+ mc33978_irq_thread,
+ IRQF_ONESHOT,
+ dev_name(dev), mc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request IRQ\n");
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+ match_data->cells, match_data->num_cells,
+ NULL, 0, mc->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 = &mc33978_match_data },
+ { .compatible = "nxp,mc34978", .data = &mc34978_match_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mc33978_of_match);
+
+static const struct spi_device_id mc33978_spi_id[] = {
+ { "mc33978", (kernel_ulong_t)&mc33978_match_data },
+ { "mc34978", (kernel_ulong_t)&mc34978_match_data },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, mc33978_spi_id);
+
+static struct spi_driver mc33978_driver = {
+ .driver = {
+ .name = MC33978_DRV_NAME,
+ .of_match_table = mc33978_of_match,
+ },
+ .probe = mc33978_probe,
+ .id_table = mc33978_spi_id,
+};
+module_spi_driver(mc33978_driver);
+
+MODULE_AUTHOR("David Jander <david@protonic.nl>");
+MODULE_DESCRIPTION("NXP MC33978/MC34978 MFD core driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/mc33978.h b/include/linux/mfd/mc33978.h
new file mode 100644
index 000000000000..e8dec678e5a4
--- /dev/null
+++ b/include/linux/mfd/mc33978.h
@@ -0,0 +1,92 @@
+/* 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
+ *
+ * MC34978/MC33978 Multiple Switch Detection Interface - Shared Definitions
+ */
+
+#ifndef _LINUX_MFD_MC33978_H
+#define _LINUX_MFD_MC33978_H
+
+#include <linux/bits.h>
+
+/* Register Map - All addresses are base command bytes (R/W bit = 0) */
+#define MC33978_REG_CHECK 0x00 /* SPI communication check */
+#define MC33978_REG_CONFIG 0x02 /* Device configuration */
+#define MC33978_REG_TRI_SP 0x04 /* Tri-state enable SP */
+#define MC33978_REG_TRI_SG 0x06 /* Tri-state enable SG */
+#define MC33978_REG_WET_SP 0x08 /* Wetting current level SP */
+#define MC33978_REG_WET_SG0 0x0a /* Wetting current level SG0 (SG7-SG0) */
+#define MC33978_REG_WET_SG1 0x0c /* Wetting current level SG1 (SG13-SG8) */
+#define MC33978_REG_CWET_SP 0x16 /* Continuous wetting current SP */
+#define MC33978_REG_CWET_SG 0x18 /* Continuous wetting current SG */
+#define MC33978_REG_IE_SP 0x1a /* Interrupt enable SP */
+#define MC33978_REG_IE_SG 0x1c /* Interrupt enable SG */
+#define MC33978_REG_LPM_CONFIG 0x1e /* Low-power mode configuration */
+#define MC33978_REG_WAKE_SP 0x20 /* Wake-up enable SP */
+#define MC33978_REG_WAKE_SG 0x22 /* Wake-up enable SG */
+#define MC33978_REG_COMP_SP 0x24 /* Comparator only mode SP */
+#define MC33978_REG_COMP_SG 0x26 /* Comparator only mode SG */
+#define MC33978_REG_LPM_VT_SP 0x28 /* LPM voltage threshold SP */
+#define MC33978_REG_LPM_VT_SG 0x2a /* LPM voltage threshold SG */
+#define MC33978_REG_IP_SP 0x2c /* Polling current SP */
+#define MC33978_REG_IP_SG 0x2e /* Polling current SG */
+#define MC33978_REG_SPOLL_SP 0x30 /* Slow polling SP */
+#define MC33978_REG_SPOLL_SG 0x32 /* Slow polling SG */
+#define MC33978_REG_WDEB_SP 0x34 /* Wake-up debounce SP */
+#define MC33978_REG_WDEB_SG 0x36 /* Wake-up debounce SG */
+#define MC33978_REG_ENTER_LPM 0x38 /* Enter low-power mode (write-only) */
+#define MC33978_REG_AMUX_CTRL 0x3a /* AMUX control */
+#define MC33978_REG_READ_IN 0x3e /* Read switch status (READ_SW in datasheet) */
+#define MC33978_REG_FAULT 0x42 /* Fault status register */
+#define MC33978_REG_IRQ 0x46 /* Interrupt request (write-only) */
+#define MC33978_REG_RESET 0x48 /* Reset (write-only) */
+
+/*
+ * FAULT Register (0x42) bit definitions
+ * Reading this register clears most fault flags except persistent conditions
+ */
+#define MC33978_FAULT_SPI_ERROR BIT(10) /* SPI communication error */
+#define MC33978_FAULT_HASH BIT(9) /* SPI register hash mismatch */
+#define MC33978_FAULT_UV BIT(7) /* VBATP undervoltage */
+#define MC33978_FAULT_OV BIT(6) /* VBATP overvoltage */
+#define MC33978_FAULT_TEMP_WARN BIT(5) /* Temperature warning threshold */
+#define MC33978_FAULT_OT BIT(4) /* Over-temperature */
+#define MC33978_FAULT_INTB_WAKE BIT(3) /* Woken by INT_B pin */
+#define MC33978_FAULT_WAKEB_WAKE BIT(2) /* Woken by WAKE_B pin */
+#define MC33978_FAULT_SPI_WAKE BIT(1) /* Woken by SPI message */
+#define MC33978_FAULT_POR BIT(0) /* Power-on reset occurred */
+
+/* Critical faults that need immediate attention */
+#define MC33978_FAULT_CRITICAL (MC33978_FAULT_UV | \
+ MC33978_FAULT_OV | \
+ MC33978_FAULT_OT)
+
+/* Bits relevant as hwmon alarms; excludes wake/reset/SPI status bits */
+#define MC33978_FAULT_ALARM_MASK (MC33978_FAULT_UV | \
+ MC33978_FAULT_OV | \
+ MC33978_FAULT_TEMP_WARN | \
+ MC33978_FAULT_OT)
+
+#define MC33978_NUM_PINS 22
+
+/*
+ * Virtual IRQ number for fault handling.
+ * Using hwirq 22 (beyond the 22 pin IRQs 0-21).
+ */
+#define MC33978_HWIRQ_FAULT 22
+
+/*
+ * AMUX channel definitions
+ * The AMUX can route one of 24 signals to the external AMUX pin
+ */
+#define MC33978_AMUX_CH_SG0 0 /* Switch-to-Ground inputs 0-13 */
+#define MC33978_AMUX_CH_SG13 13
+#define MC33978_AMUX_CH_SP0 14 /* Programmable switch inputs 0-7 */
+#define MC33978_AMUX_CH_SP7 21
+#define MC33978_AMUX_CH_TEMP 22 /* Internal temperature diode */
+#define MC33978_AMUX_CH_VBATP 23 /* Battery voltage sense */
+#define MC33978_NUM_AMUX_CH 24 /* Total number of AMUX channels */
+
+#endif /* _LINUX_MFD_MC33978_H */
--
2.47.3
^ permalink raw reply related
* [PATCH v9 4/6] pinctrl: add NXP MC33978/MC34978 pinctrl driver
From: Oleksij Rempel @ 2026-03-31 17:16 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Peter Rosin, Linus Walleij
Cc: David Jander, Oleksij Rempel, kernel, linux-kernel, devicetree,
linux-hwmon, linux-gpio
In-Reply-To: <20260331171612.102018-1-o.rempel@pengutronix.de>
From: David Jander <david@protonic.nl>
Add pin control and GPIO driver for the NXP MC33978/MC34978 Multiple
Switch Detection Interface (MSDI) devices.
This driver exposes the 22 mechanical switch detection inputs (14
Switch-to-Ground, 8 Programmable) as standard GPIOs.
Key features implemented:
- GPIO read/write: Translates physical switch states (open/closed)
to logical GPIO levels based on the configured switch topology
(Switch-to-Ground vs. Switch-to-Battery).
- Emulated Output: Allows setting pins "high" or "low" by manipulating
the tri-state registers and hardware pull topologies.
- Interrupt routing: Proxies GPIO interrupt requests to the irq_domain
managed by the parent MFD core driver via a hierarchical irq_chip.
Signed-off-by: David Jander <david@protonic.nl>
Co-developed-by: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
changes v9:
- Resolve probe fwnode directly from parent (`dev_fwnode(dev->parent)`) and
fail early with `-ENODEV` if the parent firmware node is missing.
- Set child device node from this validated parent fwnode.
- Replace mutex_init() with devm_mutex_init()
- Add gpiochip_disable_irq/enable_irq calls in mask/unmask callbacks
for proper gpiolib IRQ state tracking with IRQCHIP_IMMUTABLE
- Set DOMAIN_BUS_WIRED token for GPIO IRQ domain to distinguish from
parent MFD domain sharing same fwnode
- Add explanatory comment about fwnode sharing and bus token isolation
to prevent domain shadowing concerns
- select GPIOLIB_IRQCHIP and IRQ_DOMAIN_HIERARCHY
changes v8:
- Fix comment documentation to state the driver implements a hierarchical
irq_chip instead of proxying .to_irq().
- Add missing <linux/irqdomain.h> include.
- Add .irq_set_wake = irq_chip_set_wake_parent to the gpio_irq_chip to
properly proxy wake-up configuration to the parent domain.
- Replace irq_find_host() with irq_find_matching_fwnode() during probe
to support parent domain lookup on non-OF platforms.
changes v7:
- Refactor I/O state reading and tri-state updates for SG/SB topologies
- Fix open-drain and open-source pinconf emulation
- Make direction_input a no-op to prevent overriding pinctrl bias
- Add defensive wrappers for IRQ proxying to prevent NULL pointer panics
- Add missing mutex guards to pinconf and get operations
- Convert generic internal variables to u32 and add lockdep assertions
changes v6:
- no changes
changes v5:
- no changes
changes v4:
- add Reviewed-by: Linus Walleij ...
- Replace the of_device_id match table with a platform_device_id table
- Add device_set_node(dev, dev_fwnode(dev->parent)) during probe
- Remove the check for a missing dev->of_node
changes v3:
- replace manual mutex_lock()/mutex_unlock() paths with guard(mutex)
- Unify error checking style by replacing if (ret < 0) with if (ret)
- Migrate from a custom .to_irq callback to a hierarchical gpio_irq_chip
- Implement .irq_bus_lock and .irq_bus_sync_unlock proxies to properly
cascade SPI bus lock operations to the parent MFD domain
- Set girq->handler to handle_simple_irq
changes v2:
- Translate all remaining German comments to English.
- Remove unnecessary #ifdef CONFIG_OF wrappers around dt_node_to_map.
- Add detailed comments to mc33978_get() and mc33978_get_multiple() explaining
the hardware comparator logic (1 = closed, 0 = open) and justifying the
bitwise inversion required to report actual physical voltage levels.
- Add comments to the .set() and .set_config() callbacks explaining why
gpiolib's standard open-drain emulation (switching to input mode) fails on
this hardware due to active wetting currents, and why tri-state isolation is
mandatory.
- Add a comment to mc33978_gpio_to_irq() explaining why it must act as a
proxy to the parent MFD's irq_domain (shared physical INT_B line with hwmon).
- Drop dummy pin group callbacks (get_groups_count, etc.). This relies on a
preparatory patch in this series making these callbacks optional in the core.
- Fix debugfs 'pinconf-pins' read errors by correctly returning -ENOTSUPP
instead of -EOPNOTSUPP for unsupported generic configurations.
- Fix empty 'gpio-ranges' and missing debugfs labels by explicitly calling
gpiochip_add_pin_range() during probe.
- Eliminate "magic" bitwise math in the wetting current configuration by
introducing a static lookup array (mc33978_wet_mA).
- Resolve checkpatch.pl strict warnings regarding macro argument reuse by
converting MC33978_SPSG, MC33978_PINSHIFT, MC33978_WREG, and MC33978_WSHIFT
to static inline functions.
- Remove artifacts from previous interrupt handling implementations.
- Address minor formatting and whitespace nits.
---
drivers/pinctrl/Kconfig | 16 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-mc33978.c | 865 ++++++++++++++++++++++++++++++
3 files changed, 882 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-mc33978.c
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index afecd9407f53..64f9c5b1aacb 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -388,6 +388,22 @@ config PINCTRL_MAX77620
function in alternate mode. This driver also configure push-pull,
open drain, FPS slots etc.
+config PINCTRL_MC33978
+ tristate "MC33978/MC34978 industrial input controller support"
+ depends on MFD_MC33978
+ select GPIOLIB
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ select GENERIC_PINCONF
+ help
+ Say Y here to enable support for NXP MC33978/MC34978 Multiple
+ Switch Detection Interface (MSDI) devices. This driver provides
+ pinctrl and GPIO interfaces for the 22 mechanical switch inputs
+ (14 Switch-to-Ground, 8 Programmable).
+
+ It allows reading switch states, configuring hardware pull
+ topologies, and handling interrupts for state changes.
+
config PINCTRL_MCP23S08_I2C
tristate
select REGMAP_I2C
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index f7d5d5f76d0c..afb58fb5a197 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_PINCTRL_XWAY) += pinctrl-xway.o
obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o
obj-$(CONFIG_PINCTRL_MAX7360) += pinctrl-max7360.o
obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o
+obj-$(CONFIG_PINCTRL_MC33978) += pinctrl-mc33978.o
obj-$(CONFIG_PINCTRL_MCP23S08_I2C) += pinctrl-mcp23s08_i2c.o
obj-$(CONFIG_PINCTRL_MCP23S08_SPI) += pinctrl-mcp23s08_spi.o
obj-$(CONFIG_PINCTRL_MCP23S08) += pinctrl-mcp23s08.o
diff --git a/drivers/pinctrl/pinctrl-mc33978.c b/drivers/pinctrl/pinctrl-mc33978.c
new file mode 100644
index 000000000000..415e43199aa3
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-mc33978.c
@@ -0,0 +1,865 @@
+// 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 Multiple Switch Detection Interface - Pinctrl/GPIO Driver
+ *
+ * Provides GPIO and pinctrl interfaces for the 22 switch detection inputs.
+ * Handles digital input reading and wetting current configuration. Analog AMUX
+ * functionality is handled by a separate mux driver.
+ *
+ * GPIO Mapping:
+ * - GPIO 0-13: SG0-SG13 (Switch-to-Ground inputs)
+ * - GPIO 14-21: SP0-SP7 (Programmable: Switch-to-Ground or Switch-to-Battery)
+ * This is dictated by the READ_IN register where bits [21:14] = SP[7:0]
+ * and bits [13:0] = SG[13:0].
+ *
+ * Register Organization:
+ * Configuration registers are generally paired. The _SP register at offset N
+ * controls SP0-SP7, and the _SG register at offset N+2 controls SG0-SG13.
+ *
+ * Wetting Currents vs. Pull Resistors:
+ * The hardware physically lacks traditional passive pull-up or pull-down
+ * resistors. Instead, it uses active, controllable current regulators
+ * (wetting currents) to detect switch states and clean mechanical contacts.
+ * - Because these are active current sources, specifying an ohmic value for
+ * pull-up/down biases is physically invalid. The driver ignores ohm arguments.
+ * - 8 selectable current values: 2, 6, 8, 10, 12, 14, 16, 20 mA.
+ * - Exposed via the pinconf PIN_CONFIG_DRIVE_STRENGTH parameter (in mA).
+ *
+ * Emulated Outputs:
+ * The hardware lacks traditional push-pull output drivers; it is strictly an
+ * input device. "Outputs" are simulated by toggling the wetting currents and
+ * physically isolating the pins via hardware tri-state registers. Consequently,
+ * consumers MUST flag outputs with GPIO_OPEN_DRAIN or GPIO_OPEN_SOURCE in
+ * the Device Tree.
+ *
+ * Input Detection Mechanics:
+ * This input mechanism relies on the active current regulators rather than
+ * passive hard resistors. For a Switch-to-Ground (SG) pin, the chip sources
+ * a constant current. When the switch is open, the pin voltage floats up to
+ * the battery voltage. When the switch closes, it creates a path to ground;
+ * because the current is strictly regulated, the pin voltage drops sharply
+ * below the internal 4.0V comparator threshold.
+ * The hardware evaluates this and reports an abstract "contact status"
+ * (1 = closed, 0 = open). For SG pins, a closed switch (~0V) reports as '1'.
+ * To align with gpiolib expectations where ~0V equals a physical logical '0',
+ * this driver explicitly inverts the hardware status for all SG-configured
+ * pins before reporting them.
+ *
+ * Interrupts:
+ * The physical INT_B line and threaded IRQ domain are managed centrally by
+ * the parent MFD core. This driver implements a hierarchical irq_chip
+ * to proxy masking/unmasking and configuration to the parent domain.
+ *
+ * Written by David Jander <david@protonic.nl>
+ *
+ * Datasheet:
+ * https://www.nxp.com/docs/en/data-sheet/MC33978.pdf
+ */
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/mc33978.h>
+
+#define MC33978_NGPIO 22
+
+/*
+ * Input numbering is dictated by bit-order of the input register:
+ * Inputs 0-13 -> SG0-SG13
+ * Inputs 14-21 -> SP0-SP7
+ */
+#define MC33978_NUM_SG 14
+#define MC33978_SP_MASK GENMASK(MC33978_NGPIO - 1, MC33978_NUM_SG)
+#define MC33978_SG_MASK GENMASK(MC33978_NUM_SG - 1, 0)
+#define MC33978_SG_SHIFT 0
+#define MC33978_SP_SHIFT MC33978_NUM_SG
+
+#define MC33978_TRISTATE 0
+#define MC33978_PU 1
+#define MC33978_PD 2
+
+struct mc33978_pinctrl {
+ struct device *dev;
+ struct regmap *regmap;
+
+ struct irq_domain *domain;
+
+ struct gpio_chip chip;
+ struct pinctrl_dev *pctldev;
+ struct pinctrl_desc pinctrl_desc;
+
+ /*
+ * Protects multi-register hardware sequences in .set() and atomic
+ * READ_IN + CONFIG reads in .get()
+ */
+ struct mutex lock;
+};
+
+static const struct pinctrl_pin_desc mc33978_pins[] = {
+ PINCTRL_PIN(0, "sg0"),
+ PINCTRL_PIN(1, "sg1"),
+ PINCTRL_PIN(2, "sg2"),
+ PINCTRL_PIN(3, "sg3"),
+ PINCTRL_PIN(4, "sg4"),
+ PINCTRL_PIN(5, "sg5"),
+ PINCTRL_PIN(6, "sg6"),
+ PINCTRL_PIN(7, "sg7"),
+ PINCTRL_PIN(8, "sg8"),
+ PINCTRL_PIN(9, "sg9"),
+ PINCTRL_PIN(10, "sg10"),
+ PINCTRL_PIN(11, "sg11"),
+ PINCTRL_PIN(12, "sg12"),
+ PINCTRL_PIN(13, "sg13"),
+ PINCTRL_PIN(14, "sp0"),
+ PINCTRL_PIN(15, "sp1"),
+ PINCTRL_PIN(16, "sp2"),
+ PINCTRL_PIN(17, "sp3"),
+ PINCTRL_PIN(18, "sp4"),
+ PINCTRL_PIN(19, "sp5"),
+ PINCTRL_PIN(20, "sp6"),
+ PINCTRL_PIN(21, "sp7"),
+};
+
+static inline bool mc33978_is_sp(unsigned int pin)
+{
+ return pin >= MC33978_NUM_SG;
+}
+
+/* Choose register offset for _SG/_SP registers. reg is always the _SP addr. */
+static inline u8 mc33978_spsg(u8 reg, unsigned int pin)
+{
+ return mc33978_is_sp(pin) ? reg : reg + 2;
+}
+
+/* Get the bit index into the corresponding register */
+static inline unsigned int mc33978_pinshift(unsigned int pin)
+{
+ return mc33978_is_sp(pin) ? pin - MC33978_NUM_SG : pin;
+}
+
+#define MC33978_PINMASK(pin) BIT(mc33978_pinshift(pin))
+
+/*
+ * Wetting current registers: 3 in total, each pin uses a 3-bit field,
+ * 8 pins per register, except for the last one.
+ */
+static inline u8 mc33978_wreg(u8 reg, unsigned int pin)
+{
+ return reg + (mc33978_is_sp(pin) ? 0 : 2 + 2 * (pin / 8));
+}
+
+static inline unsigned int mc33978_wshift(unsigned int pin)
+{
+ return mc33978_is_sp(pin) ? 3 * (pin - MC33978_NUM_SG) : 3 * (pin % 8);
+}
+
+#define MC33978_WMASK(pin) (7 << mc33978_wshift(pin))
+
+static int mc33978_read(struct mc33978_pinctrl *mpc, u8 reg, u32 *val)
+{
+ int ret;
+
+ ret = regmap_read(mpc->regmap, reg, val);
+ if (ret)
+ dev_err_ratelimited(mpc->dev, "Regmap read error %d at reg: %02x.\n",
+ ret, reg);
+ return ret;
+}
+
+static int mc33978_update_bits(struct mc33978_pinctrl *mpc, u8 reg, u32 mask,
+ u32 val)
+{
+ int ret;
+
+ ret = regmap_update_bits(mpc->regmap, reg, mask, val);
+ if (ret)
+ dev_err_ratelimited(mpc->dev, "Regmap update bits error %d at reg: %02x.\n",
+ ret, reg);
+ return ret;
+}
+
+static const struct pinctrl_ops mc33978_pinctrl_ops = {
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+ .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int mc33978_get_pull(struct mc33978_pinctrl *mpc, unsigned int pin, u32 *val)
+{
+ u32 data;
+ int ret;
+
+ lockdep_assert_held(&mpc->lock);
+
+ ret = mc33978_read(mpc, mc33978_spsg(MC33978_REG_TRI_SP, pin), &data);
+ if (ret)
+ return ret;
+
+ /* Is the pin tri-stated? */
+ if (data & MC33978_PINMASK(pin)) {
+ *val = MC33978_TRISTATE;
+ return 0;
+ }
+
+ /* Pins 0..13 only support pull-up */
+ if (!mc33978_is_sp(pin)) {
+ *val = MC33978_PU;
+ return 0;
+ }
+
+ /* Check pin pull direction for pins 14..21 */
+ ret = mc33978_read(mpc, MC33978_REG_CONFIG, &data);
+ if (ret)
+ return ret;
+
+ if (data & MC33978_PINMASK(pin))
+ *val = MC33978_PD;
+ else
+ *val = MC33978_PU;
+
+ return 0;
+}
+
+static int mc33978_set_pull(struct mc33978_pinctrl *mpc, unsigned int pin, int val)
+{
+ u32 mask = MC33978_PINMASK(pin);
+ int ret;
+
+ lockdep_assert_held(&mpc->lock);
+
+ /* SG pins physically lack pull-down current sources */
+ if (val == MC33978_PD && !mc33978_is_sp(pin))
+ return -EINVAL;
+
+ /* Configure direction (Exclusively for SP pins) */
+ if (mc33978_is_sp(pin) && val != MC33978_TRISTATE) {
+ ret = mc33978_update_bits(mpc, MC33978_REG_CONFIG, mask,
+ (val == MC33978_PD) ? mask : 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Enable current source or set to tri-state */
+ return mc33978_update_bits(mpc, mc33978_spsg(MC33978_REG_TRI_SP, pin),
+ mask,
+ (val == MC33978_TRISTATE) ? mask : 0);
+}
+
+static const unsigned int mc33978_wet_mA[] = { 2, 6, 8, 10, 12, 14, 16, 20 };
+
+static int mc33978_set_ds(struct mc33978_pinctrl *mpc, unsigned int pin,
+ u32 val)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mc33978_wet_mA); i++) {
+ if (val == mc33978_wet_mA[i]) {
+ return mc33978_update_bits(mpc,
+ mc33978_wreg(MC33978_REG_WET_SP, pin),
+ MC33978_WMASK(pin),
+ i << mc33978_wshift(pin));
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int mc33978_get_ds(struct mc33978_pinctrl *mpc, unsigned int pin,
+ u32 *val)
+{
+ u32 data;
+ int ret;
+
+ ret = mc33978_read(mpc, mc33978_wreg(MC33978_REG_WET_SP, pin), &data);
+ if (ret)
+ return ret;
+
+ data &= MC33978_WMASK(pin);
+ data >>= mc33978_wshift(pin);
+
+ if (data >= ARRAY_SIZE(mc33978_wet_mA))
+ return -EINVAL;
+
+ *val = mc33978_wet_mA[data];
+
+ return 0;
+}
+
+static int mc33978_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *config)
+{
+ struct mc33978_pinctrl *mpc = pinctrl_dev_get_drvdata(pctldev);
+ enum pin_config_param param = pinconf_to_config_param(*config);
+ u32 arg;
+ u32 data;
+ int ret;
+
+ guard(mutex)(&mpc->lock);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_PULL_UP:
+ ret = mc33978_get_pull(mpc, pin, &data);
+ if (ret)
+ return ret;
+ if (data != MC33978_PU)
+ return -EINVAL;
+ arg = 1;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ ret = mc33978_get_pull(mpc, pin, &data);
+ if (ret)
+ return ret;
+ if (data != MC33978_PD)
+ return -EINVAL;
+ arg = 1;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (!mc33978_is_sp(pin))
+ return -EINVAL;
+
+ ret = mc33978_read(mpc, MC33978_REG_CONFIG, &data);
+ if (ret)
+ return ret;
+
+ if (!(data & MC33978_PINMASK(pin)))
+ return -EINVAL;
+ arg = 1;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ if (mc33978_is_sp(pin)) {
+ ret = mc33978_read(mpc, MC33978_REG_CONFIG, &data);
+ if (ret)
+ return ret;
+
+ if (data & MC33978_PINMASK(pin))
+ return -EINVAL;
+ }
+ arg = 1;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ ret = mc33978_get_pull(mpc, pin, &data);
+ if (ret)
+ return ret;
+ if (data != MC33978_TRISTATE)
+ return -EINVAL;
+ arg = 1;
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ret = mc33978_get_ds(mpc, pin, &data);
+ if (ret)
+ return ret;
+ arg = data;
+ break;
+ default:
+ /*
+ * Ignore checkpatch warning: the pinctrl core specifically
+ * expects -ENOTSUPP to silently skip unsupported generic
+ * parameters. Using -EOPNOTSUPP causes debugfs read failures.
+ */
+ return -ENOTSUPP;
+ }
+
+ *config = pinconf_to_config_packed(param, arg);
+
+ return 0;
+}
+
+/*
+ * Hardware constraint regarding PIN_CONFIG_BIAS_PULL_UP/DOWN:
+ * The MC33978 utilizes active constant current sources (wetting currents)
+ * rather than passive pull-resistors. Since the equivalent ohmic resistance
+ * scales dynamically with the fluctuating board voltage (VBATP), computing
+ * a static ohm value is physically invalid.
+ * The driver intentionally ignores resistance arguments during configuration
+ * and continuously reports 0 ohms to the pinctrl framework.
+ */
+static int mc33978_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct mc33978_pinctrl *mpc = pinctrl_dev_get_drvdata(pctldev);
+ enum pin_config_param param;
+ int ret = 0;
+ u32 arg;
+ int i;
+
+ guard(mutex)(&mpc->lock);
+
+ for (i = 0; i < num_configs; i++) {
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ /*
+ * The hardware physically lacks push-pull output drivers.
+ * By explicitly handling OPEN_DRAIN and OPEN_SOURCE here, we
+ * signal to gpiolib that we support these modes "natively".
+ * This crucially prevents gpiolib from falling back to its
+ * software emulation (which sets the pin to input mode to
+ * achieve High-Z). On the MC33978, input mode is NOT High-Z;
+ * it actively drives the line with a wetting current!
+ */
+ switch (param) {
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ /* Setup topology only; do not turn on current yet */
+ if (mc33978_is_sp(pin))
+ ret = mc33978_update_bits(mpc, MC33978_REG_CONFIG,
+ MC33978_PINMASK(pin), 0);
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ ret = mc33978_set_pull(mpc, pin, MC33978_PU);
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (!mc33978_is_sp(pin)) {
+ dev_err(mpc->dev, "Pin %u is SG and does not support open-drain\n",
+ pin);
+ return -EINVAL;
+ }
+ /* Setup topology only; do not turn on current yet */
+ ret = mc33978_update_bits(mpc, MC33978_REG_CONFIG,
+ MC33978_PINMASK(pin),
+ MC33978_PINMASK(pin));
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (!mc33978_is_sp(pin)) {
+ dev_err(mpc->dev, "Pin %u is SG and does not support pull-down\n",
+ pin);
+ return -EINVAL;
+ }
+ ret = mc33978_set_pull(mpc, pin, MC33978_PD);
+ break;
+ /*
+ * The MC33978 uses active wetting currents rather than passive
+ * pull-resistors. Disabling the bias (pull-up/down) is
+ * physically equivalent to putting the pin into a
+ * high-impedance state. Both actions are achieved by isolating
+ * the pin via the hardware tri-state registers.
+ */
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ ret = mc33978_set_pull(mpc, pin, MC33978_TRISTATE);
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH_UA:
+ arg /= 1000;
+ fallthrough;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ret = mc33978_set_ds(mpc, pin, arg);
+ break;
+ default:
+ /*
+ * Required by the pinctrl core to safely fall back or
+ * skip unsupported configs. Do not use -EOPNOTSUPP.
+ */
+ return -ENOTSUPP;
+ }
+
+ if (ret) {
+ dev_err(mpc->dev, "Failed to set config param %04x for pin %u: %d\n",
+ param, pin, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct pinconf_ops mc33978_pinconf_ops = {
+ .pin_config_get = mc33978_pinconf_get,
+ .pin_config_set = mc33978_pinconf_set,
+ .is_generic = true,
+};
+
+static int mc33978_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ /* This chip is strictly an input device (comparators always active) */
+ return 0;
+}
+
+/*
+ * The hardware evaluates pin voltage against a threshold (default 4.0V)
+ * and reports an abstract contact status (1 = closed, 0 = open):
+ *
+ * SG (Switch-to-Ground) topology (pull-up current source):
+ * - Voltage > Threshold: Switch Open (HW reports 0) -> Physical High
+ * - Voltage < Threshold: Switch Closed (HW reports 1) -> Physical Low
+ *
+ * SB (Switch-to-Battery) topology (pull-down current source):
+ * - Voltage > Threshold: Switch Closed (HW reports 1) -> Physical High
+ * - Voltage < Threshold: Switch Open (HW reports 0) -> Physical Low
+ *
+ * We translate this contact status back into physical voltage levels by
+ * inverting the hardware status for all pins operating in SG topology.
+ */
+static int mc33978_read_in_state(struct mc33978_pinctrl *mpc,
+ unsigned long mask, unsigned long *state)
+{
+ u32 status, inv_mask;
+ u32 config_reg = 0;
+ int ret;
+
+ ret = mc33978_read(mpc, MC33978_REG_READ_IN, &status);
+ if (ret)
+ return ret;
+
+ /* Read CONFIG register only if the requested mask involves SP pins */
+ if (mask & MC33978_SP_MASK) {
+ ret = mc33978_read(mpc, MC33978_REG_CONFIG, &config_reg);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Create an inversion mask for all pins currently operating in
+ * Switch-to-Ground (SG) topology. SG pins always have pull-ups.
+ * For SP pins, CONFIG bit value 0 = Switch-to-Ground (PU),
+ * CONFIG bit value 1 = Switch-to-Battery (PD).
+ */
+ inv_mask = MC33978_SG_MASK |
+ (~(config_reg << MC33978_NUM_SG) & MC33978_SP_MASK);
+
+ *state = (status ^ inv_mask) & mask;
+
+ return 0;
+}
+
+static int mc33978_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct mc33978_pinctrl *mpc = gpiochip_get_data(chip);
+ unsigned long state;
+ int ret;
+
+ guard(mutex)(&mpc->lock);
+
+ ret = mc33978_read_in_state(mpc, BIT(offset), &state);
+ if (ret)
+ return ret;
+
+ return !!(state & BIT(offset));
+}
+
+static int mc33978_get_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mc33978_pinctrl *mpc = gpiochip_get_data(chip);
+ unsigned long state;
+ int ret;
+
+ guard(mutex)(&mpc->lock);
+
+ ret = mc33978_read_in_state(mpc, *mask, &state);
+ if (ret)
+ return ret;
+
+ *bits = (*bits & ~*mask) | state;
+
+ return 0;
+}
+
+/*
+ * Emulate output states by routing or isolating active wetting currents.
+ * To turn the line ON, we disable the hardware tri-state (write 0).
+ * To turn the line OFF (High-Z), we enable tri-state (write 1).
+ *
+ * For Open-Source (Pull-Up): value=1 turns it ON, value=0 is High-Z.
+ * For Open-Drain (Pull-Down): value=0 turns it ON, value=1 is High-Z.
+ * We dynamically read the CONFIG register to determine the topology
+ * and invert the bits accordingly for Open-Drain pins.
+ *
+ * Note: The hardware physically lacks push-pull drivers. Toggling outputs
+ * via tri-state isolation may cause transient spikes.
+ */
+static int mc33978_update_tri_state(struct mc33978_pinctrl *mpc, u32 mask,
+ u32 bits)
+{
+ u32 sgmask = (mask & MC33978_SG_MASK) >> MC33978_SG_SHIFT;
+ u32 sgbits = (bits & MC33978_SG_MASK) >> MC33978_SG_SHIFT;
+ u32 spmask = (mask & MC33978_SP_MASK) >> MC33978_SP_SHIFT;
+ u32 spbits = (bits & MC33978_SP_MASK) >> MC33978_SP_SHIFT;
+ u32 config_reg = 0;
+ int ret = 0;
+
+ if (spmask) {
+ /* Read topology: 1 = PD (Open-Drain), 0 = PU (Open-Source) */
+ ret = mc33978_read(mpc, MC33978_REG_CONFIG, &config_reg);
+ if (ret)
+ return ret;
+
+ /*
+ * Invert bits for Open-Drain (PD) pins.
+ * The Open-Drain API contract expects value=1 to be High-Z.
+ */
+ spbits ^= (config_reg & spmask);
+
+ ret = mc33978_update_bits(mpc, MC33978_REG_TRI_SP, spmask,
+ ~spbits);
+ if (ret)
+ return ret;
+ }
+
+ /* SG pins are always Pull-Up (Open-Source), no inversion needed */
+ if (sgmask)
+ ret = mc33978_update_bits(mpc, MC33978_REG_TRI_SG, sgmask,
+ ~sgbits);
+
+ return ret;
+}
+
+static int mc33978_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct mc33978_pinctrl *mpc = gpiochip_get_data(chip);
+ u32 mask = BIT(offset);
+ u32 bits = value ? mask : 0;
+
+ guard(mutex)(&mpc->lock);
+
+ return mc33978_update_tri_state(mpc, mask, bits);
+}
+
+static int mc33978_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mc33978_pinctrl *mpc = gpiochip_get_data(chip);
+
+ guard(mutex)(&mpc->lock);
+
+ return mc33978_update_tri_state(mpc, *mask, *bits);
+}
+
+static int mc33978_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ return mc33978_set(chip, offset, value);
+}
+
+static int mc33978_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ *parent_type = child_type;
+ *parent = child;
+
+ return 0;
+}
+
+/*
+ * Defensive wrappers for hierarchical IRQ proxying.
+ *
+ * gpiolib's hierarchical allocation exposes a lifecycle gap: the child
+ * descriptor is registered before irq_domain_alloc_irqs_parent() fully
+ * instantiates the parent chip.
+ *
+ * During consumer probe (e.g., gpiod_to_irq()), irq_create_fwspec_mapping()
+ * allocates the hierarchy. As part of this, irq_domain_set_info() initializes
+ * the top-level irq_desc and calls __irq_set_handler(). If the irq_desc
+ * requires locking, __irq_get_desc_lock() will invoke the child's
+ * .irq_bus_lock before the parent allocation is complete.
+ *
+ * Upstream generic helpers (e.g., irq_chip_mask_parent) blindly dereference
+ * data->parent_data->chip, causing an immediate NULL pointer panic during
+ * this gap. These wrappers check for a valid parent chip to safely drop
+ * premature locking or masking events while the legacy subsystem hierarchy
+ * is still assembling itself.
+ */
+static void mc33978_gpio_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct irq_data *parent = data->parent_data;
+
+ if (parent && parent->chip && parent->chip->irq_mask)
+ parent->chip->irq_mask(parent);
+ gpiochip_disable_irq(gc, data->hwirq);
+}
+
+static void mc33978_gpio_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct irq_data *parent = data->parent_data;
+
+ gpiochip_enable_irq(gc, data->hwirq);
+ if (parent && parent->chip && parent->chip->irq_unmask)
+ parent->chip->irq_unmask(parent);
+}
+
+static int mc33978_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct irq_data *parent = data->parent_data;
+
+ if (parent && parent->chip && parent->chip->irq_set_type)
+ return parent->chip->irq_set_type(parent, type);
+
+ return -EINVAL;
+}
+
+static void mc33978_gpio_irq_bus_lock(struct irq_data *data)
+{
+ struct irq_data *parent = data->parent_data;
+
+ if (parent && parent->chip && parent->chip->irq_bus_lock)
+ parent->chip->irq_bus_lock(parent);
+}
+
+static void mc33978_gpio_irq_bus_sync_unlock(struct irq_data *data)
+{
+ struct irq_data *parent = data->parent_data;
+
+ if (parent && parent->chip && parent->chip->irq_bus_sync_unlock)
+ parent->chip->irq_bus_sync_unlock(parent);
+}
+
+static const struct irq_chip mc33978_gpio_irqchip = {
+ .name = "mc33978-gpio",
+ .irq_mask = mc33978_gpio_irq_mask,
+ .irq_unmask = mc33978_gpio_irq_unmask,
+ .irq_set_type = mc33978_gpio_irq_set_type,
+ .irq_bus_lock = mc33978_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = mc33978_gpio_irq_bus_sync_unlock,
+ .irq_set_wake = irq_chip_set_wake_parent,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void mc33978_init_gpio_chip(struct mc33978_pinctrl *mpc,
+ struct device *dev)
+{
+ struct gpio_irq_chip *girq;
+
+ mpc->chip.label = dev_name(dev);
+ mpc->chip.direction_input = mc33978_direction_input;
+ mpc->chip.get = mc33978_get;
+ mpc->chip.get_multiple = mc33978_get_multiple;
+ mpc->chip.direction_output = mc33978_direction_output;
+ mpc->chip.set = mc33978_set;
+ mpc->chip.set_multiple = mc33978_set_multiple;
+ mpc->chip.set_config = gpiochip_generic_config;
+
+ mpc->chip.base = -1;
+ mpc->chip.ngpio = MC33978_NGPIO;
+ mpc->chip.can_sleep = true;
+ mpc->chip.parent = dev;
+ mpc->chip.owner = THIS_MODULE;
+
+ girq = &mpc->chip.irq;
+ gpio_irq_chip_set_chip(girq, &mc33978_gpio_irqchip);
+ /*
+ * Share parent's DT fwnode. This does NOT cause IRQ domain shadowing
+ * because the parent MFD domain uses DOMAIN_BUS_NEXUS while this GPIO
+ * domain will use DOMAIN_BUS_WIRED (set after gpiochip registration).
+ * Domain lookups match on both fwnode AND bus_token, ensuring proper
+ * domain isolation. See crystalcove GPIO driver for similar pattern.
+ */
+ girq->fwnode = dev_fwnode(dev);
+ girq->parent_domain = mpc->domain;
+ girq->child_to_parent_hwirq = mc33978_gpio_child_to_parent_hwirq;
+ girq->handler = handle_simple_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+}
+
+static void mc33978_init_pinctrl_desc(struct mc33978_pinctrl *mpc,
+ struct device *dev)
+{
+ mpc->pinctrl_desc.name = dev_name(dev);
+
+ mpc->pinctrl_desc.pctlops = &mc33978_pinctrl_ops;
+ mpc->pinctrl_desc.confops = &mc33978_pinconf_ops;
+ mpc->pinctrl_desc.pins = mc33978_pins;
+ mpc->pinctrl_desc.npins = MC33978_NGPIO;
+ mpc->pinctrl_desc.owner = THIS_MODULE;
+}
+
+static int mc33978_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *fwnode;
+ struct mc33978_pinctrl *mpc;
+ int ret;
+
+ fwnode = dev_fwnode(dev->parent);
+ if (!fwnode)
+ return dev_err_probe(dev, -ENODEV,
+ "Missing parent firmware node\n");
+
+ device_set_node(dev, fwnode);
+
+ mpc = devm_kzalloc(dev, sizeof(*mpc), GFP_KERNEL);
+ if (!mpc)
+ return -ENOMEM;
+
+ mpc->dev = dev;
+
+ mpc->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!mpc->regmap)
+ return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n");
+
+ /* Find parent MFD IRQ domain (uses DOMAIN_BUS_NEXUS token) */
+ mpc->domain = irq_find_matching_fwnode(fwnode, DOMAIN_BUS_NEXUS);
+ if (!mpc->domain)
+ return dev_err_probe(dev, -ENODEV, "Failed to find parent IRQ domain\n");
+
+ ret = devm_mutex_init(dev, &mpc->lock);
+ if (ret)
+ return ret;
+
+ mc33978_init_gpio_chip(mpc, dev);
+ mc33978_init_pinctrl_desc(mpc, dev);
+
+ mpc->pctldev = devm_pinctrl_register(dev, &mpc->pinctrl_desc, mpc);
+ if (IS_ERR(mpc->pctldev))
+ return dev_err_probe(dev, PTR_ERR(mpc->pctldev),
+ "can't register pinctrl\n");
+
+ ret = devm_gpiochip_add_data(dev, &mpc->chip, mpc);
+ if (ret)
+ return dev_err_probe(dev, ret, "can't add GPIO chip\n");
+
+ /*
+ * Distinguish GPIO IRQ domain from parent MFD domain sharing the same
+ * fwnode. Matches the pattern used by other GPIO drivers (e.g.,
+ * crystalcove). DOMAIN_BUS_WIRED indicates this domain represents
+ * actual GPIO pin interrupts (wired lines).
+ */
+ irq_domain_update_bus_token(mpc->chip.irq.domain, DOMAIN_BUS_WIRED);
+
+ ret = gpiochip_add_pin_range(&mpc->chip, dev_name(dev), 0, 0,
+ MC33978_NGPIO);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add pin range\n");
+
+ platform_set_drvdata(pdev, mpc);
+
+ return 0;
+}
+
+static const struct platform_device_id mc33978_pinctrl_id[] = {
+ { "mc33978-pinctrl", },
+ { "mc34978-pinctrl", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, mc33978_pinctrl_id);
+
+static struct platform_driver mc33978_pinctrl_driver = {
+ .driver = {
+ .name = "mc33978-pinctrl",
+ },
+ .probe = mc33978_pinctrl_probe,
+ .id_table = mc33978_pinctrl_id,
+};
+module_platform_driver(mc33978_pinctrl_driver);
+
+MODULE_AUTHOR("David Jander <david@protonic.nl>");
+MODULE_DESCRIPTION("NXP MC33978/MC34978 pinctrl driver");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related
* [PATCH v9 1/6] dt-bindings: pinctrl: add NXP MC33978/MC34978 MSDI
From: Oleksij Rempel @ 2026-03-31 17:16 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Peter Rosin, Linus Walleij
Cc: Oleksij Rempel, kernel, linux-kernel, devicetree, linux-hwmon,
linux-gpio, David Jander
In-Reply-To: <20260331171612.102018-1-o.rempel@pengutronix.de>
Add device tree binding documentation for the NXP MC33978 and MC34978
Multiple Switch Detection Interface (MSDI) devices.
The MC33978 and MC34978 differ primarily in their operating temperature
ranges. While not software-detectable, providing specific compatible
strings allows the hwmon subsystem to correctly interpret thermal
thresholds and hardware faults.
These ICs monitor up to 22 mechanical switch contacts in automotive and
industrial environments. They provide configurable wetting currents to
break through contact oxidation and feature extensive hardware
protection against thermal overload and voltage transients (load
dumps/brown-outs).
The device interfaces via SPI. While it provides multiple functions, its
primary hardware purpose is pin/switch control. To accurately represent
the hardware as a single physical integrated circuit without unnecessary
DT overhead, all functions are flattened into a single pinctrl node:
- pinctrl: Exposing the 22 switch inputs (SG/SP pins) as a GPIO controller
and managing their pin configurations.
- hwmon: Exposing critical hardware faults (OT, OV, UV) and static
voltage/temperature thresholds.
- mux: Controlling the 24-to-1 analog multiplexer to route pin voltages,
internal temperature, or battery voltage to an external SoC ADC.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
changes v9:
- no changes
changes v8:
- Update IRQ_TYPE_* macros include path reference in documentation from
interrupt-controller.h to dt-bindings/interrupt-controller/irq.h.
- Add bias-disable, drive-open-drain, drive-open-source, and drive-strength
to the list of supported pin configuration properties.
changes v7:
- no changes
changes v6:
- add Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
- add Reviewed-by: Linus Walleij <linusw@kernel.org>
changes v5:
- Commit Message: Added justification for distinct compatible strings
based on temperature ranges.
- Restricted pins property to an explicit enum of valid hardware pins
changes v4:
- Drop the standalone mfd/nxp,mc33978.yaml schema entirely.
- Move the unified device binding to bindings/pinctrl/nxp,mc33978.yaml,
- Remove the dedicated child node compatible strings (nxp,mc33978-pinctrl).
- Flatten the pinctrl/gpio properties directly into the main SPI device
node.
changes v3:
- Drop regular expression pattern from pinctrl child node and define
it as a standard property
- Reorder required properties list in MFD binding
- Remove stray blank line from the MFD binding devicetree example
- Replace unevaluatedProperties with additionalProperties in the pinctrl
binding
changes v2:
- Squashed MFD, pinctrl, hwmon, and mux bindings into a single patch
- Removed the empty hwmon child node
- Folded the mux-controller node into the parent MFD node
- Added vbatp-supply and vddq-supply to the required properties block
- Changed the example node name from mc33978@0 to gpio@0
- Removed unnecessary literal block scalars (|) from descriptions
- Documented SG, SP, and SB pin acronyms in the pinctrl description
- Added consumer polarity guidance (GPIO_ACTIVE_LOW/HIGH) for SG/SB
inputs, with a note on output circuit dependency
- Updated commit message
---
.../bindings/pinctrl/nxp,mc33978.yaml | 158 ++++++++++++++++++
1 file changed, 158 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/nxp,mc33978.yaml
diff --git a/Documentation/devicetree/bindings/pinctrl/nxp,mc33978.yaml b/Documentation/devicetree/bindings/pinctrl/nxp,mc33978.yaml
new file mode 100644
index 000000000000..2a3c565c3c03
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/nxp,mc33978.yaml
@@ -0,0 +1,158 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/nxp,mc33978.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP MC33978/MC34978 Multiple Switch Detection Interface
+
+maintainers:
+ - David Jander <david@protonic.nl>
+ - Oleksij Rempel <o.rempel@pengutronix.de>
+
+description: |
+ The MC33978 and MC34978 are Multiple Switch Detection Interface (MSDI)
+ devices with 22 switch inputs, integrated fault detection, and analog
+ multiplexer (AMUX) for voltage/temperature monitoring.
+
+ Pin numbering:
+ - Pins 0-13: SG0-SG13 (Switch-to-Ground inputs). These pins monitor
+ contacts closed to ground and typically require GPIO_ACTIVE_LOW
+ flags when used as digital inputs.
+ - Pins 14-21: SP0-SP7 (Programmable inputs). These can be configured
+ as SG (Switch-to-Ground) or SB (Switch-to-Battery) inputs. SB
+ inputs monitor contacts closed to the battery voltage and typically
+ require GPIO_ACTIVE_HIGH flags when used as digital inputs.
+
+ Output Emulation:
+ The hardware lacks standard push-pull output drivers. Outputs are emulated
+ by toggling the programmable wetting current sources (acting as pull-ups
+ or pull-downs) and the hardware tri-state registers. Because of this
+ physical constraint:
+ - Consumers using pins as outputs MUST flag them with GPIO_OPEN_DRAIN or
+ GPIO_OPEN_SOURCE in the device tree.
+ - Push-pull configurations are physically unsupported.
+ - The active polarity depends entirely on the external circuit (e.g., how
+ an LED is wired) and must be flagged accordingly by the consumer.
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - nxp,mc33978
+ - nxp,mc34978
+
+ reg:
+ maxItems: 1
+ description: SPI chip select number
+
+ spi-max-frequency:
+ maximum: 8000000
+ description: Maximum SPI clock frequency (up to 8 MHz)
+
+ interrupts:
+ maxItems: 1
+ description:
+ INT_B pin interrupt. Active-low, indicates pin state changes or
+ fault conditions.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+ description:
+ First cell is the IRQ number (0-21 for pins, 22 for faults).
+ Second cell is the trigger type (IRQ_TYPE_* from dt-bindings/interrupt-controller/irq.h).
+
+ '#mux-control-cells':
+ const: 0
+ description:
+ Present if the device AMUX selector is used as a mux provider.
+ Consumers (e.g. io-channel-mux) must provide settle-time-us for the
+ external ADC sampling path.
+
+ vddq-supply:
+ description: Digital supply voltage
+
+ vbatp-supply:
+ description: Battery/power supply
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ ngpios:
+ const: 22
+
+patternProperties:
+ '^.*-grp$':
+ type: object
+ $ref: /schemas/pinctrl/pincfg-node.yaml#
+ additionalProperties: false
+ description: Pin configuration subnodes.
+ properties:
+ pins:
+ items:
+ enum: [sg0, sg1, sg2, sg3, sg4, sg5, sg6, sg7, sg8, sg9,
+ sg10, sg11, sg12, sg13, sp0, sp1, sp2, sp3,
+ sp4, sp5, sp6, sp7]
+
+ bias-pull-up: true
+ bias-pull-down: true
+ bias-high-impedance: true
+ bias-disable: true
+ drive-open-drain: true
+ drive-open-source: true
+ drive-strength:
+ enum: [2, 6, 8, 10, 12, 14, 16, 20]
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+ - vddq-supply
+ - vbatp-supply
+ - gpio-controller
+ - '#gpio-cells'
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ msdi: gpio@0 {
+ compatible = "nxp,mc33978";
+ reg = <0>;
+ spi-max-frequency = <4000000>;
+
+ interrupt-parent = <&gpiog>;
+ interrupts = <9 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+ vddq-supply = <®_3v3>;
+ vbatp-supply = <®_12v>;
+
+ #mux-control-cells = <0>;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <22>;
+
+ door-grp {
+ pins = "sg0";
+ bias-high-impedance;
+ };
+ };
+ };
--
2.47.3
^ permalink raw reply related
* [PATCH v9 6/6] mux: add NXP MC33978/MC34978 AMUX driver
From: Oleksij Rempel @ 2026-03-31 17:16 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Peter Rosin, Linus Walleij
Cc: Oleksij Rempel, kernel, linux-kernel, devicetree, linux-hwmon,
linux-gpio, David Jander
In-Reply-To: <20260331171612.102018-1-o.rempel@pengutronix.de>
Add a mux-control driver for the 24-to-1 analog multiplexer (AMUX)
embedded in the NXP MC33978/MC34978 Multiple Switch Detection
Interface (MSDI) devices.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v9:
- rename mc33978-mux to mux-mc33978 in the Kconfig help
- fail if fwnode is NULL
changes v8:
- no chnages
changes v7:
- Simplify the return path and local variable assignment in
mc33978_mux_set().
- Change idle_state to a signed integer to properly handle negative MUX
subsystem constants.
- Default to MUX_IDLE_AS_IS when the "idle-state" device tree property
is missing.
- Explicitly reject MUX_IDLE_DISCONNECT since the hardware does not
support disconnecting the multiplexer.
changes v6:
- parse optional idle-state property
- validate idle-state against available AMUX channels
- lower-case probe error messages
changes v5:
- no changes
changes v4:
- no changes
changes v3:
- no changes
changes v2:
- Add missing <linux/err.h> include.
- Add platform_device_id table
---
drivers/mux/Kconfig | 14 ++++
drivers/mux/Makefile | 2 +
drivers/mux/mc33978-mux.c | 141 ++++++++++++++++++++++++++++++++++++++
3 files changed, 157 insertions(+)
create mode 100644 drivers/mux/mc33978-mux.c
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index c68132e38138..ffe92a714096 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -45,6 +45,20 @@ config MUX_GPIO
To compile the driver as a module, choose M here: the module will
be called mux-gpio.
+config MUX_MC33978
+ tristate "NXP MC33978/MC34978 Analog Multiplexer"
+ depends on MFD_MC33978
+ help
+ MC33978/MC34978 24-to-1 analog multiplexer (AMUX) driver.
+
+ This driver provides mux-control for the analog multiplexer,
+ which can route switch voltages, temperature, and battery voltage
+ to an external ADC. Typically used with IIO ADC drivers to measure
+ analog values from the 22 switch inputs plus temperature and VBATP.
+
+ To compile the driver as a module, choose M here: the module will
+ be called mux-mc33978.
+
config MUX_MMIO
tristate "MMIO/Regmap register bitfield-controlled Multiplexer"
depends on OF
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
index 6e9fa47daf56..339c44b4d4f4 100644
--- a/drivers/mux/Makefile
+++ b/drivers/mux/Makefile
@@ -7,10 +7,12 @@ mux-core-objs := core.o
mux-adg792a-objs := adg792a.o
mux-adgs1408-objs := adgs1408.o
mux-gpio-objs := gpio.o
+mux-mc33978-objs := mc33978-mux.o
mux-mmio-objs := mmio.o
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
+obj-$(CONFIG_MUX_MC33978) += mux-mc33978.o
obj-$(CONFIG_MUX_MMIO) += mux-mmio.o
diff --git a/drivers/mux/mc33978-mux.c b/drivers/mux/mc33978-mux.c
new file mode 100644
index 000000000000..b44c862f0dbe
--- /dev/null
+++ b/drivers/mux/mc33978-mux.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+/*
+ * MC33978/MC34978 Analog Multiplexer (AMUX) Driver
+ *
+ * This driver provides mux-control for the 24-to-1 analog multiplexer.
+ * The AMUX routes one of the following signals to the external AMUX pin:
+ * - Channels 0-13: SG0-SG13 switch voltages
+ * - Channels 14-21: SP0-SP7 switch voltages
+ * - Channel 22: Internal temperature diode
+ * - Channel 23: Battery voltage (VBATP)
+ *
+ * Consumer drivers (typically IIO ADC drivers) use the mux-control
+ * subsystem to select which signal to measure.
+ *
+ * Architecture:
+ * The MC33978 does not have an internal ADC. Instead, it routes analog
+ * signals to an external AMUX pin that must be connected to an external
+ * ADC (such as the SoC's internal ADC). The IIO subsystem is responsible
+ * for coordinating the mux selection and ADC sampling.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mux/driver.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/mc33978.h>
+
+/* AMUX_CTRL register field definitions */
+#define MC33978_AMUX_CTRL_MASK GENMASK(5, 0) /* 6-bit channel select */
+
+struct mc33978_mux_priv {
+ struct device *dev;
+ struct regmap *map;
+};
+
+static int mc33978_mux_set(struct mux_control *mux, int state)
+{
+ struct mux_chip *mux_chip = mux->chip;
+ struct mc33978_mux_priv *priv = mux_chip_priv(mux_chip);
+ int ret;
+
+ if (state < 0 || state >= MC33978_NUM_AMUX_CH)
+ return -EINVAL;
+
+ ret = regmap_update_bits(priv->map, MC33978_REG_AMUX_CTRL,
+ MC33978_AMUX_CTRL_MASK, state);
+ if (ret)
+ dev_err(priv->dev, "failed to set AMUX channel %d: %d\n",
+ state, ret);
+
+ return ret;
+}
+
+static const struct mux_control_ops mc33978_mux_ops = {
+ .set = mc33978_mux_set,
+};
+
+static int mc33978_mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mc33978_mux_priv *priv;
+ struct fwnode_handle *fwnode;
+ struct mux_chip *mux_chip;
+ struct mux_control *mux;
+ s32 idle_state;
+ int ret;
+
+ mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*priv));
+ if (IS_ERR(mux_chip))
+ return dev_err_probe(dev, PTR_ERR(mux_chip), "failed to allocate mux chip\n");
+
+ fwnode = dev_fwnode(dev->parent);
+ if (!fwnode)
+ return dev_err_probe(dev, -ENODEV, "missing parent firmware node\n");
+
+ /* Borrow the parent's firmware node so consumers can find this mux chip */
+ device_set_node(&mux_chip->dev, fwnode);
+
+ priv = mux_chip_priv(mux_chip);
+ priv->dev = dev;
+
+ priv->map = dev_get_regmap(dev->parent, NULL);
+ if (!priv->map)
+ return dev_err_probe(dev, -ENODEV, "failed to get parent regmap\n");
+
+ mux_chip->ops = &mc33978_mux_ops;
+
+ mux = &mux_chip->mux[0];
+ mux->states = MC33978_NUM_AMUX_CH;
+
+ ret = device_property_read_u32(&mux_chip->dev, "idle-state",
+ (u32 *)&idle_state);
+ if (ret < 0 && ret != -EINVAL) {
+ return dev_err_probe(dev, ret, "failed to parse idle-state\n");
+ } else if (ret == -EINVAL) {
+ mux->idle_state = MUX_IDLE_AS_IS;
+ } else {
+ if (idle_state == MUX_IDLE_DISCONNECT)
+ return dev_err_probe(dev, -EINVAL,
+ "idle-disconnect not supported by hardware\n");
+ if (idle_state != MUX_IDLE_AS_IS &&
+ (idle_state < 0 || idle_state >= MC33978_NUM_AMUX_CH))
+ return dev_err_probe(dev, -EINVAL, "invalid idle-state %d\n",
+ idle_state);
+ mux->idle_state = idle_state;
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register mux chip\n");
+
+ platform_set_drvdata(pdev, mux_chip);
+
+ return 0;
+}
+
+static const struct platform_device_id mc33978_mux_id[] = {
+ { "mc33978-mux", },
+ { "mc34978-mux", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, mc33978_mux_id);
+
+static struct platform_driver mc33978_mux_driver = {
+ .driver = {
+ .name = "mc33978-mux",
+ },
+ .probe = mc33978_mux_probe,
+ .id_table = mc33978_mux_id,
+};
+module_platform_driver(mc33978_mux_driver);
+
+MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
+MODULE_DESCRIPTION("NXP MC33978/MC34978 Analog Multiplexer Driver");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox