[PATCH v4 3/9] pinctrl: airoha: add shared pinctrl code

David Lechner dlechner at baylibre.com
Thu May 7 00:31:00 CEST 2026


On 5/4/26 8:03 AM, Mikhail Kshevetskiy wrote:
> This patch introduce shared Airoha pinctrl code.
> 
> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
> ---
>  drivers/pinctrl/Kconfig                 |   1 +
>  drivers/pinctrl/Makefile                |   1 +
>  drivers/pinctrl/airoha/Kconfig          |  11 +
>  drivers/pinctrl/airoha/Makefile         |   3 +
>  drivers/pinctrl/airoha/airoha-common.h  | 512 ++++++++++++++
>  drivers/pinctrl/airoha/pinctrl-airoha.c | 843 ++++++++++++++++++++++++
>  6 files changed, 1371 insertions(+)
>  create mode 100644 drivers/pinctrl/airoha/Kconfig
>  create mode 100644 drivers/pinctrl/airoha/Makefile
>  create mode 100644 drivers/pinctrl/airoha/airoha-common.h
>  create mode 100644 drivers/pinctrl/airoha/pinctrl-airoha.c

I don't remember if I asked before...

Should we add a MAINTAINERS entry for this new directory?

> 
> diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
> index 578edbf8168..46a95a1ab6b 100644
> --- a/drivers/pinctrl/Kconfig
> +++ b/drivers/pinctrl/Kconfig
> @@ -405,6 +405,7 @@ config SPL_PINCTRL_ZYNQMP
>  
>  endif
>  
> +source "drivers/pinctrl/airoha/Kconfig"
>  source "drivers/pinctrl/broadcom/Kconfig"
>  source "drivers/pinctrl/exynos/Kconfig"
>  source "drivers/pinctrl/intel/Kconfig"
> diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
> index 29fb9b484d0..b03e838ab39 100644
> --- a/drivers/pinctrl/Makefile
> +++ b/drivers/pinctrl/Makefile
> @@ -24,6 +24,7 @@ obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl_pic32.o
>  obj-$(CONFIG_PINCTRL_EXYNOS)	+= exynos/
>  obj-$(CONFIG_PINCTRL_K210)	+= pinctrl-k210.o
>  obj-$(CONFIG_PINCTRL_MESON)	+= meson/
> +obj-$(CONFIG_PINCTRL_AIROHA)	+= airoha/

I still think this could be better placed. It looks like there is
at leaset some attempt to put these in alphabetical order.

>  obj-$(CONFIG_PINCTRL_MTK)	+= mediatek/
>  obj-$(CONFIG_PINCTRL_MSCC)	+= mscc/
>  obj-$(CONFIG_ARCH_MVEBU)	+= mvebu/
> diff --git a/drivers/pinctrl/airoha/Kconfig b/drivers/pinctrl/airoha/Kconfig
> new file mode 100644
> index 00000000000..eb87afbb374
> --- /dev/null
> +++ b/drivers/pinctrl/airoha/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config PINCTRL_AIROHA
> +	depends on ARCH_AIROHA
> +	select PINCTRL_FULL
> +	select PINCTRL_GENERIC
> +	select PINMUX
> +	select PINCONF
> +	select REGMAP
> +	select SYSCON
> +	bool
> diff --git a/drivers/pinctrl/airoha/Makefile b/drivers/pinctrl/airoha/Makefile
> new file mode 100644
> index 00000000000..a25b744dd7a
> --- /dev/null
> +++ b/drivers/pinctrl/airoha/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_PINCTRL_AIROHA)		+= pinctrl-airoha.o
> diff --git a/drivers/pinctrl/airoha/airoha-common.h b/drivers/pinctrl/airoha/airoha-common.h
> new file mode 100644
> index 00000000000..af4964bf25d
> --- /dev/null
> +++ b/drivers/pinctrl/airoha/airoha-common.h
> @@ -0,0 +1,512 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __AIROHA_COMMON_HEADER__
> +#define __AIROHA_COMMON_HEADER__
> +

...

> +static const u32 gpio_data_regs[] = {
> +	REG_GPIO_DATA,
> +	REG_GPIO_DATA1
> +};
> +
> +static const u32 gpio_out_regs[] = {
> +	REG_GPIO_OE,
> +	REG_GPIO_OE1
> +};
> +
> +static const u32 gpio_dir_regs[] = {
> +	REG_GPIO_CTRL,
> +	REG_GPIO_CTRL1,
> +	REG_GPIO_CTRL2,
> +	REG_GPIO_CTRL3
> +};
> +
> +static const u32 irq_status_regs[] = {
> +	REG_GPIO_INT,
> +	REG_GPIO_INT1
> +};
> +
> +static const u32 irq_level_regs[] = {
> +	REG_GPIO_INT_LEVEL,
> +	REG_GPIO_INT_LEVEL1,
> +	REG_GPIO_INT_LEVEL2,
> +	REG_GPIO_INT_LEVEL3
> +};
> +
> +static const u32 irq_edge_regs[] = {
> +	REG_GPIO_INT_EDGE,
> +	REG_GPIO_INT_EDGE1,
> +	REG_GPIO_INT_EDGE2,
> +	REG_GPIO_INT_EDGE3
> +};

Putting these arrays in the header isn't a good idea. Each compile
unit (C file) that includes this header will have them repeated. It
looks like they are only used in pinctrl-arioha.c anyway, so they
should should just be moved there.

> diff --git a/drivers/pinctrl/airoha/pinctrl-airoha.c b/drivers/pinctrl/airoha/pinctrl-airoha.c
> new file mode 100644
> index 00000000000..11830c44612
> --- /dev/null
> +++ b/drivers/pinctrl/airoha/pinctrl-airoha.c
> @@ -0,0 +1,843 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Author: Lorenzo Bianconi <lorenzo at kernel.org>
> + * Author: Benjamin Larsson <benjamin.larsson at genexis.eu>
> + * Author: Markus Gothe <markus.gothe at genexis.eu>
> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
> + */
> +#include <dm.h>
> +#include <dm/device_compat.h>
> +#include <dm/device-internal.h>
> +#include <dm/lists.h>
> +#include <dm/ofnode.h>
> +#include <asm-generic/gpio.h>
> +#include <dt-bindings/pinctrl/mt65xx.h>
> +#include <regmap.h>
> +
> +#include <syscon.h>
> +#include <asm/arch/scu-regmap.h>

Not sure I understand the organization of the includes
above. Why the blank line between regmap and syscon? Why
not everything in alphabetical order?

> +
> +#include "airoha-common.h"
> +
> +
> +static int pin_in_group(unsigned int pin, const struct pingroup *grp)
> +{
> +	for (int i = 0; i < grp->npins; i++) {
> +		if (grp->pins[i] == pin)
> +			return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pin_to_gpio(struct airoha_pinctrl *pinctrl, unsigned int pin)
> +{
> +	struct airoha_pinctrl_match_data *data = pinctrl->data;
> +
> +	if ((pin < data->gpio_offs) ||
> +	    (pin > data->gpio_offs + data->gpio_pin_cnt))

Should this be >= for the upper bound check?

Also has unnecessary ().

> +		return -EINVAL;
> +
> +	return pin - data->gpio_offs;
> +}
> +



> +static int airoha_pinctrl_get_conf(struct airoha_pinctrl *pinctrl,
> +				   enum airoha_pinctrl_confs_type conf_type,
> +				   int pin, u32 *val)
> +{
> +	const struct airoha_pinctrl_confs_info *confs_info;
> +	const struct airoha_pinctrl_reg *reg;
> +
> +	confs_info = &pinctrl->data->confs_info[conf_type];
> +
> +	reg = airoha_pinctrl_get_conf_reg(confs_info->confs,
> +					  confs_info->num_confs,
> +					  pin);
> +	if (!reg)
> +		return -EINVAL;
> +
> +	if (regmap_read(pinctrl->chip_scu, reg->offset, val))
> +		return -EINVAL;
> +
> +	*val = (*val & reg->mask) >> __ffs(reg->mask);

field_get()?

> +
> +	return 0;
> +}
> +
> +static int airoha_pinctrl_set_conf(struct airoha_pinctrl *pinctrl,
> +				   enum airoha_pinctrl_confs_type conf_type,
> +				   int pin, u32 val)
> +{
> +	const struct airoha_pinctrl_confs_info *confs_info;
> +	const struct airoha_pinctrl_reg *reg = NULL;
> +
> +	confs_info = &pinctrl->data->confs_info[conf_type];
> +
> +	reg = airoha_pinctrl_get_conf_reg(confs_info->confs,
> +					  confs_info->num_confs,
> +					  pin);
> +	if (!reg)
> +		return -EINVAL;
> +
> +	if (regmap_update_bits(pinctrl->chip_scu, reg->offset, reg->mask,
> +			       val << __ffs(reg->mask)))

field_prep()?

> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int airoha_pinconf_get_direction(struct airoha_pinctrl *pinctrl, u32 p)
> +{
> +	int gpio;
> +
> +	gpio = pin_to_gpio(pinctrl, p);
> +	if (gpio < 0)
> +		return gpio;
> +
> +	return airoha_gpio_get_direction(pinctrl, gpio);
> +}
> +
> +static int airoha_pinconf_get(struct airoha_pinctrl *pinctrl,
> +			      unsigned int pin, unsigned long *config)
> +{
> +	enum pin_config_param param = pinconf_to_config_param(*config);
> +	u32 arg;
> +
> +	switch (param) {
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +	case PIN_CONFIG_BIAS_DISABLE:
> +	case PIN_CONFIG_BIAS_PULL_UP: {
> +		u32 pull_up, pull_down;
> +
> +		if (airoha_pinctrl_get_pullup_conf(pinctrl, pin, &pull_up) ||
> +		    airoha_pinctrl_get_pulldown_conf(pinctrl, pin, &pull_down))
> +			return -EINVAL;
> +
> +		if (param == PIN_CONFIG_BIAS_PULL_UP &&
> +		    !(pull_up && !pull_down))
> +			return -EINVAL;
> +		else if (param == PIN_CONFIG_BIAS_PULL_DOWN &&
> +			 !(pull_down && !pull_up))
> +			return -EINVAL;
> +		else if (pull_up || pull_down)
> +			return -EINVAL;
> +
> +		arg = 1;
> +		break;
> +	}
> +	case PIN_CONFIG_DRIVE_STRENGTH: {
> +		u32 e2, e4;
> +
> +		if (airoha_pinctrl_get_drive_e2_conf(pinctrl, pin, &e2) ||
> +		    airoha_pinctrl_get_drive_e4_conf(pinctrl, pin, &e4))
> +			return -EINVAL;
> +
> +		arg = e4 << 1 | e2;
> +		break;
> +	}
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +		if (airoha_pinctrl_get_pcie_rst_od_conf(pinctrl, pin, &arg))
> +			return -EINVAL;
> +		break;
> +	case PIN_CONFIG_OUTPUT_ENABLE:
> +	case PIN_CONFIG_INPUT_ENABLE:
> +		arg = airoha_pinconf_get_direction(pinctrl, pin);
> +		if (arg != param)

How does this work? It looks like airoha_pinconf_get_direction()
returns enum gpio_func_t or negative error code.

Should we check for error first before comparing?

Do we need some kind of conversion between enum gpio_func_t and
enum pin_config_param in airoha_pinconf_get_direction()?

> +			return -EINVAL;
> +
> +		arg = 1;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	*config = pinconf_to_config_packed(param, arg);
> +
> +	return 0;
> +}
> +
> +static int airoha_pinconf_set_pin_value(struct airoha_pinctrl *pinctrl,
> +					unsigned int p, bool value)
> +{
> +	int gpio;
> +
> +	gpio = pin_to_gpio(pinctrl, p);
> +	if (gpio < 0)
> +		return gpio;
> +
> +	return airoha_gpio_set(pinctrl, gpio, value);
> +}
> +
> +static int airoha_pinconf_set(struct airoha_pinctrl *pinctrl,
> +			      unsigned int pin, unsigned long *configs,
> +			      unsigned int num_configs)
> +{
> +	int i;
> +
> +	for (i = 0; i < num_configs; i++) {
> +		u32 param = pinconf_to_config_param(configs[i]);
> +		u32 arg = pinconf_to_config_argument(configs[i]);
> +

In all of the cases below, I would expect that we should be checking
the return value for error. Or add a comment on why it is safe to ignore
the return value if that is the case.

> +		switch (param) {
> +		case PIN_CONFIG_BIAS_DISABLE:
> +			airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 0);
> +			airoha_pinctrl_set_pullup_conf(pinctrl, pin, 0);
> +			break;
> +		case PIN_CONFIG_BIAS_PULL_UP:
> +			airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 0);
> +			airoha_pinctrl_set_pullup_conf(pinctrl, pin, 1);
> +			break;
> +		case PIN_CONFIG_BIAS_PULL_DOWN:
> +			airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 1);
> +			airoha_pinctrl_set_pullup_conf(pinctrl, pin, 0);
> +			break;
> +		case PIN_CONFIG_DRIVE_STRENGTH: {
> +			u32 e2 = 0, e4 = 0;
> +
> +			switch (arg) {
> +			case MTK_DRIVE_2mA:
> +				break;
> +			case MTK_DRIVE_4mA:
> +				e2 = 1;
> +				break;
> +			case MTK_DRIVE_6mA:
> +				e4 = 1;
> +				break;
> +			case MTK_DRIVE_8mA:
> +				e2 = 1;
> +				e4 = 1;
> +				break;
> +			default:
> +				return -EINVAL;
> +			}
> +
> +			airoha_pinctrl_set_drive_e2_conf(pinctrl, pin, e2);
> +			airoha_pinctrl_set_drive_e4_conf(pinctrl, pin, e4);
> +			break;
> +		}
> +		case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +			airoha_pinctrl_set_pcie_rst_od_conf(pinctrl, pin, !!arg);
> +			break;
> +		case PIN_CONFIG_OUTPUT_ENABLE:
> +		case PIN_CONFIG_INPUT_ENABLE:
> +		case PIN_CONFIG_OUTPUT: {
> +			bool input = param == PIN_CONFIG_INPUT_ENABLE;
> +			int err;
> +
> +			err = airoha_pinmux_set_direction(pinctrl, pin, input);
> +			if (err)
> +				return err;
> +
> +			if (param == PIN_CONFIG_OUTPUT) {
> +				err = airoha_pinconf_set_pin_value(pinctrl,
> +								   pin, !!arg);
> +				if (err)
> +					return err;
> +			}
> +
> +			break;
> +		}
> +		default:
> +			return -EOPNOTSUPP;
> +		}
> +	}
> +
> +	return 0;
> +}
> +


> +/**************************
> + * pinctrl driver interface
> + **************************/
> +static int airoha_get_pins_count(struct udevice *dev)
> +{
> +	struct airoha_pinctrl *pinctrl = dev_get_priv(dev);
> +
> +	return pinctrl->data->num_pins;
> +}
> +
> +static const char *airoha_get_pin_name(struct udevice *dev, unsigned selector)

Inconsitent use of unsigned. Preferred is unsigned int. 

> +{
> +	struct airoha_pinctrl *pinctrl = dev_get_priv(dev);
> +
> +	return pinctrl->data->pins[selector].name;
> +}
> +


> +static int airoha_pinconf_set_handler(struct udevice *dev, unsigned pin_selector,
> +				      unsigned param, unsigned argument)
> +{
> +	struct airoha_pinctrl *pinctrl = dev_get_priv(dev);
> +	unsigned long configs[1] = { pinconf_to_config_packed(param, argument) };
> +	unsigned int pin = pinctrl->data->pins[pin_selector].number;
> +
> +	dev_dbg(dev, "enabling %s=%d property for pin %s\n",
> +		airoha_pinconf_params[param].property, argument,

This will result in out of bounds access. param is not the index but rather
a field in the struct in the array.

Easiset would be to just print numeric value of param, otherwise we need a
lookup function.

> +		airoha_get_pin_name(dev, pin_selector));
> +
> +	return airoha_pinconf_set(pinctrl, pin, configs,
> +				  ARRAY_SIZE(configs));
> +}
> +
> +static int airoha_pinconf_group_set_handler(struct udevice *dev, unsigned group_selector,
> +					    unsigned param, unsigned argument)
> +{
> +	struct airoha_pinctrl *pinctrl = dev_get_priv(dev);
> +	unsigned long configs[1] = { pinconf_to_config_packed(param, argument) };
> +
> +	dev_dbg(dev, "enabling %s=%d property for pin group %s\n",
> +		airoha_pinconf_params[param].property, argument,

ditto

> +		airoha_get_group_name(dev, group_selector));
> +
> +	return airoha_pinconf_group_set(pinctrl, group_selector,
> +					configs, ARRAY_SIZE(configs));
> +}
> +
> +static int airoha_get_pin_muxing(struct udevice *dev, unsigned int selector,
> +				 char *buf, int size)
> +{
> +	struct airoha_pinctrl *pinctrl = dev_get_priv(dev);
> +	struct airoha_pinctrl_match_data *data = pinctrl->data;
> +	const char *name, *type;
> +	int ret, gpio, found = 0;
> +	unsigned long config;
> +	unsigned int param, pin;
> +	u32 val;
> +
> +	pin = data->pins[selector].number;
> +	for (int i = 0; i < data->num_grps; i++) {
> +		if (!pin_in_group(pin, &data->grps[i]))
> +			continue;
> +
> +		name = data->grps[i].name;
> +		for (int j = 0; j < data->num_funcs; j++) {
> +			if (!func_grp_active(pinctrl, &data->funcs[j], name))
> +				continue;
> +
> +			ret = snprintf(buf, size, "%s(%s)",
> +				       data->funcs[j].desc.name, name);

Should use scnprintf(). snprintf() returns the number that would have been
written, which can be larger than number actually written.

Same applies elsewhere in this patch.



More information about the U-Boot mailing list