* [PATCH v2 1/6] mux: add devm_mux_control_get_from_np() to get mux from child node
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
@ 2026-02-25 23:55 ` Frank Li
2026-02-25 23:55 ` [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips Frank Li
` (4 subsequent siblings)
5 siblings, 0 replies; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
Add new API devm_mux_control_get_from_np() to retrieve a mux control from
a specified child device node.
Make devm_mux_control_get() call devm_mux_control_get_from_np() with a NULL
node parameter, which defaults to using the device's own of_node.
Support the following DT schema:
pinctrl@0 {
uart-func {
mux-state = <&mux_chip 0>;
};
spi-func {
mux-state = <&mux_chip 1>;
};
};
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/mux/core.c | 40 ++++++++++++++++++++++++----------------
include/linux/mux/consumer.h | 16 ++++++++++++----
2 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/drivers/mux/core.c b/drivers/mux/core.c
index a3840fe0995fe0125432d34edd8ab0f2cd1a6e9a..bdd959389b4ee1b0b8a7367fadf2c148c8f2f0b1 100644
--- a/drivers/mux/core.c
+++ b/drivers/mux/core.c
@@ -522,13 +522,15 @@ static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
* @mux_name: The name identifying the mux-control.
* @state: Pointer to where the requested state is returned, or NULL when
* the required multiplexer states are handled by other means.
+ * @node: the device nodes, use dev->of_node if it is NULL.
*
* Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
*/
static struct mux_control *mux_get(struct device *dev, const char *mux_name,
- unsigned int *state)
+ unsigned int *state,
+ struct device_node *node)
{
- struct device_node *np = dev->of_node;
+ struct device_node *np = node ? node : dev->of_node;
struct of_phandle_args args;
struct mux_chip *mux_chip;
unsigned int controller;
@@ -617,7 +619,7 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
*/
struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
{
- return mux_get(dev, mux_name, NULL);
+ return mux_get(dev, mux_name, NULL, NULL);
}
EXPORT_SYMBOL_GPL(mux_control_get);
@@ -641,15 +643,17 @@ static void devm_mux_control_release(struct device *dev, void *res)
}
/**
- * devm_mux_control_get() - Get the mux-control for a device, with resource
- * management.
+ * devm_mux_control_get_from_np() - Get the mux-control for a device, with
+ * resource management.
* @dev: The device that needs a mux-control.
* @mux_name: The name identifying the mux-control.
+ * @np: the device nodes, use dev->of_node if it is NULL.
*
* Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
*/
-struct mux_control *devm_mux_control_get(struct device *dev,
- const char *mux_name)
+struct mux_control *
+devm_mux_control_get_from_np(struct device *dev, const char *mux_name,
+ struct device_node *np)
{
struct mux_control **ptr, *mux;
@@ -668,16 +672,18 @@ struct mux_control *devm_mux_control_get(struct device *dev,
return mux;
}
-EXPORT_SYMBOL_GPL(devm_mux_control_get);
+EXPORT_SYMBOL_GPL(devm_mux_control_get_from_np);
/*
* mux_state_get() - Get the mux-state for a device.
* @dev: The device that needs a mux-state.
* @mux_name: The name identifying the mux-state.
+ * @np: the device nodes, use dev->of_node if it is NULL.
*
* Return: A pointer to the mux-state, or an ERR_PTR with a negative errno.
*/
-static struct mux_state *mux_state_get(struct device *dev, const char *mux_name)
+static struct mux_state *
+mux_state_get(struct device *dev, const char *mux_name, struct device_node *np)
{
struct mux_state *mstate;
@@ -685,7 +691,7 @@ static struct mux_state *mux_state_get(struct device *dev, const char *mux_name)
if (!mstate)
return ERR_PTR(-ENOMEM);
- mstate->mux = mux_get(dev, mux_name, &mstate->state);
+ mstate->mux = mux_get(dev, mux_name, &mstate->state, np);
if (IS_ERR(mstate->mux)) {
int err = PTR_ERR(mstate->mux);
@@ -716,15 +722,17 @@ static void devm_mux_state_release(struct device *dev, void *res)
}
/**
- * devm_mux_state_get() - Get the mux-state for a device, with resource
- * management.
+ * devm_mux_state_get_from_np() - Get the mux-state for a device, with resource
+ * management.
* @dev: The device that needs a mux-control.
* @mux_name: The name identifying the mux-control.
+ * @np: the device nodes, use dev->of_node if it is NULL.
*
* Return: Pointer to the mux-state, or an ERR_PTR with a negative errno.
*/
-struct mux_state *devm_mux_state_get(struct device *dev,
- const char *mux_name)
+struct mux_state *
+devm_mux_state_get_from_np(struct device *dev, const char *mux_name,
+ struct device_node *np)
{
struct mux_state **ptr, *mstate;
@@ -732,7 +740,7 @@ struct mux_state *devm_mux_state_get(struct device *dev,
if (!ptr)
return ERR_PTR(-ENOMEM);
- mstate = mux_state_get(dev, mux_name);
+ mstate = mux_state_get(dev, mux_name, np);
if (IS_ERR(mstate)) {
devres_free(ptr);
return mstate;
@@ -743,7 +751,7 @@ struct mux_state *devm_mux_state_get(struct device *dev,
return mstate;
}
-EXPORT_SYMBOL_GPL(devm_mux_state_get);
+EXPORT_SYMBOL_GPL(devm_mux_state_get_from_np);
/*
* Using subsys_initcall instead of module_init here to try to ensure - for
diff --git a/include/linux/mux/consumer.h b/include/linux/mux/consumer.h
index 2e25c838f8312532040441ee618424b76378aad7..6300e091035323dd6158d52a55a109d43ef120aa 100644
--- a/include/linux/mux/consumer.h
+++ b/include/linux/mux/consumer.h
@@ -56,9 +56,17 @@ int mux_state_deselect(struct mux_state *mstate);
struct mux_control *mux_control_get(struct device *dev, const char *mux_name);
void mux_control_put(struct mux_control *mux);
-struct mux_control *devm_mux_control_get(struct device *dev,
- const char *mux_name);
-struct mux_state *devm_mux_state_get(struct device *dev,
- const char *mux_name);
+struct mux_control *
+devm_mux_control_get_from_np(struct device *dev, const char *mux_name,
+ struct device_node *np);
+
+#define devm_mux_control_get(dev, mux_name) \
+ devm_mux_control_get_from_np(dev, mux_name, NULL)
+
+struct mux_state *
+devm_mux_state_get_from_np(struct device *dev, const char *mux_name,
+ struct device_node *np);
+#define devm_mux_state_get(dev, mux_name) \
+ devm_mux_state_get_from_np(dev, mux_name, NULL)
#endif /* _LINUX_MUX_CONSUMER_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
2026-02-25 23:55 ` [PATCH v2 1/6] mux: add devm_mux_control_get_from_np() to get mux from child node Frank Li
@ 2026-02-25 23:55 ` Frank Li
2026-02-27 9:02 ` Linus Walleij
2026-03-06 0:57 ` Rob Herring (Arm)
2026-02-25 23:55 ` [PATCH v2 3/6] pinctrl: add optional .release_mux() callback Frank Li
` (3 subsequent siblings)
5 siblings, 2 replies; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
Add a generic pinctrl binding for board-level pinmux chips that are
controlled through the multiplexer subsystem.
On some boards, especially development boards, external mux chips are used
to switch SoC signals between different peripherals (e.g. MMC and UART).
The mux select lines are often driven by a GPIO expander over I2C,
as illustrated below:
┌──────┐ ┌─────┐
│ SOC │ │ │ ┌───────┐
│ │ │ │───►│ MMC │
│ │ │ MUX │ └───────┘
│ ├─────►│ │ ┌───────┐
│ │ │ │───►│ UART │
│ │ └─────┘ └───────┘
│ │ ▲
│ │ ┌────┴──────────────┐
│ I2C ├───►│ GPIO Expander │
└──────┘ └───────────────────┘
Traditionally, gpio-hog is used to configure the onboard mux at boot.
However, the GPIO expander may probe later than consumer devices such as
MMC. As a result, the MUX might not be configured when the peripheral
driver probes, leading to initialization failures or data transfer errors.
Introduce a generic pinctrl binding that models the board-level MUX as a
pin control provider and builds proper device links between the MUX, its
GPIO controller, and peripheral devices. This ensures correct probe
ordering and reliable mux configuration.
The implementation leverages the standard multiplexer subsystem, which
provides broad support for onboard mux controllers and avoids the need for
per-driver custom MUX handling.
Allow pinctrl-* pattern as node name because this pinctrl device have not
reg property.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2:
- change descriptions for device, not for driver
- add missed additionalProperties: false
---
.../bindings/pinctrl/pinctrl-multiplexer.yaml | 57 ++++++++++++++++++++++
.../devicetree/bindings/pinctrl/pinctrl.yaml | 2 +-
2 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/pinctrl/pinctrl-multiplexer.yaml b/Documentation/devicetree/bindings/pinctrl/pinctrl-multiplexer.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2b0385ed879b70b24ca9c39b098c3840d08d7482
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/pinctrl-multiplexer.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/pinctrl-multiplexer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic pinctrl device for on-board MUX Chips
+
+maintainers:
+ - Frank Li <Frank.Li@nxp.com>
+
+description:
+ Generic pinctrl device for on-board MUX Chips, which switch SoC signals
+ between different peripherals (e.g. MMC and UART).
+
+ The MUX select lines are often driven by a I2C GPIO expander.
+
+properties:
+ compatible:
+ const: pinctrl-multiplexer
+
+patternProperties:
+ '-grp$':
+ type: object
+ additionalProperties: false
+ properties:
+ mux-states:
+ maxItems: 1
+
+ required:
+ - mux-states
+
+required:
+ - compatible
+
+allOf:
+ - $ref: pinctrl.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pinctrl-mux {
+ compatible = "pinctrl-multiplexer";
+
+ uart-grp {
+ mux-states = <&mux 0>;
+ };
+
+ spi-grp {
+ mux-states = <&mux 1>;
+ };
+
+ i2c-grp {
+ mux-states = <&mux 2>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/pinctrl/pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/pinctrl.yaml
index 290438826c507ec6725f486d18cf686aa7c35e67..20176bf3074757de30f208e69b968a6bd6125273 100644
--- a/Documentation/devicetree/bindings/pinctrl/pinctrl.yaml
+++ b/Documentation/devicetree/bindings/pinctrl/pinctrl.yaml
@@ -27,7 +27,7 @@ description: |
properties:
$nodename:
- pattern: "^(pinctrl|pinmux)(@[0-9a-f]+)?$"
+ pattern: "^(pinctrl|pinmux)(@[0-9a-f]+|-[a-z0-9]+)?$"
"#pinctrl-cells":
description: >
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips
2026-02-25 23:55 ` [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips Frank Li
@ 2026-02-27 9:02 ` Linus Walleij
2026-03-06 0:57 ` Rob Herring (Arm)
1 sibling, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-02-27 9:02 UTC (permalink / raw)
To: Frank Li
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
> Add a generic pinctrl binding for board-level pinmux chips that are
> controlled through the multiplexer subsystem.
>
> On some boards, especially development boards, external mux chips are used
> to switch SoC signals between different peripherals (e.g. MMC and UART).
> The mux select lines are often driven by a GPIO expander over I2C,
> as illustrated below:
>
> ┌──────┐ ┌─────┐
> │ SOC │ │ │ ┌───────┐
> │ │ │ │───►│ MMC │
> │ │ │ MUX │ └───────┘
> │ ├─────►│ │ ┌───────┐
> │ │ │ │───►│ UART │
> │ │ └─────┘ └───────┘
> │ │ ▲
> │ │ ┌────┴──────────────┐
> │ I2C ├───►│ GPIO Expander │
> └──────┘ └───────────────────┘
>
> Traditionally, gpio-hog is used to configure the onboard mux at boot.
> However, the GPIO expander may probe later than consumer devices such as
> MMC. As a result, the MUX might not be configured when the peripheral
> driver probes, leading to initialization failures or data transfer errors.
>
> Introduce a generic pinctrl binding that models the board-level MUX as a
> pin control provider and builds proper device links between the MUX, its
> GPIO controller, and peripheral devices. This ensures correct probe
> ordering and reliable mux configuration.
>
> The implementation leverages the standard multiplexer subsystem, which
> provides broad support for onboard mux controllers and avoids the need for
> per-driver custom MUX handling.
>
> Allow pinctrl-* pattern as node name because this pinctrl device have not
> reg property.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
I think this is smart and elegant.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips
2026-02-25 23:55 ` [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips Frank Li
2026-02-27 9:02 ` Linus Walleij
@ 2026-03-06 0:57 ` Rob Herring (Arm)
1 sibling, 0 replies; 18+ messages in thread
From: Rob Herring (Arm) @ 2026-03-06 0:57 UTC (permalink / raw)
To: Frank Li
Cc: Linus Walleij, imx, linux-arm-kernel, Sascha Hauer, Fabio Estevam,
Peter Rosin, Conor Dooley, Haibo Chen, linux-kernel,
Pengutronix Kernel Team, Krzysztof Kozlowski,
Rafał Miłecki, devicetree, linux-gpio
On Wed, 25 Feb 2026 18:55:06 -0500, Frank Li wrote:
> Add a generic pinctrl binding for board-level pinmux chips that are
> controlled through the multiplexer subsystem.
>
> On some boards, especially development boards, external mux chips are used
> to switch SoC signals between different peripherals (e.g. MMC and UART).
> The mux select lines are often driven by a GPIO expander over I2C,
> as illustrated below:
>
> ┌──────┐ ┌─────┐
> │ SOC │ │ │ ┌───────┐
> │ │ │ │───►│ MMC │
> │ │ │ MUX │ └───────┘
> │ ├─────►│ │ ┌───────┐
> │ │ │ │───►│ UART │
> │ │ └─────┘ └───────┘
> │ │ ▲
> │ │ ┌────┴──────────────┐
> │ I2C ├───►│ GPIO Expander │
> └──────┘ └───────────────────┘
>
> Traditionally, gpio-hog is used to configure the onboard mux at boot.
> However, the GPIO expander may probe later than consumer devices such as
> MMC. As a result, the MUX might not be configured when the peripheral
> driver probes, leading to initialization failures or data transfer errors.
>
> Introduce a generic pinctrl binding that models the board-level MUX as a
> pin control provider and builds proper device links between the MUX, its
> GPIO controller, and peripheral devices. This ensures correct probe
> ordering and reliable mux configuration.
>
> The implementation leverages the standard multiplexer subsystem, which
> provides broad support for onboard mux controllers and avoids the need for
> per-driver custom MUX handling.
>
> Allow pinctrl-* pattern as node name because this pinctrl device have not
> reg property.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> change in v2:
> - change descriptions for device, not for driver
> - add missed additionalProperties: false
> ---
> .../bindings/pinctrl/pinctrl-multiplexer.yaml | 57 ++++++++++++++++++++++
> .../devicetree/bindings/pinctrl/pinctrl.yaml | 2 +-
> 2 files changed, 58 insertions(+), 1 deletion(-)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 3/6] pinctrl: add optional .release_mux() callback
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
2026-02-25 23:55 ` [PATCH v2 1/6] mux: add devm_mux_control_get_from_np() to get mux from child node Frank Li
2026-02-25 23:55 ` [PATCH v2 2/6] dt-bindings: pinctrl: Add generic pinctrl for board-level mux chips Frank Li
@ 2026-02-25 23:55 ` Frank Li
2026-02-27 9:07 ` Linus Walleij
2026-02-25 23:55 ` [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework Frank Li
` (2 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
Add an optional .release_mux() callback to the pinmux_ops.
Some devices require releasing resources that were previously acquired in
.set_mux(). Providing a dedicated .release_mux() callback allows drivers to
properly clean up hardware state or associated resources when a mux
function is no longer active.
The callback is optional and does not affect existing drivers.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/pinctrl/pinmux.c | 5 +++++
include/linux/pinctrl/pinmux.h | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/drivers/pinctrl/pinmux.c b/drivers/pinctrl/pinmux.c
index 3a8dd184ba3d670e01a890427e19af59b65eb813..c705bc182266c596c4e6c820f5e3ffcadbbb2838 100644
--- a/drivers/pinctrl/pinmux.c
+++ b/drivers/pinctrl/pinmux.c
@@ -517,6 +517,7 @@ void pinmux_disable_setting(const struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
+ const struct pinmux_ops *ops = pctldev->desc->pmxops;
int ret = 0;
const unsigned int *pins = NULL;
unsigned int num_pins = 0;
@@ -563,6 +564,10 @@ void pinmux_disable_setting(const struct pinctrl_setting *setting)
pins[i], desc->name, gname);
}
}
+
+ if (ops->release_mux)
+ ops->release_mux(pctldev, setting->data.mux.func,
+ setting->data.mux.group);
}
#ifdef CONFIG_DEBUG_FS
diff --git a/include/linux/pinctrl/pinmux.h b/include/linux/pinctrl/pinmux.h
index 094bbe2fd6fd5ea3c5fdf5b6d6d9a7639700b50b..ad7f8c31655e10ae854f7c325f88d2a533dcb035 100644
--- a/include/linux/pinctrl/pinmux.h
+++ b/include/linux/pinctrl/pinmux.h
@@ -51,6 +51,8 @@ struct pinctrl_gpio_range;
* are handled by the pinmux subsystem. The @func_selector selects a
* certain function whereas @group_selector selects a certain set of pins
* to be used. On simple controllers the latter argument may be ignored
+ * @release_mux: disable a certain muxing function with a certain pin group,
+ * which set by @set_mux.
* @gpio_request_enable: requests and enables GPIO on a certain pin.
* Implement this only if you can mux every pin individually as GPIO. The
* affected GPIO range is passed along with an offset(pin number) into that
@@ -80,6 +82,9 @@ struct pinmux_ops {
unsigned int selector);
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned int func_selector,
unsigned int group_selector);
+ void (*release_mux) (struct pinctrl_dev *pctldev,
+ unsigned int func_selector,
+ unsigned int group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned int offset);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v2 3/6] pinctrl: add optional .release_mux() callback
2026-02-25 23:55 ` [PATCH v2 3/6] pinctrl: add optional .release_mux() callback Frank Li
@ 2026-02-27 9:07 ` Linus Walleij
2026-02-27 15:32 ` Frank Li
0 siblings, 1 reply; 18+ messages in thread
From: Linus Walleij @ 2026-02-27 9:07 UTC (permalink / raw)
To: Frank Li
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
> Add an optional .release_mux() callback to the pinmux_ops.
>
> Some devices require releasing resources that were previously acquired in
> .set_mux(). Providing a dedicated .release_mux() callback allows drivers to
> properly clean up hardware state or associated resources when a mux
> function is no longer active.
>
> The callback is optional and does not affect existing drivers.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
Can you explain why you need this custom code for this?
Nominally pin control defines and puts the hardware into a
number of states such as:
"default"
"idle"
"sleep"
"init"
Usually (at least for silicon) what .release_mux() would to
is semantically equivalent to a transition into the "init" or
"sleep" state. And if these are not descriptive enough you can
even define a "released" state.
Is it not possible to reach the set-up of the hardware that you
are desiring by just defining such a relaxed state?
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 3/6] pinctrl: add optional .release_mux() callback
2026-02-27 9:07 ` Linus Walleij
@ 2026-02-27 15:32 ` Frank Li
2026-03-02 10:12 ` Linus Walleij
0 siblings, 1 reply; 18+ messages in thread
From: Frank Li @ 2026-02-27 15:32 UTC (permalink / raw)
To: Linus Walleij
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Fri, Feb 27, 2026 at 10:07:05AM +0100, Linus Walleij wrote:
> On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
>
> > Add an optional .release_mux() callback to the pinmux_ops.
> >
> > Some devices require releasing resources that were previously acquired in
> > .set_mux(). Providing a dedicated .release_mux() callback allows drivers to
> > properly clean up hardware state or associated resources when a mux
> > function is no longer active.
> >
> > The callback is optional and does not affect existing drivers.
> >
> > Signed-off-by: Frank Li <Frank.Li@nxp.com>
>
> Can you explain why you need this custom code for this?
>
> Nominally pin control defines and puts the hardware into a
> number of states such as:
> "default"
> "idle"
> "sleep"
> "init"
>
> Usually (at least for silicon) what .release_mux() would to
> is semantically equivalent to a transition into the "init" or
> "sleep" state. And if these are not descriptive enough you can
> even define a "released" state.
>
> Is it not possible to reach the set-up of the hardware that you
> are desiring by just defining such a relaxed state?
I am not familiar with pinctrl code. I just need a place to call a callback
which do opposite work at .set_mux() function.
I see pair function pinmux_enable_setting() call .set_mux() and
pinmux_disable_setting() just missing do oppsite work of .set_mux();
I may think too simple. I just do insmod/rmmod test. Any suggestion where
is good place to put it?
Does it call pair pinmux_enable(disable)_setting when switch state?
Frank
>
> Yours,
> Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 3/6] pinctrl: add optional .release_mux() callback
2026-02-27 15:32 ` Frank Li
@ 2026-03-02 10:12 ` Linus Walleij
0 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-03-02 10:12 UTC (permalink / raw)
To: Frank Li
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Fri, Feb 27, 2026 at 4:32 PM Frank Li <Frank.li@nxp.com> wrote:
> On Fri, Feb 27, 2026 at 10:07:05AM +0100, Linus Walleij wrote:
> > On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
> >
> > > Add an optional .release_mux() callback to the pinmux_ops.
> > >
> > > Some devices require releasing resources that were previously acquired in
> > > .set_mux(). Providing a dedicated .release_mux() callback allows drivers to
> > > properly clean up hardware state or associated resources when a mux
> > > function is no longer active.
> > >
> > > The callback is optional and does not affect existing drivers.
> > >
> > > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> >
> > Can you explain why you need this custom code for this?
> >
> > Nominally pin control defines and puts the hardware into a
> > number of states such as:
> > "default"
> > "idle"
> > "sleep"
> > "init"
> >
> > Usually (at least for silicon) what .release_mux() would to
> > is semantically equivalent to a transition into the "init" or
> > "sleep" state. And if these are not descriptive enough you can
> > even define a "released" state.
> >
> > Is it not possible to reach the set-up of the hardware that you
> > are desiring by just defining such a relaxed state?
>
> I am not familiar with pinctrl code. I just need a place to call a callback
> which do opposite work at .set_mux() function.
>
> I see pair function pinmux_enable_setting() call .set_mux() and
> pinmux_disable_setting() just missing do oppsite work of .set_mux();
>
> I may think too simple. I just do insmod/rmmod test. Any suggestion where
> is good place to put it?
>
> Does it call pair pinmux_enable(disable)_setting when switch state?
The pinmux states are more like a state machine, you transition
between different states.
I think in this case you may want your driver or the device driver
core to transition to the "init" state to release the mux, see
my other reply.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
` (2 preceding siblings ...)
2026-02-25 23:55 ` [PATCH v2 3/6] pinctrl: add optional .release_mux() callback Frank Li
@ 2026-02-25 23:55 ` Frank Li
2026-02-27 9:09 ` Daniel Baluta
2026-02-27 9:20 ` Linus Walleij
2026-02-25 23:55 ` [PATCH v2 5/6] arm64: dts: imx8mp-evk: add board-level mux for CAN2 and MICFIL Frank Li
2026-02-25 23:55 ` [PATCH v2 6/6] arm64: dts: imx8mp-evk: add flexcan2 overlay file Frank Li
5 siblings, 2 replies; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
Many boards use on-board mux chips (often controlled by GPIOs from an I2C
expander) to switch shared signals between peripherals.
Add a generic pinctrl driver built on top of the mux framework to
centralize mux handling and avoid probe ordering issues. Keep board-level
routing out of individual drivers and supports boot-time only mux
selection.
Ensure correct probe ordering, especially when the GPIO expander is probed
later.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2:
- fix copywrite by add nxp
- fix if (!*map) check
- add release_mux to call mux_state_deselect()
---
drivers/pinctrl/Kconfig | 9 ++
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-generic-mux.c | 241 ++++++++++++++++++++++++++++++++++
3 files changed, 251 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index afecd9407f5354f5b92223f8cd80d2f7a08f8e7d..0657eeeeb587fa5e68dc3c1e00be35608e243b80 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -274,6 +274,15 @@ config PINCTRL_GEMINI
select GENERIC_PINCONF
select MFD_SYSCON
+config PINCTRL_GENERIC_MUX
+ tristate "Generic Pinctrl driver by using multiplexer"
+ depends on MULTIPLEXER
+ select PINMUX
+ select GENERIC_PINCONF
+ help
+ Generic pinctrl driver by MULTIPLEXER framework to control on
+ board pin selection.
+
config PINCTRL_INGENIC
bool "Pinctrl driver for the Ingenic JZ47xx SoCs"
default MACH_INGENIC
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index f7d5d5f76d0c8becc0aa1d77c68b6ced924ea264..fcd1703440d24579636e8ddb6cbd83a0a982dfb7 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_PINCTRL_EQUILIBRIUM) += pinctrl-equilibrium.o
obj-$(CONFIG_PINCTRL_EP93XX) += pinctrl-ep93xx.o
obj-$(CONFIG_PINCTRL_EYEQ5) += pinctrl-eyeq5.o
obj-$(CONFIG_PINCTRL_GEMINI) += pinctrl-gemini.o
+obj-$(CONFIG_PINCTRL_GENERIC_MUX) += pinctrl-generic-mux.o
obj-$(CONFIG_PINCTRL_INGENIC) += pinctrl-ingenic.o
obj-$(CONFIG_PINCTRL_K210) += pinctrl-k210.o
obj-$(CONFIG_PINCTRL_K230) += pinctrl-k230.o
diff --git a/drivers/pinctrl/pinctrl-generic-mux.c b/drivers/pinctrl/pinctrl-generic-mux.c
new file mode 100644
index 0000000000000000000000000000000000000000..978cbc4f82a0b3e56dd83ce24426d4e764262a6e
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-generic-mux.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic Pin Control Driver for Board-Level Mux Chips
+ * Copyright (C) 2026 NXP
+ */
+
+#include <linux/cleanup.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include <linux/mux/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "pinmux.h"
+
+struct mux_pin_function {
+ struct mux_state *mux_state;
+};
+
+struct mux_pinctrl {
+ struct device *dev;
+ struct pinctrl_dev *pctl;
+
+ /* mutex protect [pinctrl|pinmux]_generic functions */
+ struct mutex lock;
+ int cur_select;
+};
+
+static int
+mux_pinmux_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map, unsigned int *num_maps)
+{
+ struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
+ struct mux_pin_function *function;
+ struct device *dev = mpctl->dev;
+ const char **pgnames;
+ int selector;
+ int group;
+ int ret;
+
+ *map = devm_kcalloc(dev, 1, sizeof(**map), GFP_KERNEL);
+ if (!*map)
+ return -ENOMEM;
+
+ *num_maps = 0;
+
+ function = devm_kzalloc(dev, sizeof(*function), GFP_KERNEL);
+ if (!function) {
+ ret = -ENOMEM;
+ goto err_func;
+ }
+
+ pgnames = devm_kzalloc(dev, sizeof(*pgnames), GFP_KERNEL);
+ if (!pgnames) {
+ ret = -ENOMEM;
+ goto err_pgnames;
+ }
+
+ pgnames[0] = np_config->name;
+
+ guard(mutex)(&mpctl->lock);
+
+ selector = pinmux_generic_add_function(mpctl->pctl, np_config->name,
+ pgnames, 1, function);
+ if (selector < 0) {
+ ret = selector;
+ goto err_add_func;
+ }
+
+ group = pinctrl_generic_add_group(mpctl->pctl, np_config->name, NULL, 0, mpctl);
+ if (group < 0) {
+ ret = group;
+ goto err_add_group;
+ }
+
+ function->mux_state = devm_mux_state_get_from_np(pctldev->dev, NULL, np_config);
+ if (IS_ERR(function->mux_state)) {
+ ret = PTR_ERR(function->mux_state);
+ goto err_mux_state_get;
+ }
+
+ (*map)->type = PIN_MAP_TYPE_MUX_GROUP;
+ (*map)->data.mux.group = np_config->name;
+ (*map)->data.mux.function = np_config->name;
+
+ *num_maps = 1;
+
+ return 0;
+
+err_mux_state_get:
+ pinctrl_generic_remove_group(mpctl->pctl, group);
+err_add_group:
+ pinmux_generic_remove_function(mpctl->pctl, selector);
+err_add_func:
+ devm_kfree(dev, pgnames);
+err_pgnames:
+ devm_kfree(dev, function);
+err_func:
+ devm_kfree(dev, *map);
+
+ return ret;
+}
+
+static void
+mux_pinmux_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map,
+ unsigned int num_maps)
+{
+ struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
+
+ devm_kfree(mpctl->dev, map);
+}
+
+static const struct pinctrl_ops mux_pinctrl_ops = {
+ .get_groups_count = pinctrl_generic_get_group_count,
+ .get_group_name = pinctrl_generic_get_group_name,
+ .get_group_pins = pinctrl_generic_get_group_pins,
+ .dt_node_to_map = mux_pinmux_dt_node_to_map,
+ .dt_free_map = mux_pinmux_dt_free_map,
+};
+
+static int mux_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int func_selector,
+ unsigned int group_selector)
+{
+ struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
+ const struct function_desc *function;
+ struct mux_pin_function *func;
+ int ret;
+
+ guard(mutex)(&mpctl->lock);
+
+ function = pinmux_generic_get_function(pctldev, func_selector);
+ func = function->data;
+
+ if (mpctl->cur_select == func_selector)
+ return 0;
+
+ if (mpctl->cur_select >= 0 && mpctl->cur_select != func_selector)
+ return -EINVAL;
+
+ ret = mux_state_select(func->mux_state);
+ if (ret)
+ return ret;
+
+ mpctl->cur_select = func_selector;
+
+ return 0;
+}
+
+static void mux_pinmux_release_mux(struct pinctrl_dev *pctldev,
+ unsigned int func_selector,
+ unsigned int group_selector)
+{
+ struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
+ const struct function_desc *function;
+ struct mux_pin_function *func;
+
+ guard(mutex)(&mpctl->lock);
+
+ function = pinmux_generic_get_function(pctldev, func_selector);
+ func = function->data;
+
+ mux_state_deselect(func->mux_state);
+
+ mpctl->cur_select = -1;
+}
+
+static const struct pinmux_ops mux_pinmux_ops = {
+ .get_functions_count = pinmux_generic_get_function_count,
+ .get_function_name = pinmux_generic_get_function_name,
+ .get_function_groups = pinmux_generic_get_function_groups,
+ .set_mux = mux_pinmux_set_mux,
+ .release_mux = mux_pinmux_release_mux,
+};
+
+static int mux_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_pinctrl *mpctl;
+ struct pinctrl_desc *pctl_desc;
+ int ret;
+
+ mpctl = devm_kzalloc(dev, sizeof(*mpctl), GFP_KERNEL);
+ if (!mpctl)
+ return -ENOMEM;
+
+ mpctl->dev = dev;
+ mpctl->cur_select = -1;
+
+ platform_set_drvdata(pdev, mpctl);
+
+ pctl_desc = devm_kzalloc(dev, sizeof(*pctl_desc), GFP_KERNEL);
+ if (!pctl_desc)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &mpctl->lock);
+ if (ret)
+ return ret;
+
+ pctl_desc->name = dev_name(dev);
+ pctl_desc->owner = THIS_MODULE;
+ pctl_desc->pctlops = &mux_pinctrl_ops;
+ pctl_desc->pmxops = &mux_pinmux_ops;
+
+ ret = devm_pinctrl_register_and_init(dev, pctl_desc, mpctl,
+ &mpctl->pctl);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register pinctrl.\n");
+
+ ret = pinctrl_enable(mpctl->pctl);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable pinctrl.\n");
+
+ return 0;
+}
+
+static const struct of_device_id mux_pinctrl_of_match[] = {
+ { .compatible = "pinctrl-multiplexer" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mux_pinctrl_of_match);
+
+static struct platform_driver mux_pinctrl_driver = {
+ .driver = {
+ .name = "generic-pinctrl-mux",
+ .of_match_table = mux_pinctrl_of_match,
+ },
+ .probe = mux_pinctrl_probe,
+};
+module_platform_driver(mux_pinctrl_driver);
+
+MODULE_AUTHOR("Frank Li <Frank.Li@nxp.com>");
+MODULE_DESCRIPTION("Generic Pin Control Driver for Board-Level Mux Chips");
+MODULE_LICENSE("GPL");
+
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-02-25 23:55 ` [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework Frank Li
@ 2026-02-27 9:09 ` Daniel Baluta
2026-02-27 9:20 ` Linus Walleij
1 sibling, 0 replies; 18+ messages in thread
From: Daniel Baluta @ 2026-02-27 9:09 UTC (permalink / raw)
To: Frank Li, Peter Rosin, Linus Walleij, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Rafał Miłecki,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen
On 2/26/26 01:55, Frank Li wrote:
> diff --git a/drivers/pinctrl/pinctrl-generic-mux.c b/drivers/pinctrl/pinctrl-generic-mux.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..978cbc4f82a0b3e56dd83ce24426d4e764262a6e
> --- /dev/null
> +++ b/drivers/pinctrl/pinctrl-generic-mux.c
> @@ -0,0 +1,241 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Generic Pin Control Driver for Board-Level Mux Chips
> + * Copyright (C) 2026 NXP
> + */
Nit: NXP legal asked us to drop (C) so this should be Copyright 2026 NXP!
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-02-25 23:55 ` [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework Frank Li
2026-02-27 9:09 ` Daniel Baluta
@ 2026-02-27 9:20 ` Linus Walleij
2026-02-27 15:22 ` Frank Li
1 sibling, 1 reply; 18+ messages in thread
From: Linus Walleij @ 2026-02-27 9:20 UTC (permalink / raw)
To: Frank Li
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
Hi Frank,
thanks for your patch!
On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
> Many boards use on-board mux chips (often controlled by GPIOs from an I2C
> expander) to switch shared signals between peripherals.
>
> Add a generic pinctrl driver built on top of the mux framework to
> centralize mux handling and avoid probe ordering issues. Keep board-level
> routing out of individual drivers and supports boot-time only mux
> selection.
>
> Ensure correct probe ordering, especially when the GPIO expander is probed
> later.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
(...)
> +static int
> +mux_pinmux_dt_node_to_map(struct pinctrl_dev *pctldev,
> + struct device_node *np_config,
> + struct pinctrl_map **map, unsigned int *num_maps)
> +{
> + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> + struct mux_pin_function *function;
> + struct device *dev = mpctl->dev;
> + const char **pgnames;
> + int selector;
> + int group;
> + int ret;
> +
> + *map = devm_kcalloc(dev, 1, sizeof(**map), GFP_KERNEL);
> + if (!*map)
> + return -ENOMEM;
> +
> + *num_maps = 0;
> +
> + function = devm_kzalloc(dev, sizeof(*function), GFP_KERNEL);
> + if (!function) {
> + ret = -ENOMEM;
> + goto err_func;
> + }
> +
> + pgnames = devm_kzalloc(dev, sizeof(*pgnames), GFP_KERNEL);
> + if (!pgnames) {
> + ret = -ENOMEM;
> + goto err_pgnames;
> + }
> +
> + pgnames[0] = np_config->name;
> +
> + guard(mutex)(&mpctl->lock);
> +
> + selector = pinmux_generic_add_function(mpctl->pctl, np_config->name,
> + pgnames, 1, function);
> + if (selector < 0) {
> + ret = selector;
> + goto err_add_func;
> + }
> +
> + group = pinctrl_generic_add_group(mpctl->pctl, np_config->name, NULL, 0, mpctl);
> + if (group < 0) {
> + ret = group;
> + goto err_add_group;
> + }
> +
> + function->mux_state = devm_mux_state_get_from_np(pctldev->dev, NULL, np_config);
> + if (IS_ERR(function->mux_state)) {
> + ret = PTR_ERR(function->mux_state);
> + goto err_mux_state_get;
> + }
> +
> + (*map)->type = PIN_MAP_TYPE_MUX_GROUP;
> + (*map)->data.mux.group = np_config->name;
> + (*map)->data.mux.function = np_config->name;
> +
> + *num_maps = 1;
> +
> + return 0;
> +
> +err_mux_state_get:
> + pinctrl_generic_remove_group(mpctl->pctl, group);
> +err_add_group:
> + pinmux_generic_remove_function(mpctl->pctl, selector);
> +err_add_func:
> + devm_kfree(dev, pgnames);
> +err_pgnames:
> + devm_kfree(dev, function);
> +err_func:
> + devm_kfree(dev, *map);
> +
> + return ret;
> +}
This is so close to the pinctrl-internal helpers that you better work with
those instead.
Can't you just use pinctrl_generic_pins_function_dt_node_to_map()?
It was added in the last merge window in
commit 43722575e5cdcc6c457bfe81fae9c3ad343ea031
"pinctrl: add generic functions + pins mapper"
There are problems with the above, for example this is only called
on the probe() path so you would not need any devm_*free calls,
as you can see in the generic helpers.
I think you need to look into using or extending the existing helpers for this,
> +static void
> +mux_pinmux_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map,
> + unsigned int num_maps)
> +{
> + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> +
> + devm_kfree(mpctl->dev, map);
> +}
Just use pinctrl_utils_free_map().
> +static void mux_pinmux_release_mux(struct pinctrl_dev *pctldev,
> + unsigned int func_selector,
> + unsigned int group_selector)
> +{
> + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> + const struct function_desc *function;
> + struct mux_pin_function *func;
> +
> + guard(mutex)(&mpctl->lock);
> +
> + function = pinmux_generic_get_function(pctldev, func_selector);
> + func = function->data;
> +
> + mux_state_deselect(func->mux_state);
> +
> + mpctl->cur_select = -1;
> +}
As mentioned I have my doubts about this, explain why this hardware
is so different that this is needed.
Other than that I like the concept!
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-02-27 9:20 ` Linus Walleij
@ 2026-02-27 15:22 ` Frank Li
2026-03-02 10:11 ` Linus Walleij
0 siblings, 1 reply; 18+ messages in thread
From: Frank Li @ 2026-02-27 15:22 UTC (permalink / raw)
To: Linus Walleij
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Fri, Feb 27, 2026 at 10:20:14AM +0100, Linus Walleij wrote:
> Hi Frank,
>
> thanks for your patch!
>
> On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
>
> > Many boards use on-board mux chips (often controlled by GPIOs from an I2C
> > expander) to switch shared signals between peripherals.
> >
> > Add a generic pinctrl driver built on top of the mux framework to
> > centralize mux handling and avoid probe ordering issues. Keep board-level
> > routing out of individual drivers and supports boot-time only mux
> > selection.
> >
> > Ensure correct probe ordering, especially when the GPIO expander is probed
> > later.
> >
> > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> (...)
>
> > +static int
> > +mux_pinmux_dt_node_to_map(struct pinctrl_dev *pctldev,
> > + struct device_node *np_config,
> > + struct pinctrl_map **map, unsigned int *num_maps)
> > +{
> > + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> > + struct mux_pin_function *function;
> > + struct device *dev = mpctl->dev;
> > + const char **pgnames;
> > + int selector;
> > + int group;
> > + int ret;
> > +
> > + *map = devm_kcalloc(dev, 1, sizeof(**map), GFP_KERNEL);
> > + if (!*map)
> > + return -ENOMEM;
> > +
> > + *num_maps = 0;
> > +
> > + function = devm_kzalloc(dev, sizeof(*function), GFP_KERNEL);
> > + if (!function) {
> > + ret = -ENOMEM;
> > + goto err_func;
> > + }
> > +
> > + pgnames = devm_kzalloc(dev, sizeof(*pgnames), GFP_KERNEL);
> > + if (!pgnames) {
> > + ret = -ENOMEM;
> > + goto err_pgnames;
> > + }
> > +
> > + pgnames[0] = np_config->name;
> > +
> > + guard(mutex)(&mpctl->lock);
> > +
> > + selector = pinmux_generic_add_function(mpctl->pctl, np_config->name,
> > + pgnames, 1, function);
> > + if (selector < 0) {
> > + ret = selector;
> > + goto err_add_func;
> > + }
> > +
> > + group = pinctrl_generic_add_group(mpctl->pctl, np_config->name, NULL, 0, mpctl);
> > + if (group < 0) {
> > + ret = group;
> > + goto err_add_group;
> > + }
> > +
> > + function->mux_state = devm_mux_state_get_from_np(pctldev->dev, NULL, np_config);
> > + if (IS_ERR(function->mux_state)) {
> > + ret = PTR_ERR(function->mux_state);
> > + goto err_mux_state_get;
> > + }
> > +
> > + (*map)->type = PIN_MAP_TYPE_MUX_GROUP;
> > + (*map)->data.mux.group = np_config->name;
> > + (*map)->data.mux.function = np_config->name;
> > +
> > + *num_maps = 1;
> > +
> > + return 0;
> > +
> > +err_mux_state_get:
> > + pinctrl_generic_remove_group(mpctl->pctl, group);
> > +err_add_group:
> > + pinmux_generic_remove_function(mpctl->pctl, selector);
> > +err_add_func:
> > + devm_kfree(dev, pgnames);
> > +err_pgnames:
> > + devm_kfree(dev, function);
> > +err_func:
> > + devm_kfree(dev, *map);
> > +
> > + return ret;
> > +}
>
> This is so close to the pinctrl-internal helpers that you better work with
> those instead.
>
> Can't you just use pinctrl_generic_pins_function_dt_node_to_map()?
> It was added in the last merge window in
> commit 43722575e5cdcc6c457bfe81fae9c3ad343ea031
> "pinctrl: add generic functions + pins mapper"
>
> There are problems with the above, for example this is only called
> on the probe() path so you would not need any devm_*free calls,
> as you can see in the generic helpers.
>
> I think you need to look into using or extending the existing helpers for this,
>
> > +static void
> > +mux_pinmux_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map,
> > + unsigned int num_maps)
> > +{
> > + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> > +
> > + devm_kfree(mpctl->dev, map);
> > +}
>
> Just use pinctrl_utils_free_map().
>
> > +static void mux_pinmux_release_mux(struct pinctrl_dev *pctldev,
> > + unsigned int func_selector,
> > + unsigned int group_selector)
> > +{
> > + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> > + const struct function_desc *function;
> > + struct mux_pin_function *func;
> > +
> > + guard(mutex)(&mpctl->lock);
> > +
> > + function = pinmux_generic_get_function(pctldev, func_selector);
> > + func = function->data;
> > +
> > + mux_state_deselect(func->mux_state);
> > +
> > + mpctl->cur_select = -1;
> > +}
>
> As mentioned I have my doubts about this, explain why this hardware
> is so different that this is needed.
>
As board mux (uart and flexcan) exist, for example, only one of UART and
FlexCAN work.
when modprobe uart.ko, mux_state_select called.
So flexcan driver can't get such mux as expected.
when remmod uart.ko, we need release mux_state, so flexcan driver can
get such resource.
Genernally, DT may only enouble one of UART or flexcan.
but insmod uart.ko
rmmod uart.ko
insmod uart.ko (here also need release previous's state at prevous rmmod).
Frank
> Other than that I like the concept!
>
> Yours,
> Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-02-27 15:22 ` Frank Li
@ 2026-03-02 10:11 ` Linus Walleij
2026-03-05 17:37 ` Frank Li
0 siblings, 1 reply; 18+ messages in thread
From: Linus Walleij @ 2026-03-02 10:11 UTC (permalink / raw)
To: Frank Li
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Fri, Feb 27, 2026 at 4:23 PM Frank Li <Frank.li@nxp.com> wrote:
> On Fri, Feb 27, 2026 at 10:20:14AM +0100, Linus Walleij wrote:
> > On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
> > > +static void mux_pinmux_release_mux(struct pinctrl_dev *pctldev,
> > > + unsigned int func_selector,
> > > + unsigned int group_selector)
> > > +{
> > > + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> > > + const struct function_desc *function;
> > > + struct mux_pin_function *func;
> > > +
> > > + guard(mutex)(&mpctl->lock);
> > > +
> > > + function = pinmux_generic_get_function(pctldev, func_selector);
> > > + func = function->data;
> > > +
> > > + mux_state_deselect(func->mux_state);
> > > +
> > > + mpctl->cur_select = -1;
> > > +}
> >
> > As mentioned I have my doubts about this, explain why this hardware
> > is so different that this is needed.
>
> As board mux (uart and flexcan) exist, for example, only one of UART and
> FlexCAN work.
>
> when modprobe uart.ko, mux_state_select called.
>
> So flexcan driver can't get such mux as expected.
>
> when remmod uart.ko, we need release mux_state, so flexcan driver can
> get such resource.
>
> Genernally, DT may only enouble one of UART or flexcan.
>
> but insmod uart.ko
> rmmod uart.ko
>
> insmod uart.ko (here also need release previous's state at prevous rmmod).
Can't you just enter the state "init"? This can be used
explicitly on the uart .remove() path using pinctrl_pm_select_init_state().
Sure the device core does not do it automatically but this is a
special case.
If you want a generic solution without having to change any drivers,
Add pinctrl_unbind_pins() to drivers/base/pinctrl.c and call on the
generic .remove path for all drivers to put the device into "init"
state during rmmod.
This gives us better control of the actual hardware states I think?
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-03-02 10:11 ` Linus Walleij
@ 2026-03-05 17:37 ` Frank Li
2026-03-08 23:49 ` Linus Walleij
0 siblings, 1 reply; 18+ messages in thread
From: Frank Li @ 2026-03-05 17:37 UTC (permalink / raw)
To: Linus Walleij
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Mon, Mar 02, 2026 at 11:11:00AM +0100, Linus Walleij wrote:
> On Fri, Feb 27, 2026 at 4:23 PM Frank Li <Frank.li@nxp.com> wrote:
> > On Fri, Feb 27, 2026 at 10:20:14AM +0100, Linus Walleij wrote:
> > > On Thu, Feb 26, 2026 at 12:55 AM Frank Li <Frank.Li@nxp.com> wrote:
>
> > > > +static void mux_pinmux_release_mux(struct pinctrl_dev *pctldev,
> > > > + unsigned int func_selector,
> > > > + unsigned int group_selector)
> > > > +{
> > > > + struct mux_pinctrl *mpctl = pinctrl_dev_get_drvdata(pctldev);
> > > > + const struct function_desc *function;
> > > > + struct mux_pin_function *func;
> > > > +
> > > > + guard(mutex)(&mpctl->lock);
> > > > +
> > > > + function = pinmux_generic_get_function(pctldev, func_selector);
> > > > + func = function->data;
> > > > +
> > > > + mux_state_deselect(func->mux_state);
> > > > +
> > > > + mpctl->cur_select = -1;
> > > > +}
> > >
> > > As mentioned I have my doubts about this, explain why this hardware
> > > is so different that this is needed.
> >
> > As board mux (uart and flexcan) exist, for example, only one of UART and
> > FlexCAN work.
> >
> > when modprobe uart.ko, mux_state_select called.
> >
> > So flexcan driver can't get such mux as expected.
> >
> > when remmod uart.ko, we need release mux_state, so flexcan driver can
> > get such resource.
> >
> > Genernally, DT may only enouble one of UART or flexcan.
> >
> > but insmod uart.ko
> > rmmod uart.ko
> >
> > insmod uart.ko (here also need release previous's state at prevous rmmod).
>
> Can't you just enter the state "init"? This can be used
> explicitly on the uart .remove() path using pinctrl_pm_select_init_state().
>
> Sure the device core does not do it automatically but this is a
> special case.
>
> If you want a generic solution without having to change any drivers,
> Add pinctrl_unbind_pins() to drivers/base/pinctrl.c and call on the
> generic .remove path for all drivers to put the device into "init"
> state during rmmod.
>
> This gives us better control of the actual hardware states I think?
init state is before probe if exist, which not fit this case because:
- on board mux need be enabled before probe, some device need communicate
with periphal at probe, such as SD card. SD data/cmd line must be ready
to scan SD, similar case for all mtd devices.
if add new state "release", when the pinctrl may switch between default,
sleep, ... , "release" have to switch between switch state, such as default
-> sleep have to change to default->release->sleep.
mux device also have idle state, which map to pinctrl's "init" or "sleep"
state.
The key difference should be
- pinctrl can uncondtional switch state.
- mux frame have to use pair mux_control_(de)select() to switch state.
So, I think just need a hook in pinctrl system to call mux_control_deselect()
when switch state and release resource.
Frank
>
> Yours,
> Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework
2026-03-05 17:37 ` Frank Li
@ 2026-03-08 23:49 ` Linus Walleij
0 siblings, 0 replies; 18+ messages in thread
From: Linus Walleij @ 2026-03-08 23:49 UTC (permalink / raw)
To: Frank Li, Fan Wu
Cc: Peter Rosin, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafał Miłecki, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, linux-kernel, linux-gpio, devicetree, imx,
linux-arm-kernel, Haibo Chen
On Thu, Mar 5, 2026 at 6:38 PM Frank Li <Frank.li@nxp.com> wrote:
> init state is before probe if exist, which not fit this case because:
> - on board mux need be enabled before probe, some device need communicate
> with periphal at probe, such as SD card. SD data/cmd line must be ready
> to scan SD, similar case for all mtd devices.
>
> if add new state "release", when the pinctrl may switch between default,
> sleep, ... , "release" have to switch between switch state, such as default
> -> sleep have to change to default->release->sleep.
>
> mux device also have idle state, which map to pinctrl's "init" or "sleep"
> state.
>
> The key difference should be
> - pinctrl can uncondtional switch state.
> - mux frame have to use pair mux_control_(de)select() to switch state.
>
> So, I think just need a hook in pinctrl system to call mux_control_deselect()
> when switch state and release resource.
The problem is that this becomes essentially a revert of:
commit 2243a87d90b42eb38bc281957df3e57c712b5e56
"pinctrl: avoid duplicated calling enable_pinmux_setting for a pin"
from 2014.
.set_mux() used to be called .enable() and when .disable() was
deleted in this commit, only .enable() remained and that was later
renamed to .set_mux().
By essentially adding back the .disable() callback under a new
name, you risk to re-introduce the problem solved by this
committ.
So you need to analyse that committ log a bit, reference it
and explain why you are *not* re-introducing this problem by
adding back the callback.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 5/6] arm64: dts: imx8mp-evk: add board-level mux for CAN2 and MICFIL
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
` (3 preceding siblings ...)
2026-02-25 23:55 ` [PATCH v2 4/6] pinctrl: add generic board-level pinctrl driver using mux framework Frank Li
@ 2026-02-25 23:55 ` Frank Li
2026-02-25 23:55 ` [PATCH v2 6/6] arm64: dts: imx8mp-evk: add flexcan2 overlay file Frank Li
5 siblings, 0 replies; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
The board integrates an on-board mux to route shared signals to either
CAN2 or PDM (MICFIL). The mux is controlled by a GPIO.
Add a pinctrl-based multiplexer node to describe this routing and ensure
proper probe ordering of the dependent devices.
Previously, MICFIL operation implicitly depended on the default level of
PCA6416 GPIO3. After adding the pinctrl-multiplexer, make the dependency
explicit.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2
- update commit message to show why need update PDM MICIFL.
---
arch/arm64/boot/dts/freescale/imx8mp-evk.dts | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8mp-evk.dts b/arch/arm64/boot/dts/freescale/imx8mp-evk.dts
index b256be710ea1281465f5cecc7a3b979f2c068e43..1341ee27239fd41a26117adc9023524ce50420a7 100644
--- a/arch/arm64/boot/dts/freescale/imx8mp-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mp-evk.dts
@@ -50,6 +50,25 @@ status {
};
};
+ can_mux: mux-controller-0 {
+ compatible = "gpio-mux";
+ #mux-control-cells = <0>;
+ #mux-state-cells = <1>;
+ mux-gpios = <&pca6416 3 GPIO_ACTIVE_HIGH>;
+ };
+
+ can_mux_pinctrl: pinctrl-gpiomux {
+ compatible = "pinctrl-multiplexer";
+
+ can_fun: can-grp {
+ mux-states = <&can_mux 1>;
+ };
+
+ pdm_fun: pdm-grp {
+ mux-states = <&can_mux 0>;
+ };
+ };
+
memory@40000000 {
device_type = "memory";
reg = <0x0 0x40000000 0 0xc0000000>,
@@ -446,7 +465,7 @@ &flexcan1 {
&flexcan2 {
pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_flexcan2>;
+ pinctrl-0 = <&pinctrl_flexcan2>, <&can_fun>;
phys = <&flexcan_phy 1>;
status = "disabled";/* can2 pin conflict with pdm */
};
@@ -712,7 +731,7 @@ &lcdif3 {
&micfil {
#sound-dai-cells = <0>;
pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_pdm>;
+ pinctrl-0 = <&pinctrl_pdm>, <&pdm_fun>;
assigned-clocks = <&clk IMX8MP_CLK_PDM>;
assigned-clock-parents = <&clk IMX8MP_AUDIO_PLL1_OUT>;
assigned-clock-rates = <196608000>;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v2 6/6] arm64: dts: imx8mp-evk: add flexcan2 overlay file
2026-02-25 23:55 [PATCH v2 0/6] pinctrl: Add generic pinctrl for board-level mux chips Frank Li
` (4 preceding siblings ...)
2026-02-25 23:55 ` [PATCH v2 5/6] arm64: dts: imx8mp-evk: add board-level mux for CAN2 and MICFIL Frank Li
@ 2026-02-25 23:55 ` Frank Li
5 siblings, 0 replies; 18+ messages in thread
From: Frank Li @ 2026-02-25 23:55 UTC (permalink / raw)
To: Peter Rosin, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rafał Miłecki, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam
Cc: linux-kernel, linux-gpio, devicetree, imx, linux-arm-kernel,
Haibo Chen, Frank Li
Add flexcan2 overlay file, which enable flexcan2 node and disable micfil
node.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
arch/arm64/boot/dts/freescale/Makefile | 4 ++++
arch/arm64/boot/dts/freescale/imx8mp-evk-flexcan2.dtso | 15 +++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/Makefile b/arch/arm64/boot/dts/freescale/Makefile
index 700bab4d3e6001fe6cf460fcb09cfe57acc77e36..bd377191a68a6167d5f9a65184d19c789a4223ee 100644
--- a/arch/arm64/boot/dts/freescale/Makefile
+++ b/arch/arm64/boot/dts/freescale/Makefile
@@ -233,6 +233,10 @@ dtb-$(CONFIG_ARCH_MXC) += imx8mp-dhcom-pdk3.dtb
dtb-$(CONFIG_ARCH_MXC) += imx8mp-dhcom-picoitx.dtb
dtb-$(CONFIG_ARCH_MXC) += imx8mp-edm-g-wb.dtb
dtb-$(CONFIG_ARCH_MXC) += imx8mp-evk.dtb
+
+imx8mp-evk-flexcan2-dtbs += imx8mp-evk.dtb imx8mp-evk-flexcan2.dtbo
+dtb-$(CONFIG_ARCH_MXC) += imx8mp-evk-flexcan2.dtb
+
dtb-$(CONFIG_ARCH_MXC) += imx8mp-frdm.dtb
dtb-$(CONFIG_ARCH_MXC) += imx8mp-hummingboard-mate.dtb
dtb-$(CONFIG_ARCH_MXC) += imx8mp-hummingboard-pro.dtb
diff --git a/arch/arm64/boot/dts/freescale/imx8mp-evk-flexcan2.dtso b/arch/arm64/boot/dts/freescale/imx8mp-evk-flexcan2.dtso
new file mode 100644
index 0000000000000000000000000000000000000000..f7d2674c45f72353a20300300e98c8a1eba4a2a6
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx8mp-evk-flexcan2.dtso
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright 2026 NXP
+ */
+
+/dts-v1/;
+/plugin/;
+
+&flexcan2 {
+ status = "okay"; /* can2 pin conflict with pdm */
+};
+
+&micfil {
+ status = "disabled";
+};
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread