This driver adds the base support of pinctrl over SCMI. The driver
does two main things. First it allows you to configure the initial
pin states. And second it's used a base to build a GPIO driver on
top of it.
To configure the states then add a pinmux config to the scmi_pinctrl
section:
scmi_pinctrl: protocol@19 {
reg = <0x19>;
pinmux1: pinmux_test {
pinmux = <0 1 0xFFFFFFFF 18 1
0 2 0xFFFFFFFF 18 0
0 3 0xFFFFFFFF 18 1>;
};
};
The numbers are from PINCTRL_SETTINGS_CONFIGURE: selector, identifier,
function_id, config_type, and config_value.
Signed-off-by: Dan Carpenter <[email protected]>
---
drivers/firmware/scmi/Makefile | 1 +
drivers/firmware/scmi/pinctrl.c | 363 ++++++++++++++++++++++
drivers/firmware/scmi/scmi_agent-uclass.c | 4 +-
drivers/pinctrl/Kconfig | 9 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-scmi.c | 357 +++++++++++++++++++++
include/scmi_agent-uclass.h | 2 +-
include/scmi_protocols.h | 356 +++++++++++++++++++++
8 files changed, 1090 insertions(+), 3 deletions(-)
create mode 100644 drivers/firmware/scmi/pinctrl.c
create mode 100644 drivers/pinctrl/pinctrl-scmi.c
diff --git a/drivers/firmware/scmi/Makefile b/drivers/firmware/scmi/Makefile
index 6129726f8173..761d89a11615 100644
--- a/drivers/firmware/scmi/Makefile
+++ b/drivers/firmware/scmi/Makefile
@@ -5,5 +5,6 @@ obj-$(CONFIG_SCMI_AGENT_SMCCC) += smccc_agent.o
obj-$(CONFIG_SCMI_AGENT_MAILBOX) += mailbox_agent.o
obj-$(CONFIG_SCMI_AGENT_OPTEE) += optee_agent.o
obj-$(CONFIG_SCMI_POWER_DOMAIN) += pwdom.o
+obj-$(CONFIG_PINCTRL_SCMI) += pinctrl.o
obj-$(CONFIG_SANDBOX) += sandbox-scmi_agent.o sandbox-scmi_devices.o
obj-y += vendors/imx/
diff --git a/drivers/firmware/scmi/pinctrl.c b/drivers/firmware/scmi/pinctrl.c
new file mode 100644
index 000000000000..981bf64cd74b
--- /dev/null
+++ b/drivers/firmware/scmi/pinctrl.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#define LOG_CATEGORY UCLASS_PINCTRL
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <dm/pinctrl.h>
+#include <linux/bitfield.h>
+#include <linux/compat.h>
+#include <scmi_agent.h>
+#include <scmi_agent-uclass.h>
+#include <scmi_protocols.h>
+
+static int map_config_param_to_scmi(u32 config_param)
+{
+ switch (config_param) {
+ case PIN_CONFIG_BIAS_BUS_HOLD:
+ return SCMI_PIN_BIAS_BUS_HOLD;
+ case PIN_CONFIG_BIAS_DISABLE:
+ return SCMI_PIN_BIAS_DISABLE;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ return SCMI_PIN_BIAS_HIGH_IMPEDANCE;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return SCMI_PIN_BIAS_PULL_DOWN;
+ case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
+ return SCMI_PIN_BIAS_PULL_DEFAULT;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ return SCMI_PIN_BIAS_PULL_UP;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return SCMI_PIN_DRIVE_OPEN_DRAIN;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ return SCMI_PIN_DRIVE_OPEN_SOURCE;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return SCMI_PIN_DRIVE_PUSH_PULL;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ return SCMI_PIN_DRIVE_STRENGTH;
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return SCMI_PIN_INPUT_DEBOUNCE;
+ case PIN_CONFIG_INPUT_ENABLE:
+ return SCMI_PIN_INPUT_MODE;
+ case PIN_CONFIG_INPUT_SCHMITT:
+ return SCMI_PIN_INPUT_SCHMITT;
+ case PIN_CONFIG_LOW_POWER_MODE:
+ return SCMI_PIN_LOW_POWER_MODE;
+ case PIN_CONFIG_OUTPUT_ENABLE:
+ return SCMI_PIN_OUTPUT_MODE;
+ case PIN_CONFIG_OUTPUT:
+ return SCMI_PIN_OUTPUT_VALUE;
+ case PIN_CONFIG_POWER_SOURCE:
+ return SCMI_PIN_POWER_SOURCE;
+ case PIN_CONFIG_SLEW_RATE:
+ return SCMI_PIN_SLEW_RATE;
+ }
+
+ return -EINVAL;
+}
+
+int scmi_pinctrl_protocol_attrs(struct udevice *dev, int *num_pins,
+ int *num_groups, int *num_functions)
+{
+ struct scmi_pinctrl_protocol_attrs_out out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PROTOCOL_ATTRIBUTES,
+ .out_msg = (u8 *)&out,
+ .out_msg_sz = sizeof(out),
+ };
+ int ret;
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ return ret;
+ if (out.status)
+ return scmi_to_linux_errno(out.status);
+
+ *num_groups = FIELD_GET(GENMASK(31, 16), out.attr_low);
+ *num_pins = FIELD_GET(GENMASK(15, 0), out.attr_low);
+ *num_functions = FIELD_GET(GENMASK(15, 0), out.attr_high);
+
+ return 0;
+}
+
+int scmi_pinctrl_attrs(struct udevice *dev, enum select_type select_type,
+ unsigned int selector, bool *gpio, unsigned int *count,
+ char *name)
+{
+ struct scmi_pinctrl_attrs_in in;
+ struct scmi_pinctrl_attrs_out out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_ATTRIBUTES,
+ .in_msg = (u8 *)&in,
+ .in_msg_sz = sizeof(in),
+ .out_msg = (u8 *)&out,
+ .out_msg_sz = sizeof(out),
+ };
+ int ret;
+
+ in.select_type = select_type;
+ in.id = selector;
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ return ret;
+ if (out.status)
+ return scmi_to_linux_errno(out.status);
+
+ //estended_name = FIELD_GET(BIT(31), out.attr);
+ if (gpio)
+ *gpio = FIELD_GET(BIT(17), out.attr);
+ //pin_only = FIELD_GET(BIT(16), out.attr);
+ if (count)
+ *count = FIELD_GET(GENMASK(15, 0), out.attr);
+ memcpy(name, out.name, sizeof(out.name));
+
+ return 0;
+}
+
+int scmi_pinctrl_list_associations(struct udevice *dev,
+ enum select_type select_type,
+ unsigned int selector,
+ unsigned short *output,
+ unsigned short num_out)
+{
+ struct scmi_pinctrl_list_associations_in in;
+ struct scmi_pinctrl_list_associations_out *out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_LIST_ASSOCIATIONS,
+ .in_msg = (u8 *)&in,
+ .in_msg_sz = sizeof(in),
+ };
+ size_t out_sz = sizeof(*out) + num_out * sizeof(out->array[0]);
+ unsigned int count;
+ int ret = -EINVAL;
+
+ out = kzalloc(out_sz, GFP_KERNEL);
+ if (!out)
+ return -ENOMEM;
+
+ msg.out_msg = (u8 *)out;
+ msg.out_msg_sz = out_sz;
+ in.select_type = select_type;
+ in.id = selector;
+ in.index = 0;
+
+ while (num_out > 0) {
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ goto free;
+ if (out->status) {
+ ret = scmi_to_linux_errno(out->status);
+ goto free;
+ }
+
+ count = FIELD_GET(GENMASK(11, 0), out->flags);
+ if (count > num_out)
+ return -EINVAL;
+ memcpy(&output[in.index], out->array, count * sizeof(u16));
+ num_out -= count;
+ in.index += count;
+ }
+free:
+ kfree(out);
+ return ret;
+}
+
+#define SCMI_PINCTRL_CONFIG_SETTINGS_FUNCTION -2u
+
+int scmi_pinctrl_settings_get_one(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector,
+ u32 config_type, u32 *value)
+{
+ struct scmi_pinctrl_settings_get_in in;
+ struct scmi_pinctrl_settings_get_out *out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_SETTINGS_GET,
+ .in_msg = (u8 *)&in,
+ .in_msg_sz = sizeof(in),
+ };
+ size_t out_sz = sizeof(*out) + (1 * sizeof(u32) * 2);
+ u32 num_configs;
+ int ret;
+
+ out = kzalloc(out_sz, GFP_KERNEL);
+ if (!out)
+ return -ENOMEM;
+
+ msg.out_msg = (u8 *)out;
+ msg.out_msg_sz = out_sz;
+ in.id = selector;
+ in.attr = 0;
+ if (config_type == SCMI_PINCTRL_CONFIG_SETTINGS_FUNCTION)
+ in.attr = FIELD_PREP(GENMASK(19, 18), 2);
+ in.attr |= FIELD_PREP(GENMASK(17, 16), select_type);
+ if (config_type != SCMI_PINCTRL_CONFIG_SETTINGS_FUNCTION)
+ in.attr |= FIELD_PREP(GENMASK(7, 0), config_type);
+
+ if (config_type == SCMI_PINCTRL_CONFIG_SETTINGS_ALL) {
+ /* FIXME: implement */
+ return -EIO;
+ }
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ goto free;
+ if (out->status) {
+ ret = scmi_to_linux_errno(out->status);
+ goto free;
+ }
+ num_configs = FIELD_GET(GENMASK(7, 0), out->num_configs);
+ if (out->num_configs == 0) {
+ *value = out->function_selected;
+ goto free;
+ }
+ if (num_configs != 1) {
+ ret = -EINVAL;
+ goto free;
+ }
+
+ *value = out->configs[1];
+free:
+ kfree(out);
+ return ret;
+}
+
+static int scmi_pinctrl_settings_configure_helper(struct udevice *dev,
+ enum select_type select_type,
+ unsigned int selector,
+ u32 function_id,
+ u16 num_configs, u32 *configs)
+{
+ struct scmi_pinctrl_settings_configure_in *in;
+ struct scmi_pinctrl_settings_configure_out out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_SETTINGS_CONFIGURE,
+ .out_msg = (u8 *)&out,
+ .out_msg_sz = sizeof(out),
+ };
+ size_t in_sz = sizeof(*in) + (num_configs * sizeof(u32) * 2);
+ int ret;
+
+ in = kzalloc(in_sz, GFP_KERNEL);
+ if (!in)
+ return -ENOMEM;
+
+ msg.in_msg = (u8 *)in;
+ msg.in_msg_sz = in_sz;
+ in->id = selector;
+ in->function_id = function_id;
+ in->attr = 0;
+ in->attr |= FIELD_PREP(GENMASK(9, 2), num_configs);
+ in->attr |= FIELD_PREP(GENMASK(1, 0), select_type);
+ memcpy(in->configs, configs, num_configs * sizeof(32) * 2);
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ goto free;
+ if (out.status) {
+ ret = scmi_to_linux_errno(out.status);
+ goto free;
+ }
+free:
+ kfree(in);
+ return ret;
+}
+
+int scmi_pinctrl_settings_configure(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector, u16 num_configs,
+ u32 *configs)
+{
+ return scmi_pinctrl_settings_configure_helper(dev, select_type,
+ selector,
+
SCMI_PINCTRL_FUNCTION_NONE,
+ num_configs, configs);
+}
+
+int scmi_pinctrl_settings_configure_one(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector,
+ u32 param, u32 argument)
+{
+ u32 config_value[2];
+ int scmi_config;
+
+ /* see stmfx_pinctrl_conf_set() */
+ scmi_config = map_config_param_to_scmi(param);
+ if (scmi_config < 0)
+ return scmi_config;
+
+ config_value[0] = scmi_config;
+ config_value[1] = argument;
+
+ return scmi_pinctrl_settings_configure(dev, select_type, selector, 1,
+ &config_value[0]);
+}
+
+int scmi_pinctrl_set_function(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector, u32 function_id)
+{
+ return scmi_pinctrl_settings_configure_helper(dev, select_type,
selector,
+ function_id, 0, NULL);
+}
+
+int scmi_pinctrl_request(struct udevice *dev, enum select_type select_type,
+ unsigned int selector)
+{
+ struct scmi_pinctrl_request_in in;
+ struct scmi_pinctrl_request_out out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_REQUEST,
+ .in_msg = (u8 *)&in,
+ .in_msg_sz = sizeof(in),
+ .out_msg = (u8 *)&out,
+ .out_msg_sz = sizeof(out),
+ };
+ int ret;
+
+ in.id = selector;
+ in.flags = FIELD_PREP(GENMASK(1, 0), select_type);
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ return ret;
+ if (out.status)
+ return scmi_to_linux_errno(out.status);
+
+ return 0;
+}
+
+int scmi_pinctrl_release(struct udevice *dev, enum select_type select_type,
+ unsigned int selector)
+{
+ struct scmi_pinctrl_release_in in;
+ struct scmi_pinctrl_release_out out;
+ struct scmi_msg msg = {
+ .protocol_id = SCMI_PROTOCOL_ID_PINCTRL,
+ .message_id = SCMI_PINCTRL_RELEASE,
+ .in_msg = (u8 *)&in,
+ .in_msg_sz = sizeof(in),
+ .out_msg = (u8 *)&out,
+ .out_msg_sz = sizeof(out),
+ };
+ int ret;
+
+ in.id = selector;
+ in.flags = FIELD_PREP(GENMASK(1, 0), select_type);
+
+ ret = devm_scmi_process_msg(dev, &msg);
+ if (ret)
+ return ret;
+ if (out.status)
+ return scmi_to_linux_errno(out.status);
+
+ return 0;
+}
+
diff --git a/drivers/firmware/scmi/scmi_agent-uclass.c
b/drivers/firmware/scmi/scmi_agent-uclass.c
index ad825d66da2d..cd458a7f4588 100644
--- a/drivers/firmware/scmi/scmi_agent-uclass.c
+++ b/drivers/firmware/scmi/scmi_agent-uclass.c
@@ -106,7 +106,7 @@ struct udevice *scmi_get_protocol(struct udevice *dev,
proto = priv->voltagedom_dev;
break;
#endif
-#if IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
+#if IS_ENABLED(CONFIG_PINCTRL_SCMI) || IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
case SCMI_PROTOCOL_ID_PINCTRL:
proto = priv->pinctrl_dev;
break;
@@ -179,7 +179,7 @@ static int scmi_add_protocol(struct udevice *dev,
priv->voltagedom_dev = proto;
break;
#endif
-#if IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
+#if IS_ENABLED(CONFIG_PINCTRL_SCMI) || IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
case SCMI_PROTOCOL_ID_PINCTRL:
priv->pinctrl_dev = proto;
break;
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index ea90713ec6ca..512c396880c4 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -291,6 +291,15 @@ config PINCTRL_SANDBOX
Currently, this driver actually does nothing but print debug
messages when pinctrl operations are invoked.
+config PINCTRL_SCMI
+ bool "Support SCMI pin controllers"
+ depends on PINCTRL_FULL && SCMI_FIRMWARE
+ help
+ This is for pinctrl over the SCMI protocol. This allows the
+ initial pin configuration to be set up from the device tree. The
+ gpio_scmi driver is built on top of this driver if GPIO is
+ required.
+
config PINCTRL_SINGLE
bool "Single register pin-control and pin-multiplex driver"
depends on DM
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 33ff7b95ef22..8ab163531821 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_PINCTRL_MSCC) += mscc/
obj-$(CONFIG_ARCH_MVEBU) += mvebu/
obj-$(CONFIG_ARCH_NEXELL) += nexell/
obj-$(CONFIG_PINCTRL_QE) += pinctrl-qe-io.o
+obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o
obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o
obj-$(CONFIG_PINCTRL_STI) += pinctrl-sti.o
obj-$(CONFIG_PINCTRL_STM32) += pinctrl_stm32.o
diff --git a/drivers/pinctrl/pinctrl-scmi.c b/drivers/pinctrl/pinctrl-scmi.c
new file mode 100644
index 000000000000..32594489178a
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-scmi.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <dm/pinctrl.h>
+#include <scmi_agent.h>
+#include <scmi_agent-uclass.h>
+#include <scmi_protocols.h>
+
+static const struct pinconf_param pinctrl_scmi_conf_params[] = {
+ { "bias-bus-hold", PIN_CONFIG_BIAS_BUS_HOLD, 0},
+ { "bias-disable", PIN_CONFIG_BIAS_DISABLE, 0 },
+ { "bias-high-impedance", PIN_CONFIG_BIAS_HIGH_IMPEDANCE, 0 },
+ { "bias-pull-down", PIN_CONFIG_BIAS_PULL_DOWN, 0 },
+ { "bias-pull-pin-default", PIN_CONFIG_BIAS_PULL_PIN_DEFAULT, 0 },
+ { "bias-pull-up", PIN_CONFIG_BIAS_PULL_UP, 0 },
+ { "drive-open-drain", PIN_CONFIG_DRIVE_OPEN_DRAIN, 0 },
+ { "drive-open-source", PIN_CONFIG_DRIVE_OPEN_SOURCE, 0 },
+ { "drive-push-pull", PIN_CONFIG_DRIVE_PUSH_PULL, 0 },
+ { "drive-strength", PIN_CONFIG_DRIVE_STRENGTH, 0 },
+ { "input-debounce", PIN_CONFIG_INPUT_DEBOUNCE, 0 },
+ { "input-enable", PIN_CONFIG_INPUT_ENABLE, 1 },
+ { "input-schmitt", PIN_CONFIG_INPUT_SCHMITT, 0 },
+ { "low-power-mode", PIN_CONFIG_LOW_POWER_MODE, 0 },
+ { "output-mode", PIN_CONFIG_OUTPUT_ENABLE, 0 },
+ { "output-value", PIN_CONFIG_OUTPUT, 0 },
+ { "power-source", PIN_CONFIG_POWER_SOURCE, 0 },
+ { "slew-rate", PIN_CONFIG_SLEW_RATE, 0 },
+ /* The SCMI spec also include "default", "pull-mode" and "input-value */
+};
+
+static bool valid_selector(struct udevice *dev, enum select_type select_type,
u32 selector)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (select_type == SCMI_PIN)
+ return selector < priv->num_pins;
+ if (select_type == SCMI_GROUP)
+ return selector < priv->num_groups;
+ if (select_type == SCMI_FUNCTION)
+ return selector < priv->num_functions;
+
+ return false;
+}
+
+static int pinctrl_scmi_get_pins_count(struct udevice *dev)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ return priv->num_pins;
+}
+
+static int pinctrl_scmi_get_groups_count(struct udevice *dev)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ return priv->num_groups;
+}
+
+static int pinctrl_scmi_get_functions_count(struct udevice *dev)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ return priv->num_functions;
+}
+
+static const char *pinctrl_scmi_get_pin_name(struct udevice *dev, unsigned int
selector)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (selector >= priv->num_pins)
+ return NULL;
+
+ return (const char *)priv->pin_info[selector].name;
+}
+
+static const char *pinctrl_scmi_get_group_name(struct udevice *dev, unsigned
int selector)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (selector >= priv->num_groups)
+ return NULL;
+
+ return (const char *)priv->group_info[selector].name;
+}
+
+static const char *pinctrl_scmi_get_function_name(struct udevice *dev,
unsigned int selector)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (selector >= priv->num_functions)
+ return NULL;
+
+ return (const char *)priv->function_info[selector].name;
+}
+
+static int pinctrl_scmi_pinmux_set(struct udevice *dev, u32 pin, u32 function)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (pin >= priv->num_pins || function >= priv->num_functions)
+ return -EINVAL;
+
+ return scmi_pinctrl_set_function(dev, SCMI_PIN, pin, function);
+}
+
+static int pinctrl_scmi_pinmux_group_set(struct udevice *dev, u32 group, u32
function)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+
+ if (group >= priv->num_groups || function >= priv->num_functions)
+ return -EINVAL;
+
+ return scmi_pinctrl_set_function(dev, SCMI_GROUP, group, function);
+}
+
+static int pinctrl_scmi_set_state(struct udevice *dev, struct udevice *config)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+ const int batch_count = 20;
+ u32 prev_type = -1u;
+ u32 prev_selector;
+ u32 *configs;
+ const u32 *prop;
+ int offset, cnt, len;
+ int ret = 0;
+
+ prop = dev_read_prop(config, "pinmux", &len);
+ if (!prop)
+ return 0;
+
+ if (len % sizeof(u32) * 5) {
+ dev_err(dev, "invalid pin configuration: len=%d\n", len);
+ return -FDT_ERR_BADSTRUCTURE;
+ }
+
+ configs = kcalloc(batch_count, sizeof(u32), GFP_KERNEL);
+ if (!configs)
+ return -ENOMEM;
+
+ offset = 0;
+ cnt = 0;
+ while (offset + 4 < len / sizeof(u32)) {
+ u32 select_type = fdt32_to_cpu(prop[offset]);
+ u32 selector = fdt32_to_cpu(prop[offset + 1]);
+ u32 function = fdt32_to_cpu(prop[offset + 2]);
+ u32 config_type = fdt32_to_cpu(prop[offset + 3]);
+ u32 config_value = fdt32_to_cpu(prop[offset + 4]);
+
+ if (select_type > SCMI_GROUP ||
+ !valid_selector(dev, select_type, selector) ||
+ (function != SCMI_PINCTRL_FUNCTION_NONE &&
+ function > priv->num_functions)) {
+ dev_err(dev, "invalid pinctrl data (%u %u %u %u %u)\n",
+ select_type, selector, function, config_type,
+ config_value);
+ ret = -EINVAL;
+ goto free;
+ }
+
+ if (function != SCMI_PINCTRL_FUNCTION_NONE) {
+ if (cnt) {
+ scmi_pinctrl_settings_configure(dev, prev_type,
prev_selector,
+ cnt / 2,
configs);
+ prev_type = -1u;
+ cnt = 0;
+ }
+ scmi_pinctrl_set_function(dev, select_type, selector,
function);
+ offset += 5;
+ continue;
+ }
+
+ if (cnt == batch_count)
+ goto set;
+
+ if (prev_type == -1u)
+ goto store;
+
+ if (select_type == prev_type &&
+ selector == prev_selector)
+ goto store;
+set:
+ scmi_pinctrl_settings_configure(dev, prev_type, prev_selector,
+ cnt / 2, configs);
+ cnt = 0;
+store:
+ prev_type = select_type;
+ prev_selector = selector;
+ configs[cnt++] = config_type;
+ configs[cnt++] = config_value;
+ offset += 5;
+ }
+
+ if (cnt)
+ scmi_pinctrl_settings_configure(dev, prev_type, prev_selector,
+ cnt / 2, configs);
+free:
+ kfree(configs);
+
+ return ret;
+}
+
+static int get_pin_muxing(struct udevice *dev, unsigned int selector,
+ char *buf, int size)
+{
+ u32 value;
+ int ret;
+
+ ret = scmi_pinctrl_settings_get_one(dev, SCMI_PIN, selector,
+ SCMI_PIN_INPUT_VALUE, &value);
+ if (ret) {
+ dev_err(dev, "settings_get() failed: %d\n", ret);
+ return ret;
+ }
+
+ snprintf(buf, size, "%d", value);
+ return 0;
+}
+
+static int pinctrl_scmi_pinconf_set(struct udevice *dev, u32 pin, u32 param,
u32 argument)
+{
+ return scmi_pinctrl_settings_configure_one(dev, SCMI_PIN, pin, param,
argument);
+}
+
+static int pinctrl_scmi_pinconf_group_set(struct udevice *dev, u32 group, u32
param, u32 argument)
+{
+ return scmi_pinctrl_settings_configure_one(dev, SCMI_GROUP, group,
param, argument);
+}
+
+static struct pinctrl_ops scmi_pinctrl_ops = {
+ .get_pins_count = pinctrl_scmi_get_pins_count,
+ .get_pin_name = pinctrl_scmi_get_pin_name,
+
+ .get_groups_count = pinctrl_scmi_get_groups_count,
+ .get_group_name = pinctrl_scmi_get_group_name,
+
+ .get_functions_count = pinctrl_scmi_get_functions_count,
+ .get_function_name = pinctrl_scmi_get_function_name,
+
+ .pinmux_set = pinctrl_scmi_pinmux_set,
+ .pinmux_group_set = pinctrl_scmi_pinmux_group_set,
+
+ .pinconf_num_params = ARRAY_SIZE(pinctrl_scmi_conf_params),
+ .pinconf_params = pinctrl_scmi_conf_params,
+
+ .pinconf_set = pinctrl_scmi_pinconf_set,
+ .pinconf_group_set = pinctrl_scmi_pinconf_group_set,
+ .set_state = pinctrl_scmi_set_state,
+ .get_pin_muxing = get_pin_muxing,
+};
+
+static int scmi_pinctrl_probe(struct udevice *dev)
+{
+ struct pinctrl_scmi_priv *priv = dev_get_priv(dev);
+ int ret;
+ int i;
+
+ ret = devm_scmi_of_get_channel(dev);
+ if (ret) {
+ dev_err(dev, "get_channel() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = scmi_pinctrl_protocol_attrs(dev, &priv->num_pins,
+ &priv->num_groups,
+ &priv->num_functions);
+ if (ret) {
+ dev_err(dev, "failed to get protocol attributes: %d\n", ret);
+ return ret;
+ }
+
+ priv->pin_info = devm_kcalloc(dev, priv->num_pins,
+ sizeof(*priv->pin_info), GFP_KERNEL);
+ priv->group_info = devm_kcalloc(dev, priv->num_groups,
+ sizeof(*priv->group_info), GFP_KERNEL);
+ priv->function_info = devm_kcalloc(dev, priv->num_functions,
+ sizeof(*priv->function_info),
GFP_KERNEL);
+ if (!priv->pin_info || !priv->group_info || !priv->function_info)
+ return -ENOMEM;
+
+ for (i = 0; i < priv->num_pins; i++) {
+ ret = scmi_pinctrl_attrs(dev, SCMI_PIN, i, NULL, NULL,
+ priv->pin_info[i].name);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < priv->num_groups; i++) {
+ ret = scmi_pinctrl_attrs(dev, SCMI_GROUP, i, NULL,
+ &priv->group_info[i].num_pins,
+ priv->group_info[i].name);
+ if (ret) {
+ dev_err(dev, "loading group %d failed: %d\n", i, ret);
+ return ret;
+ }
+ priv->group_info[i].pins = devm_kcalloc(dev,
+
priv->group_info[i].num_pins,
+
sizeof(*priv->group_info[i].pins),
+ GFP_KERNEL);
+ if (!priv->group_info[i].pins)
+ return -ENOMEM;
+
+ ret = scmi_pinctrl_list_associations(dev, SCMI_GROUP, i,
+ priv->group_info[i].pins,
+
priv->group_info[i].num_pins);
+ if (ret) {
+ dev_err(dev, "list association %d failed for group:
%d\n", i, ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < priv->num_functions; i++) {
+ ret = scmi_pinctrl_attrs(dev, SCMI_FUNCTION, i, NULL,
+ &priv->function_info[i].num_groups,
+ priv->function_info[i].name);
+ if (ret) {
+ dev_err(dev, "loading function %d failed: %d\n", i,
ret);
+ return ret;
+ }
+ priv->function_info[i].groups = devm_kcalloc(dev,
+ priv->function_info[i].num_groups,
+ sizeof(*priv->function_info[i].groups),
+ GFP_KERNEL);
+ if (!priv->function_info[i].groups)
+ return -ENOMEM;
+
+ ret = scmi_pinctrl_list_associations(dev, SCMI_FUNCTION, i,
+
priv->function_info[i].groups,
+
priv->function_info[i].num_groups);
+ if (ret) {
+ dev_err(dev, "list association %d failed for function:
%d\n", i, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+U_BOOT_DRIVER(pinctrl_scmi) = {
+ .name = "scmi_pinctrl",
+ .id = UCLASS_PINCTRL,
+ .ops = &scmi_pinctrl_ops,
+ .probe = scmi_pinctrl_probe,
+ .priv_auto = sizeof(struct pinctrl_scmi_priv),
+};
+
+static struct scmi_proto_match match[] = {
+ { .proto_id = SCMI_PROTOCOL_ID_PINCTRL },
+ { /* Sentinel */ }
+};
+
+U_BOOT_SCMI_PROTO_DRIVER(pinctrl_scmi, match);
+
diff --git a/include/scmi_agent-uclass.h b/include/scmi_agent-uclass.h
index 9b36d3ae67bb..c40b448bcba1 100644
--- a/include/scmi_agent-uclass.h
+++ b/include/scmi_agent-uclass.h
@@ -52,7 +52,7 @@ struct scmi_agent_priv {
#if IS_ENABLED(CONFIG_DM_REGULATOR_SCMI)
struct udevice *voltagedom_dev;
#endif
-#if IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
+#if IS_ENABLED(CONFIG_PINCTRL_SCMI) || IS_ENABLED(CONFIG_PINCTRL_IMX_SCMI)
struct udevice *pinctrl_dev;
#endif
#if IS_ENABLED(CONFIG_SCMI_ID_VENDOR_80)
diff --git a/include/scmi_protocols.h b/include/scmi_protocols.h
index a4efadafa73c..3e862adb8035 100644
--- a/include/scmi_protocols.h
+++ b/include/scmi_protocols.h
@@ -1146,4 +1146,360 @@ struct scmi_perf_in {
struct scmi_perf_out {
s32 status;
};
+
+#define SCMI_PIN_NAME_LEN 16
+
+struct pin_info {
+ char name[SCMI_PIN_NAME_LEN];
+};
+
+struct group_info {
+ char name[SCMI_PIN_NAME_LEN];
+ u16 *pins;
+ u32 num_pins;
+};
+
+struct function_info {
+ char name[SCMI_PIN_NAME_LEN];
+ u16 *groups;
+ u32 num_groups;
+};
+
+/* This is used by both the SCMI pinctrl and gpio drivers */
+struct pinctrl_scmi_priv {
+ int num_pins;
+ struct pin_info *pin_info;
+ int num_groups;
+ struct group_info *group_info;
+ int num_functions;
+ struct function_info *function_info;
+};
+
+/* SCMI Pinctrl selector type */
+enum select_type {
+ SCMI_PIN,
+ SCMI_GROUP,
+ SCMI_FUNCTION,
+};
+
+/**
+ * struct scmi_pinctrl_protocol_attrs_out - Response to
SCMI_PROTOCOL_ATTRIBUTES
+ * command.
+ * @status: SCMI command status
+ * @attr_low: Number of pins and groups
+ * @attr_high: Number of functions
+ */
+struct scmi_pinctrl_protocol_attrs_out {
+ s32 status;
+ u32 attr_low;
+ u32 attr_high;
+};
+
+/**
+ * struct scmi_pinctrl_attrs_in - Parameters for SCMI_PINCTRL_ATTRIBUTES
command
+ * @id: Identifier for pin, group or function
+ * @select_type: Pin, group or function
+ */
+struct scmi_pinctrl_attrs_in {
+ u32 id;
+ u32 select_type;
+};
+
+/**
+ * struct scmi_pinctrl_attrs_out - Response to SCMI_PINCTRL_ATTRIBUTES command
+ * @status: SCMI command status
+ * @attr: GPIO, number of pins or groups
+ * @name: Name of pin, group or function
+ */
+struct scmi_pinctrl_attrs_out {
+ s32 status;
+ u32 attr;
+ u8 name[SCMI_PIN_NAME_LEN];
+};
+
+/**
+ * struct scmi_pinctrl_list_associations_in - Parameters for
+ * SCMI_PINCTRL_LIST_ASSOCIATIONS command
+ * @id: Identifier for group or function
+ * @select_type: Group or function
+ * @index: Index within the group or function
+ */
+struct scmi_pinctrl_list_associations_in {
+ u32 id;
+ u32 select_type;
+ u32 index;
+};
+
+/**
+ * struct scmi_pinctrl_list_associations_out - Response to
+ * SCMI_PINCTRL_LIST_ASSOCIATIONS command
+ * @status: SCMI command status
+ * @flags: Number of items returned and number still remaining
+ * @array: List of groups or pins
+ */
+struct scmi_pinctrl_list_associations_out {
+ s32 status;
+ u32 flags;
+ u16 array[];
+};
+
+/**
+ * struct scmi_pinctrl_settings_get_in - Parameters for
+ * SCMI_PINCTRL_SETTINGS_GET command
+ * @id: Identifier for pin or group
+ * @attr: Config flag: one setting, function or all settings
+ * Selector: Pin or Group
+ * Skip: Number of config types to skip
+ * Config type: Type of config to read
+ */
+struct scmi_pinctrl_settings_get_in {
+ u32 id;
+ u32 attr;
+};
+
+#define SCMI_PINCTRL_CONFIG_SETTINGS_ALL -2u
+#define SCMI_PINCTRL_FUNCTION_NONE 0xFFFFFFFF
+
+/**
+ * struct scmi_pinctrl_settings_get_out - Response to SCMI_PINCTRL_SETTINGS_GET
+ * command
+ * @status: SCMI command status
+ * @function_selected: The function enabled by the pin or group
+ * @num_configs: The number of settings returned and number still
remaining
+ * @configs: The list of config data
+ */
+struct scmi_pinctrl_settings_get_out {
+ s32 status;
+ u32 function_selected;
+ u32 num_configs;
+ u32 configs[];
+};
+
+/**
+ * struct scmi_pinctrl_settings_configure_in - Parameters for
+ * SCMI_PINCTRL_SETTINGS_CONFIGURE command
+ * @id: Identifier for pin or group
+ * @function_id: The function to enable for this pin or group (optional)
+ * @attr: Function id: Set the function or not
+ * Number of configs to set
+ * Selector: pin or group
+ * @configs: List of config type value pairs
+ */
+struct scmi_pinctrl_settings_configure_in {
+ u32 id;
+ u32 function_id;
+ u32 attr;
+ u32 configs[];
+};
+
+/**
+ * struct scmi_pinctrl_settings_configure_out - Response to
+ * SCMI_PINCTRL_SETTINGS_CONFIGURE command
+ * @status: SCMI command status
+ */
+struct scmi_pinctrl_settings_configure_out {
+ s32 status;
+};
+
+/**
+ * struct scmi_pinctrl_request_in - Parameters for SCMI_PINCTRL_REQUEST command
+ * @id: Identifier for pin or group
+ * @flags: Pin, group or function
+ */
+struct scmi_pinctrl_request_in {
+ u32 id;
+ u32 flags;
+};
+
+/**
+ * struct scmi_pinctrl_request_out - Response to SCMI_PINCTRL_REQUEST command
+ * @status: SCMI command status
+ */
+struct scmi_pinctrl_request_out {
+ s32 status;
+};
+
+/**
+ * struct scmi_pinctrl_release_in - Parameters for SCMI_PINCTRL_RELEASE command
+ * @id: Identifier for pin or group
+ * @flags: Pin, group or function
+ */
+struct scmi_pinctrl_release_in {
+ u32 id;
+ u32 flags;
+};
+
+/**
+ * struct scmi_pinctrl_release_out - Response to SCMI_PINCTRL_RELEASE command
+ * @status: SCMI command status
+ */
+struct scmi_pinctrl_release_out {
+ s32 status;
+};
+
+/* SCMI Pinctrl Config Types */
+enum scmi_config_type {
+ SCMI_PIN_DEFUALT = 0,
+ SCMI_PIN_BIAS_BUS_HOLD = 1,
+ SCMI_PIN_BIAS_DISABLE = 2,
+ SCMI_PIN_BIAS_HIGH_IMPEDANCE = 3,
+ SCMI_PIN_BIAS_PULL_UP = 4,
+ SCMI_PIN_BIAS_PULL_DEFAULT = 5,
+ SCMI_PIN_BIAS_PULL_DOWN = 6,
+ SCMI_PIN_DRIVE_OPEN_DRAIN = 7,
+ SCMI_PIN_DRIVE_OPEN_SOURCE = 8,
+ SCMI_PIN_DRIVE_PUSH_PULL = 9,
+ SCMI_PIN_DRIVE_STRENGTH = 10,
+ SCMI_PIN_INPUT_DEBOUNCE = 11,
+ SCMI_PIN_INPUT_MODE = 12,
+ SCMI_PIN_PULL_MODE = 13,
+ SCMI_PIN_INPUT_VALUE = 14,
+ SCMI_PIN_INPUT_SCHMITT = 15,
+ SCMI_PIN_LOW_POWER_MODE = 16,
+ SCMI_PIN_OUTPUT_MODE = 17,
+ SCMI_PIN_OUTPUT_VALUE = 18,
+ SCMI_PIN_POWER_SOURCE = 19,
+ SCMI_PIN_SLEW_RATE = 20,
+};
+
+/**
+ * scmi_pinctrl_protocol_attrs - get pinctrl information
+ * @dev: SCMI protocol device
+ * @num_pins: Number of pins
+ * @num_groups: Number of groups
+ * @num_functions: Number of functions
+ *
+ * Obtain the number of pins, groups and functions.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_protocol_attrs(struct udevice *dev, int *num_pins,
+ int *num_groups, int *num_functions);
+
+/**
+ * scmi_pinctrl_attrs - get information for a specific pin, group or function
+ * @dev: SCMI protocol device
+ * @select_type: pin, group or function
+ * @selector: id of pin, group or function
+ * @gpio: set to true if the pin or group supports gpio
+ * @count: number of groups in function or pins in group
+ * @name: name of pin, group or function
+ *
+ * Obtain information about a specific pin, group or function.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_attrs(struct udevice *dev, enum select_type select_type,
+ unsigned int selector, bool *gpio, unsigned int *count,
+ char *name);
+
+/**
+ * scmi_pinctrl_request - claim a pin or group
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ *
+ * Claim ownership of a pin or group.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_request(struct udevice *dev, enum select_type select_type,
+ unsigned int selector);
+/**
+ * scmi_pinctrl_release - release a claimed pin or group
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ *
+ * Release a pin or group that you previously claimed.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_release(struct udevice *dev, enum select_type select_type,
+ unsigned int selector);
+
+/**
+ * scmi_pinctrl_list_associations - get list of pins in group or groups in
function
+ * @dev: SCMI protocol device
+ * @select_type: group or function
+ * @selector: id of group or function
+ * @output: list of groups in function or pins in group
+ * @num_out: How many groups are in the function or pins in the group
+ *
+ * Obtain the list of groups or pins in the function or group respectively.
+ * We know how many items will be in the list from calling
scmi_pinctrl_attrs().
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_list_associations(struct udevice *dev,
+ enum select_type select_type,
+ unsigned int selector,
+ unsigned short *output,
+ unsigned short num_out);
+
+/**
+ * scmi_pinctrl_settings_get_one - get a configuration setting
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ * @config_type: Which configuration type to read
+ * @value: returned configuration value
+ *
+ * This reads a single config setting. Most importantly the
+ * SCMI_PIN_INPUT_VALUE setting is used to read from a pin.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_settings_get_one(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector,
+ u32 config_type, u32 *value);
+
+/**
+ * scmi_pinctrl_settings_configure - set multiple configuration settings
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ * @num_configs: number of settings to set
+ * @configs: Config type and value pairs
+ *
+ * Configure multiple settings at once to reduce overhead. The
+ * SCMI_PIN_OUTPUT_VALUE setting is used to write to a pin.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_settings_configure(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector, u16 num_configs,
+ u32 *configs);
+
+/**
+ * scmi_pinctrl_settings_configure_one - set a configuration setting
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ * @param: The setting type to configure
+ * @argument: The value of the configuration
+ *
+ * Configure a single setting. The SCMI_PIN_OUTPUT_VALUE setting is used to
+ * write to a pin.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_settings_configure_one(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector,
+ u32 param, u32 argument);
+
+/**
+ * scmi_pinctrl_set_function - set the function for a group or pin
+ * @dev: SCMI protocol device
+ * @select_type: pin or group
+ * @selector: id of pin or group
+ * @function_id: id of the function
+ *
+ * Set the function for a group or pin.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int scmi_pinctrl_set_function(struct udevice *dev, enum select_type
select_type,
+ unsigned int selector, u32 function_id);
+
#endif /* _SCMI_PROTOCOLS_H */
--
2.51.0