| // 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"); |