mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 04:37:32 -04:00
pinctrl: add generic board-level pinctrl driver using mux framework
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> Signed-off-by: Linus Walleij <linusw@kernel.org>
This commit is contained in:
@@ -272,6 +272,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_PINCTRL
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
184
drivers/pinctrl/pinctrl-generic-mux.c
Normal file
184
drivers/pinctrl/pinctrl-generic-mux.c
Normal file
@@ -0,0 +1,184 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Generic Pin Control Driver for Board-Level Mux Chips
|
||||
* Copyright 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 "pinconf.h"
|
||||
#include "pinmux.h"
|
||||
#include "pinctrl-utils.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;
|
||||
};
|
||||
|
||||
static int
|
||||
mux_pinmux_dt_node_to_map(struct pinctrl_dev *pctldev,
|
||||
struct device_node *np_config,
|
||||
struct pinctrl_map **maps, unsigned int *num_maps)
|
||||
{
|
||||
unsigned int num_reserved_maps = 0;
|
||||
struct mux_pin_function *function;
|
||||
const char **group_names;
|
||||
int ret;
|
||||
|
||||
function = devm_kzalloc(pctldev->dev, sizeof(*function), GFP_KERNEL);
|
||||
if (!function)
|
||||
return -ENOMEM;
|
||||
|
||||
group_names = devm_kcalloc(pctldev->dev, 1, sizeof(*group_names), GFP_KERNEL);
|
||||
if (!group_names)
|
||||
return -ENOMEM;
|
||||
|
||||
function->mux_state = devm_mux_state_get_from_np(pctldev->dev, NULL, np_config);
|
||||
if (IS_ERR(function->mux_state))
|
||||
return PTR_ERR(function->mux_state);
|
||||
|
||||
ret = pinctrl_generic_to_map(pctldev, np_config, np_config, maps,
|
||||
num_maps, &num_reserved_maps, group_names,
|
||||
0, &np_config->name, NULL, 0);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pinmux_generic_add_function(pctldev, np_config->name, group_names,
|
||||
1, function);
|
||||
if (ret < 0) {
|
||||
pinctrl_utils_free_map(pctldev, *maps, *num_maps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 = pinctrl_utils_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;
|
||||
|
||||
ret = mux_state_select(func->mux_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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");
|
||||
Reference in New Issue
Block a user