[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