This adds GPIO support to the SCMI pin controller driver. It's an RFC
patch because I'm not really sure how these are used and so I don't
know how they should be configured via devicetree. I've labeled the
places where I think devicetree configuration would go with a FIXME.
This driver was based on work from Takahiro AKASHI.
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
---
drivers/pinctrl/core.c | 8 +-
drivers/pinctrl/pinctrl-scmi.c | 206 ++++++++++++++++++++++++++++++++-
2 files changed, 207 insertions(+), 7 deletions(-)
diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
index bbcc6881b119..91882c68bcd5 100644
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -947,7 +947,6 @@ EXPORT_SYMBOL_GPL(pinctrl_gpio_set_config);
int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned long *config)
{
struct pinctrl_gpio_range *range;
- const struct pinconf_ops *ops;
struct pinctrl_dev *pctldev;
int ret, pin;
@@ -955,19 +954,16 @@ int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned
if (ret)
return ret;
- ops = pctldev->desc->confops;
- if (!ops || !ops->pin_config_get)
- return -EINVAL;
-
mutex_lock(&pctldev->mutex);
pin = gpio_to_pin(range, gc, offset);
- ret = ops->pin_config_get(pctldev, pin, config);
+ ret = pin_config_get_for_pin(pctldev, pin, config);
mutex_unlock(&pctldev->mutex);
if (ret)
return ret;
*config = pinconf_to_config_argument(*config);
+
return 0;
}
EXPORT_SYMBOL_GPL(pinctrl_gpio_get_config);
diff --git a/drivers/pinctrl/pinctrl-scmi.c b/drivers/pinctrl/pinctrl-scmi.c
index fba0a3a2fc10..9a947ced0df7 100644
--- a/drivers/pinctrl/pinctrl-scmi.c
+++ b/drivers/pinctrl/pinctrl-scmi.c
@@ -6,6 +6,7 @@
* Copyright 2024 NXP
*/
+#include <linux/bitmap.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
@@ -16,6 +17,9 @@
#include <linux/slab.h>
#include <linux/types.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
@@ -42,6 +46,7 @@ struct scmi_pinctrl {
unsigned int nr_functions;
struct pinctrl_pin_desc *pins;
unsigned int nr_pins;
+ struct gpio_chip *gc;
};
static int pinctrl_scmi_get_groups_count(struct pinctrl_dev *pctldev)
@@ -505,6 +510,197 @@ static int pinctrl_scmi_get_pins(struct scmi_pinctrl *pmx,
return 0;
}
+static int pinctrl_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ unsigned long config;
+ bool in, out;
+ int ret;
+
+ config = PIN_CONFIG_INPUT_ENABLE;
+ ret = pinctrl_gpio_get_config(gc, offset, &config);
+ if (ret)
+ return ret;
+ in = config;
+
+ config = PIN_CONFIG_OUTPUT_ENABLE;
+ ret = pinctrl_gpio_get_config(gc, offset, &config);
+ if (ret)
+ return ret;
+ out = config;
+
+ /* Consistency check - in theory both can be enabled! */
+ if (in && !out)
+ return GPIO_LINE_DIRECTION_IN;
+ if (!in && out)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return -EINVAL;
+}
+
+static int pinctrl_gpio_direction_output_wrapper(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ return pinctrl_gpio_direction_output(gc, offset);
+}
+
+static int pinctrl_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ unsigned long config;
+ int ret;
+
+ config = PIN_CONFIG_INPUT_VALUE;
+ ret = pinctrl_gpio_get_config(gc, offset, &config);
+ if (ret)
+ return ret;
+
+ return config;
+}
+
+static void pinctrl_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ unsigned long config;
+
+ config = PIN_CONF_PACKED(PIN_CONFIG_OUTPUT, val);
+ pinctrl_gpio_set_config(gc, offset, config);
+}
+
+static int pinctrl_gc_to_func(struct gpio_chip *gc)
+{
+ struct scmi_pinctrl *pmx = gpiochip_get_data(gc);
+
+ return (gc - pmx->gc);
+}
+
+static int gpio_add_pin_ranges(struct gpio_chip *gc)
+{
+ struct scmi_pinctrl *pmx = gpiochip_get_data(gc);
+ const char * const *p_groups;
+ unsigned int n_groups;
+ int func = pinctrl_gc_to_func(gc);
+ const unsigned int *pins;
+ unsigned int n_pins;
+ int offset = 0;
+ int group;
+ int ret;
+
+ ret = pmx->pctl_desc.pmxops->get_function_groups(pmx->pctldev, func, &p_groups, &n_groups);
+ if (ret)
+ return ret;
+
+ // FIXME: fix the correct group from the device tree
+ for (group = 0; group < n_groups; group++) {
+ ret = pinctrl_get_group_pins(pmx->pctldev, p_groups[group], &pins, &n_pins);
+ if (ret)
+ return ret;
+
+ ret = gpiochip_add_pingroup_range(gc, pmx->pctldev, offset, p_groups[group]);
+ if (ret)
+ return ret;
+
+ offset += n_pins;
+ }
+
+ return 0;
+}
+
+static int get_nr_pins_in_function(struct scmi_pinctrl *pmx, int func)
+{
+ const char * const *pin_groups;
+ unsigned int n_groups;
+ const unsigned int *pins;
+ unsigned int n_pins;
+ int total = 0;
+ int i, ret;
+
+ // FIXME: get the correct number of gc.ngpio
+ // Find the right group from the device tree
+ ret = pmx->pctl_desc.pmxops->get_function_groups(pmx->pctldev, func, &pin_groups, &n_groups);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < n_groups; i++) {
+ ret = pinctrl_get_group_pins(pmx->pctldev, pin_groups[i], &pins, &n_pins);
+ if (ret)
+ return ret;
+ total += n_pins;
+ }
+
+ return total;
+}
+
+static int register_scmi_pinctrl_gpio_handler(struct device *dev, struct scmi_pinctrl *pmx)
+{
+ struct fwnode_handle *gpio = NULL;
+ int ret, i;
+
+ gpio = fwnode_get_named_child_node(dev->fwnode, "gpio");
+ if (!gpio)
+ return 0;
+
+ pmx->gc = devm_kcalloc(dev, pmx->nr_functions, sizeof(*pmx->gc), GFP_KERNEL);
+ if (!pmx->gc)
+ return -ENOMEM;
+
+ for (i = 0; i < pmx->nr_functions; i++) {
+ const char *fn_name;
+
+ ret = pinctrl_ops->is_gpio(pmx->ph, i, FUNCTION_TYPE);
+ if (ret < 0)
+ return ret;
+ if (ret == false)
+ continue;
+
+ ret = pinctrl_ops->name_get(pmx->ph, i, FUNCTION_TYPE, &fn_name);
+ if (ret)
+ return ret;
+
+ pmx->gc[i].label = devm_kasprintf(dev, GFP_KERNEL, "%s", fn_name);
+ if (!pmx->gc[i].label)
+ return -ENOMEM;
+
+ pmx->gc[i].owner = THIS_MODULE;
+ pmx->gc[i].get = pinctrl_gpio_get;
+ pmx->gc[i].set = pinctrl_gpio_set;
+ pmx->gc[i].get_direction = pinctrl_gpio_get_direction;
+ pmx->gc[i].direction_input = pinctrl_gpio_direction_input;
+ pmx->gc[i].direction_output = pinctrl_gpio_direction_output_wrapper;
+ pmx->gc[i].add_pin_ranges = gpio_add_pin_ranges;
+
+ // FIXME: verify that this is correct
+ pmx->gc[i].can_sleep = true;
+
+ ret = get_nr_pins_in_function(pmx, i);
+ if (ret < 0)
+ return ret;
+ pmx->gc[i].ngpio = ret;
+
+ pmx->gc[i].parent = dev;
+ pmx->gc[i].base = -1;
+ }
+
+ return 0;
+}
+
+static int scmi_gpiochip_add_data(struct device *dev, struct scmi_pinctrl *pmx)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < pmx->nr_functions; i++) {
+ ret = pinctrl_ops->is_gpio(pmx->ph, i, FUNCTION_TYPE);
+ if (ret < 0)
+ return ret;
+ if (ret == false)
+ continue;
+
+ ret = devm_gpiochip_add_data(dev, &pmx->gc[i], pmx);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const char * const scmi_pinctrl_blocklist[] = {
"fsl,imx95",
"fsl,imx94",
@@ -559,7 +755,15 @@ static int scmi_pinctrl_probe(struct scmi_device *sdev)
if (!pmx->functions)
return -ENOMEM;
- return pinctrl_enable(pmx->pctldev);
+ ret = register_scmi_pinctrl_gpio_handler(dev, pmx);
+ if (ret)
+ return ret;
+
+ ret = pinctrl_enable(pmx->pctldev);
+ if (ret)
+ return ret;
+
+ return scmi_gpiochip_add_data(dev, pmx);
}
static const struct scmi_device_id scmi_id_table[] = {
--
2.47.2
On Sun, Jul 20, 2025 at 9:39 PM Dan Carpenter <dan.carpenter@linaro.org> wrote: > This adds GPIO support to the SCMI pin controller driver. It's an RFC > patch because I'm not really sure how these are used and so I don't > know how they should be configured via devicetree. I've labeled the > places where I think devicetree configuration would go with a FIXME. > > This driver was based on work from Takahiro AKASHI. > > Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org> (...) > @@ -955,19 +954,16 @@ int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned > if (ret) > return ret; > > - ops = pctldev->desc->confops; > - if (!ops || !ops->pin_config_get) > - return -EINVAL; > - > mutex_lock(&pctldev->mutex); > pin = gpio_to_pin(range, gc, offset); > - ret = ops->pin_config_get(pctldev, pin, config); > + ret = pin_config_get_for_pin(pctldev, pin, config); > mutex_unlock(&pctldev->mutex); > > if (ret) > return ret; > > *config = pinconf_to_config_argument(*config); > + > return 0; > } Looks reasonable. > @@ -42,6 +46,7 @@ struct scmi_pinctrl { > unsigned int nr_functions; > struct pinctrl_pin_desc *pins; > unsigned int nr_pins; > + struct gpio_chip *gc; This being a pointer is slightly confusing. And looking below I see why: > + gpio = fwnode_get_named_child_node(dev->fwnode, "gpio"); > + if (!gpio) > + return 0; > + > + pmx->gc = devm_kcalloc(dev, pmx->nr_functions, sizeof(*pmx->gc), GFP_KERNEL); > + if (!pmx->gc) > + return -ENOMEM; So this needs a comment to what is actually going on here. To me it looks like the code is instantiating a struct gpio_chip for each function on the pin mux. That feels wrong: instead we should probably instantiate *one* gpio_chip for the whole pinmux and then create individual gpio lines for each pin that can work as a GPIO. > + for (i = 0; i < pmx->nr_functions; i++) { > + const char *fn_name; > + > + ret = pinctrl_ops->is_gpio(pmx->ph, i, FUNCTION_TYPE); > + if (ret < 0) > + return ret; So we are only looking for functions that are of GPIO type. > + if (ret == false) > + continue; > + > + ret = pinctrl_ops->name_get(pmx->ph, i, FUNCTION_TYPE, &fn_name); > + if (ret) > + return ret; > + > + pmx->gc[i].label = devm_kasprintf(dev, GFP_KERNEL, "%s", fn_name); > + if (!pmx->gc[i].label) > + return -ENOMEM; > + > + pmx->gc[i].owner = THIS_MODULE; > + pmx->gc[i].get = pinctrl_gpio_get; > + pmx->gc[i].set = pinctrl_gpio_set; > + pmx->gc[i].get_direction = pinctrl_gpio_get_direction; > + pmx->gc[i].direction_input = pinctrl_gpio_direction_input; > + pmx->gc[i].direction_output = pinctrl_gpio_direction_output_wrapper; > + pmx->gc[i].add_pin_ranges = gpio_add_pin_ranges; > + > + // FIXME: verify that this is correct > + pmx->gc[i].can_sleep = true; > + > + ret = get_nr_pins_in_function(pmx, i); > + if (ret < 0) > + return ret; > + pmx->gc[i].ngpio = ret; Please put a print here and see how many pins there are usually in the function here. In my mind it should always be 1 and if it is not 1 then something is wrong. We cannot instantiate n gpio_chips for a pin controller where n is the number of functions for GPIO, intead we need to instantiate one gpio_chip for the whole pin controller with ngpio set to the number of pins that exist on the pin controller and mask of any lines that cannot be used as GPIO from the chip using the gc->init_valid_mask() callback. I think you want to look at Bartosz series that introduce a GPIO function category and use this as a basis for your work, because I plan to merge this for v6.18: https://lore.kernel.org/linux-gpio/20250812-pinctrl-gpio-pinfuncs-v4-0-bb3906c55e64@linaro.org/T/#t If you can first patch the SCMI pin control driver to use pinmux_generic_add_pinfunction() based on Bartosz patch set and get to this: + .get_functions_count = pinmux_generic_get_function_count, + .get_function_name = pinmux_generic_get_function_name, + .get_function_groups = pinmux_generic_get_function_groups, Then it is also easier to figure out if a line is GPIO or not (the core will help you). I hope the conversion to Bartosz generics could be straightforward... It looks straightforward but there may be devil in the details :) After this you can use pinmux_generic_function_is_gpio() to check if a certain function is used for GPIO or not, I think in the SCMI case this is done with a simple strcmp(). Then you can use this to instantiate a gpio_chip for SCMI that will dynamically allow pins to be remuxed into GPIO and made available on the chip as we go. I'm sure Bartosz can help out a bit with some details! Yours, Linus Walleij
© 2016 - 2025 Red Hat, Inc.