From: Dan Carpenter Date: Tue, 24 Mar 2026 11:16:56 +0000 (+0300) Subject: gpio: scmi: Add gpio_scmi driver X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af7b6bb05a810a0ec94fd864e3477401b3d1b60a;p=thirdparty%2Fu-boot.git 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 Signed-off-by: Peng Fan --- diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1484dd3504c..0b5466b39b8 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -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 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fec258f59f5..863557e45ce 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -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 diff --git a/drivers/gpio/gpio_scmi.c b/drivers/gpio/gpio_scmi.c new file mode 100644 index 00000000000..d25e3b6a4aa --- /dev/null +++ b/drivers/gpio/gpio_scmi.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include + +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, +}; +