gpio: scmi: Add gpio_scmi driver

This provides GPIO support over SCMI.  It is built on top of the
pinctrl-scmi driver.  A typical device tree entry might look like
this:

    gpio1 {
        compatible = "scmi-pinctrl-gpio";
        gpio-controller;
        #gpio-cells = <2>;
        ngpios = <10>;
        gpio-ranges = <&scmi_pinctrl 0 8 4>,
                      <&scmi_pinctrl 4 12 1>,
                      <&scmi_pinctrl 5 15 1>,
                      <&scmi_pinctrl 6 17 4>;
        pinctrl-names = "default";
        pinctrl-0 = <&i2c2_pins>;
    };

In this GPIO driver the one thing which is different is that in the
gpio-ranges the first numbers which represent how the pins are exposed
to the users have to start at zero and it can't have gaps.

Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
This commit is contained in:
Dan Carpenter
2026-03-24 14:16:56 +03:00
committed by Peng Fan
parent d113b29523
commit af7b6bb05a
3 changed files with 255 additions and 0 deletions

View File

@@ -732,6 +732,12 @@ config SLG7XL45106_I2C_GPO
8-bit gpo expander, all gpo lines are controlled by writing
value into data register.
config GPIO_SCMI
bool "SCMI GPIO pinctrl driver"
depends on DM_GPIO && PINCTRL_SCMI
help
Support pinctrl GPIO over the SCMI interface.
config ADP5585_GPIO
bool "ADP5585 GPIO driver"
depends on DM_GPIO && DM_I2C

View File

@@ -78,6 +78,7 @@ obj-$(CONFIG_SL28CPLD_GPIO) += sl28cpld-gpio.o
obj-$(CONFIG_ADP5588_GPIO) += adp5588_gpio.o
obj-$(CONFIG_ZYNQMP_GPIO_MODEPIN) += zynqmp_gpio_modepin.o
obj-$(CONFIG_SLG7XL45106_I2C_GPO) += gpio_slg7xl45106.o
obj-$(CONFIG_GPIO_SCMI) += gpio_scmi.o
obj-$(CONFIG_$(PHASE_)ADP5585_GPIO) += adp5585_gpio.o
obj-$(CONFIG_RZG2L_GPIO) += rzg2l-gpio.o
obj-$(CONFIG_MPFS_GPIO) += mpfs_gpio.o

248
drivers/gpio/gpio_scmi.c Normal file
View File

@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2026 Linaro Ltd.
*/
#include <asm/gpio.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <linux/list.h>
#include <scmi_protocols.h>
struct scmi_gpio_range {
u32 base;
u32 offset;
u32 npins;
struct list_head list;
};
static int bank_cnt;
struct scmi_gpio_priv {
struct udevice *pin_dev;
struct list_head gpio_ranges;
char *bank_name;
u32 num_pins;
u16 *pins;
};
static int scmi_gpio_request(struct udevice *dev, unsigned int offset, const char *label)
{
struct scmi_gpio_priv *priv = dev_get_priv(dev);
int pin;
int ret;
if (offset >= priv->num_pins)
return -EINVAL;
pin = priv->pins[offset];
ret = scmi_pinctrl_request(priv->pin_dev, SCMI_PIN, pin);
if (ret == -EOPNOTSUPP)
ret = 0;
if (ret)
dev_err(dev, "%s(): request failed: %d\n", __func__, ret);
return ret;
}
static int scmi_gpio_rfree(struct udevice *dev, unsigned int offset)
{
struct scmi_gpio_priv *priv = dev_get_priv(dev);
int pin;
int ret;
if (offset >= priv->num_pins)
return -EINVAL;
pin = priv->pins[offset];
ret = scmi_pinctrl_release(priv->pin_dev, SCMI_PIN, pin);
if (ret == -EOPNOTSUPP)
ret = 0;
if (ret)
dev_err(dev, "%s(): release failed: %d\n", __func__, ret);
return ret;
}
static int scmi_gpio_set_flags(struct udevice *dev, unsigned int offset, ulong flags)
{
struct scmi_gpio_priv *priv = dev_get_priv(dev);
const int MAX_FLAGS = 10;
u32 configs[MAX_FLAGS * 2];
int cnt = 0;
u32 pin;
if (offset >= priv->num_pins)
return -EINVAL;
pin = priv->pins[offset];
if (flags & GPIOD_IS_OUT) {
configs[cnt++] = SCMI_PIN_OUTPUT_MODE;
configs[cnt++] = 1;
configs[cnt++] = SCMI_PIN_OUTPUT_VALUE;
if (flags & GPIOD_IS_OUT_ACTIVE)
configs[cnt++] = 1;
else
configs[cnt++] = 0;
}
if (flags & GPIOD_IS_IN) {
configs[cnt++] = SCMI_PIN_INPUT_MODE;
configs[cnt++] = 1;
}
if (flags & GPIOD_OPEN_DRAIN) {
configs[cnt++] = SCMI_PIN_DRIVE_OPEN_DRAIN;
configs[cnt++] = 1;
}
if (flags & GPIOD_OPEN_SOURCE) {
configs[cnt++] = SCMI_PIN_DRIVE_OPEN_SOURCE;
configs[cnt++] = 1;
}
if (flags & GPIOD_PULL_UP) {
configs[cnt++] = SCMI_PIN_BIAS_PULL_UP;
configs[cnt++] = 1;
}
if (flags & GPIOD_PULL_DOWN) {
configs[cnt++] = SCMI_PIN_BIAS_PULL_DOWN;
configs[cnt++] = 1;
}
/* TODO: handle GPIOD_ACTIVE_LOW and GPIOD_IS_AF flags */
return scmi_pinctrl_settings_configure(priv->pin_dev, SCMI_PIN, pin,
cnt / 2, &configs[0]);
}
static int scmi_gpio_get_value(struct udevice *dev, unsigned int offset)
{
struct scmi_gpio_priv *priv = dev_get_priv(dev);
u32 value;
int pin;
int ret;
if (offset >= priv->num_pins)
return -EINVAL;
pin = priv->pins[offset];
ret = scmi_pinctrl_settings_get_one(priv->pin_dev, SCMI_PIN, pin,
SCMI_PIN_INPUT_VALUE, &value);
if (ret) {
dev_err(dev, "settings_get_one() failed: %d\n", ret);
return ret;
}
return value;
}
static int scmi_gpio_get_function(struct udevice *dev, unsigned int offset)
{
struct scmi_gpio_priv *priv = dev_get_priv(dev);
u32 value;
int pin;
int ret;
if (offset >= priv->num_pins)
return -EINVAL;
pin = priv->pins[offset];
ret = scmi_pinctrl_settings_get_one(priv->pin_dev, SCMI_PIN, pin,
SCMI_PIN_INPUT_MODE,
&value);
if (ret) {
dev_err(dev, "settings_get() failed %d\n", ret);
return ret;
}
if (value)
return GPIOF_INPUT;
return GPIOF_OUTPUT;
}
static const struct dm_gpio_ops scmi_gpio_ops = {
.request = scmi_gpio_request,
.rfree = scmi_gpio_rfree,
.set_flags = scmi_gpio_set_flags,
.get_value = scmi_gpio_get_value,
.get_function = scmi_gpio_get_function,
};
static int scmi_gpio_probe(struct udevice *dev)
{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
struct scmi_gpio_priv *priv = dev_get_priv(dev);
struct ofnode_phandle_args args;
struct scmi_gpio_range *range;
int index = 0;
int ret, i;
INIT_LIST_HEAD(&priv->gpio_ranges);
for (;; index++) {
ret = dev_read_phandle_with_args(dev, "gpio-ranges",
NULL, 3, index, &args);
if (ret)
break;
if (index == 0) {
ret = uclass_get_device_by_ofnode(UCLASS_PINCTRL,
args.node,
&priv->pin_dev);
if (ret) {
dev_err(dev, "failed to find pinctrl device: %d\n", ret);
return ret;
}
}
range = devm_kmalloc(dev, sizeof(*range), GFP_KERNEL);
if (!range)
return -ENOMEM;
range->base = args.args[0];
if (range->base != priv->num_pins) {
dev_err(dev, "no gaps allowed in between pins %d vs %d\n",
priv->num_pins, range->base);
return -EINVAL;
}
range->offset = args.args[1];
range->npins = args.args[2];
priv->num_pins += args.args[2];
list_add_tail(&range->list, &priv->gpio_ranges);
}
if (priv->num_pins == 0) {
dev_err(dev, "failed to registier pin-groups\n");
return -EINVAL;
}
priv->pins = devm_kzalloc(dev, priv->num_pins * sizeof(u16), GFP_KERNEL);
if (!priv->pins)
return -ENOMEM;
list_for_each_entry(range, &priv->gpio_ranges, list) {
for (i = 0; i < range->npins; i++)
priv->pins[range->base + i] = range->offset + i;
}
ret = snprintf(NULL, 0, "gpio_scmi%d_", bank_cnt);
uc_priv->bank_name = devm_kzalloc(dev, ret + 1, GFP_KERNEL);
if (!uc_priv->bank_name)
return -ENOMEM;
snprintf((char *)uc_priv->bank_name, ret + 1, "gpio_scmi%d_", bank_cnt);
bank_cnt++;
uc_priv->gpio_count = priv->num_pins;
return 0;
}
static const struct udevice_id scmi_gpio_match[] = {
{ .compatible = "scmi-pinctrl-gpio" },
{ }
};
U_BOOT_DRIVER(scmi_pinctrl_gpio) = {
.name = "scmi_pinctrl_gpio",
.id = UCLASS_GPIO,
.of_match = scmi_gpio_match,
.probe = scmi_gpio_probe,
.priv_auto = sizeof(struct scmi_gpio_priv),
.ops = &scmi_gpio_ops,
};