mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-06-02 09:46:37 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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
248
drivers/gpio/gpio_scmi.c
Normal 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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user