]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pinctrl: add generic functions + pins mapper
authorConor Dooley <conor.dooley@microchip.com>
Tue, 20 Jan 2026 18:15:40 +0000 (18:15 +0000)
committerLinus Walleij <linusw@kernel.org>
Wed, 21 Jan 2026 12:13:37 +0000 (13:13 +0100)
Add a generic function to allow creation of groups and functions at
runtime based on devicetree content, before setting up mux mappings.
It works similarly to pinconf_generic_dt_node_to_map(), and
therefore parses pinconf properties and maps those too, allowing it
to be used as the dt_node_to_map member of the pinctrl_ops struct.

Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Linus Walleij <linusw@kernel.org>
drivers/pinctrl/Kconfig
drivers/pinctrl/Makefile
drivers/pinctrl/pinconf.h
drivers/pinctrl/pinctrl-generic.c [new file with mode: 0644]

index d2a414450c16d33334b2774df20d7df0e059a685..6cc5e214f4f3a94a09d8dbe8a91844290dba8805 100644 (file)
@@ -25,6 +25,12 @@ config GENERIC_PINCONF
        bool
        select PINCONF
 
+config GENERIC_PINCTRL
+       bool
+       depends on GENERIC_PINCONF
+       depends on GENERIC_PINCTRL_GROUPS
+       depends on GENERIC_PINMUX_FUNCTIONS
+
 config DEBUG_PINCTRL
        bool "Debug PINCTRL calls"
        depends on DEBUG_KERNEL
index 05737b1afec99da691a490b3f40e822ccbd92f11..f7d5d5f76d0c8becc0aa1d77c68b6ced924ea264 100644 (file)
@@ -7,6 +7,7 @@ obj-y                           += core.o pinctrl-utils.o
 obj-$(CONFIG_PINMUX)           += pinmux.o
 obj-$(CONFIG_PINCONF)          += pinconf.o
 obj-$(CONFIG_GENERIC_PINCONF)  += pinconf-generic.o
+obj-$(CONFIG_GENERIC_PINCTRL)  += pinctrl-generic.o
 obj-$(CONFIG_OF)               += devicetree.o
 
 obj-$(CONFIG_PINCTRL_AMD)      += pinctrl-amd.o
index e1ae716105261a940644da038dd1ef1481a08bb9..2880adef476e68950ffdd540ea42cdee6a16ec27 100644 (file)
@@ -160,3 +160,19 @@ pinconf_generic_parse_dt_pinmux(struct device_node *np, struct device *dev,
        return -ENOTSUPP;
 }
 #endif
+
+#if defined(CONFIG_GENERIC_PINCTRL) && defined (CONFIG_OF)
+int pinctrl_generic_pins_function_dt_node_to_map(struct pinctrl_dev *pctldev,
+                                                struct device_node *np,
+                                                struct pinctrl_map **maps,
+                                                unsigned int *num_maps);
+#else
+static inline int
+pinctrl_generic_pins_function_dt_node_to_map(struct pinctrl_dev *pctldev,
+                                            struct device_node *np,
+                                            struct pinctrl_map **maps,
+                                            unsigned int *num_maps)
+{
+       return -ENOTSUPP;
+}
+#endif
diff --git a/drivers/pinctrl/pinctrl-generic.c b/drivers/pinctrl/pinctrl-generic.c
new file mode 100644 (file)
index 0000000..efb39c6
--- /dev/null
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "generic pinconfig core: " fmt
+
+#include <linux/array_size.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+
+#include "core.h"
+#include "pinconf.h"
+#include "pinctrl-utils.h"
+#include "pinmux.h"
+
+static int pinctrl_generic_pins_function_dt_subnode_to_map(struct pinctrl_dev *pctldev,
+                                                          struct device_node *parent,
+                                                          struct device_node *np,
+                                                          struct pinctrl_map **maps,
+                                                          unsigned int *num_maps,
+                                                          unsigned int *num_reserved_maps,
+                                                          const char **group_names,
+                                                          unsigned int ngroups)
+{
+       struct device *dev = pctldev->dev;
+       const char **functions;
+       const char *group_name;
+       unsigned long *configs;
+       unsigned int num_configs, pin, *pins;
+       int npins, ret, reserve = 1;
+
+       npins = of_property_count_u32_elems(np, "pins");
+
+       if (npins < 1) {
+               dev_err(dev, "invalid pinctrl group %pOFn.%pOFn %d\n",
+                       parent, np, npins);
+               return npins;
+       }
+
+       group_name = devm_kasprintf(dev, GFP_KERNEL, "%pOFn.%pOFn", parent, np);
+       if (!group_name)
+               return -ENOMEM;
+
+       group_names[ngroups] = group_name;
+
+       pins = devm_kcalloc(dev, npins, sizeof(*pins), GFP_KERNEL);
+       if (!pins)
+               return -ENOMEM;
+
+       functions = devm_kcalloc(dev, npins, sizeof(*functions), GFP_KERNEL);
+       if (!functions)
+               return -ENOMEM;
+
+       for (int i = 0; i < npins; i++) {
+               ret = of_property_read_u32_index(np, "pins", i, &pin);
+               if (ret)
+                       return ret;
+
+               pins[i] = pin;
+
+               ret = of_property_read_string(np, "function", &functions[i]);
+               if (ret)
+                       return ret;
+       }
+
+       ret = pinctrl_utils_reserve_map(pctldev, maps, num_reserved_maps, num_maps, reserve);
+       if (ret)
+               return ret;
+
+       ret = pinctrl_utils_add_map_mux(pctldev, maps, num_reserved_maps, num_maps, group_name,
+                                       parent->name);
+       if (ret < 0)
+               return ret;
+
+       ret = pinctrl_generic_add_group(pctldev, group_name, pins, npins, functions);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "failed to add group %s: %d\n",
+                                    group_name, ret);
+
+       ret = pinconf_generic_parse_dt_config(np, pctldev, &configs, &num_configs);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to parse pin config of group %s\n",
+                       group_name);
+
+       if (num_configs == 0)
+               return 0;
+
+       ret = pinctrl_utils_reserve_map(pctldev, maps, num_reserved_maps, num_maps, reserve);
+       if (ret)
+               return ret;
+
+       ret = pinctrl_utils_add_map_configs(pctldev, maps, num_reserved_maps, num_maps, group_name,
+                                           configs,
+                       num_configs, PIN_MAP_TYPE_CONFIGS_GROUP);
+       kfree(configs);
+       if (ret)
+               return ret;
+
+       return 0;
+};
+
+/*
+ * For platforms that do not define groups or functions in the driver, but
+ * instead use the devicetree to describe them. This function will, unlike
+ * pinconf_generic_dt_node_to_map() etc which rely on driver defined groups
+ * and functions, create them in addition to parsing pinconf properties and
+ * adding mappings.
+ */
+int pinctrl_generic_pins_function_dt_node_to_map(struct pinctrl_dev *pctldev,
+                                                struct device_node *np,
+                                                struct pinctrl_map **maps,
+                                                unsigned int *num_maps)
+{
+       struct device *dev = pctldev->dev;
+       struct device_node *child_np;
+       const char **group_names;
+       unsigned int num_reserved_maps = 0;
+       int ngroups = 0;
+       int ret;
+
+       *maps = NULL;
+       *num_maps = 0;
+
+       /*
+        * Check if this is actually the pins node, or a parent containing
+        * multiple pins nodes.
+        */
+       if (!of_property_present(np, "pins"))
+               goto parent;
+
+       group_names = devm_kcalloc(dev, 1, sizeof(*group_names), GFP_KERNEL);
+       if (!group_names)
+               return -ENOMEM;
+
+       ret = pinctrl_generic_pins_function_dt_subnode_to_map(pctldev, np, np,
+                                                             maps, num_maps,
+                                                             &num_reserved_maps,
+                                                             group_names,
+                                                             ngroups);
+       if (ret) {
+               pinctrl_utils_free_map(pctldev, *maps, *num_maps);
+               return dev_err_probe(dev, ret, "error figuring out mappings for %s\n", np->name);
+       }
+
+       ret = pinmux_generic_add_function(pctldev, np->name, group_names, 1, NULL);
+       if (ret < 0) {
+               pinctrl_utils_free_map(pctldev, *maps, *num_maps);
+               return dev_err_probe(dev, ret, "error adding function %s\n", np->name);
+       }
+
+       return 0;
+
+parent:
+       for_each_available_child_of_node(np, child_np)
+               ngroups += 1;
+
+       group_names = devm_kcalloc(dev, ngroups, sizeof(*group_names), GFP_KERNEL);
+       if (!group_names)
+               return -ENOMEM;
+
+       ngroups = 0;
+       for_each_available_child_of_node_scoped(np, child_np) {
+               ret = pinctrl_generic_pins_function_dt_subnode_to_map(pctldev, np, child_np,
+                                                                     maps, num_maps,
+                                                                     &num_reserved_maps,
+                                                                     group_names,
+                                                                     ngroups);
+               if (ret) {
+                       pinctrl_utils_free_map(pctldev, *maps, *num_maps);
+                       return dev_err_probe(dev, ret, "error figuring out mappings for %s\n",
+                                            np->name);
+               }
+
+               ngroups++;
+       }
+
+       ret = pinmux_generic_add_function(pctldev, np->name, group_names, ngroups, NULL);
+       if (ret < 0) {
+               pinctrl_utils_free_map(pctldev, *maps, *num_maps);
+               return dev_err_probe(dev, ret, "error adding function %s\n", np->name);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pinctrl_generic_pins_function_dt_node_to_map);