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
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
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
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
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
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
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!
© 2016 - 2026 Red Hat, Inc.