[RFC 3/6] pinctrl: add scmi driver

AKASHI Takahiro takahiro.akashi at linaro.org
Wed Sep 6 04:40:08 CEST 2023


This DM-compliant driver deals with SCMI pinctrl protocol and presents
pinctrl devices exposed by SCMI firmware (server).

Signed-off-by: AKASHI Takahiro <takahiro.akashi at linaro.org>
---
 drivers/pinctrl/Kconfig        |  11 +
 drivers/pinctrl/Makefile       |   1 +
 drivers/pinctrl/pinctrl-scmi.c | 537 +++++++++++++++++++++++++++++++++
 3 files changed, 549 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-scmi.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 75b3ff47a2e8..d02f5db550c8 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -256,6 +256,17 @@ config PINCTRL_SANDBOX
 	  Currently, this driver actually does nothing but print debug
 	  messages when pinctrl operations are invoked.
 
+config PINCTRL_SCMI
+	bool "SCMI pinctrl driver"
+	depends on SCMI_FIRMWARE
+	select SCMI_PINCTRL
+	help
+	  This enables pinctrl driver base on  SCMI.
+
+	  The driver is controlled by a device tree node which contains
+	  both the GPIO definitions and pin control functions for each
+	  available multiplex function.
+
 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 fc1f01a02cbd..a791df022b7d 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -27,6 +27,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..3ebdad57b86c
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-scmi.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Linaro Limited
+ *		Author: AKASHI Takahiro <takahiro.akashi at linaro.org>
+ */
+
+#define LOG_CATEGORY UCLASS_PINCTRL
+
+#include <common.h>
+#include <dm.h>
+#include <malloc.h>
+#include <scmi_agent.h>
+#include <scmi_protocols.h>
+#include <dm/device_compat.h>
+#include <dm/pinctrl.h>
+
+/**
+ * struct scmi_pin - attributes for a pin
+ * @name:	Name of pin
+ * @value:	Value of pin
+ * @flags:	A set of flags
+ * @function:	Function selected
+ * @status:	An array of status of configuration types
+ */
+struct scmi_pin {
+	char		*name;
+	u32		value;
+	u32		flags;
+	unsigned int	function;
+	u32		status[SCMI_PINCTRL_CONFIG_RESERVED];
+};
+
+/**
+ * struct scmi_group - attributes for a group
+ * @name:	Name of group
+ * @num_pins:	A number of pins
+ * @pins:	An array of pin id's
+ */
+struct scmi_group {
+	char		*name;
+	unsigned int	num_pins;
+	u16		*pins;
+};
+
+/**
+ * struct scmi_pinctrl_priv - private data for pinctrl device
+ * @num_pins:		A number of pins
+ * @num_groups:		A number of groups
+ * @num_functions:	A number of functions
+ * @pins:		An array of pins
+ * @groups:		An array of groups
+ * @functions:		An array of function names
+ */
+struct scmi_pinctrl_priv {
+	unsigned int	num_pins;
+	unsigned int	num_groups;
+	unsigned int	num_functions;
+	struct scmi_pin	*pins;
+	struct scmi_group *groups;
+	char		**functions;
+};
+
+static const struct pinconf_param scmi_conf_params[] = {
+	{ "default", SCMI_PINCTRL_CONFIG_DEFAULT, 1 },
+	{ "bias-bus-hold", SCMI_PINCTRL_CONFIG_BIAS_BUS_HOLD, 1 },
+	{ "bias-disable", SCMI_PINCTRL_CONFIG_BIAS_DISABLE, 1 },
+	{ "bias-high-impedance", SCMI_PINCTRL_CONFIG_BIAS_HI_IMPEDANCE, 1 },
+	{ "bias-pull-up", SCMI_PINCTRL_CONFIG_BIAS_PULL_UP, 1 },
+	{ "bias-pull-default", SCMI_PINCTRL_CONFIG_BIAS_PULL_DEF, 1 },
+	{ "bias-pull-down", SCMI_PINCTRL_CONFIG_BIAS_PULL_DOWN, 1 },
+	{ "drive-open-drain", SCMI_PINCTRL_CONFIG_DRIVE_OPEN_DRAIN, 1 },
+	{ "drive-open-source", SCMI_PINCTRL_CONFIG_DRIVE_OPEN_SOURCE, 1 },
+	{ "drive-push-pull", SCMI_PINCTRL_CONFIG_DRIVE_PUSH_PULL, 1 },
+	{ "drive-strength", SCMI_PINCTRL_CONFIG_DRIVE_STRENGTH, 0 },
+	{ "input-debounce", SCMI_PINCTRL_CONFIG_INPUT_DEBOUNCE, 0 },
+	{ "input-mode", SCMI_PINCTRL_CONFIG_INPUT_MODE, 1 },
+	{ "pull-mode", SCMI_PINCTRL_CONFIG_PULL_MODE, 0 },
+	{ "input-value", SCMI_PINCTRL_CONFIG_INPUT_VALUE, 0 },
+	{ "input-schmitt", SCMI_PINCTRL_CONFIG_INPUT_SCHMITT, 1 },
+	{ "low-power-mode", SCMI_PINCTRL_CONFIG_LOW_POWER_MODE, 1 },
+	{ "output-mode", SCMI_PINCTRL_CONFIG_OUTPUT_MODE, 1 },
+	{ "output-value", SCMI_PINCTRL_CONFIG_OUTPUT_VALUE, 0 },
+	{ "power-source", SCMI_PINCTRL_CONFIG_POWER_SOURCE, 0 },
+	{ "slew-rate", SCMI_PINCTRL_CONFIG_SLEW_RATE, 0 },
+};
+
+/**
+ * pinctrl_get_name - get a name
+ * @dev:	SCMI pinctrl device
+ * @type:	Type of id
+ * @id:		Identifier of pin, group or function
+ *
+ * Get a name of @id.
+ * @type can be SCMI_PINCTRL_TYPE_PIN, GROUP or FUNCTION.
+ * An extended name is returned if it is provided.
+ *
+ * Return: A pointer to the name, NULL if failed.
+ */
+static char *pinctrl_get_name(struct udevice *dev, unsigned int type,
+			      unsigned int id)
+{
+	u8 *name, *extended_name;
+	bool extended;
+	int ret;
+
+	ret = scmi_pinctrl_attrs(dev, id, type, &extended, &name);
+	if (ret) {
+		dev_err(dev, "failed to get attributes (%d)\n", ret);
+		return NULL;
+	}
+
+	if (!extended)
+		return name;
+
+	ret = scmi_pinctrl_name_get(dev, id, type, &extended_name);
+	if (ret) {
+		dev_err(dev, "failed to get extended_name (%d)\n", ret);
+		return name;
+	}
+
+	free(name);
+	return extended_name;
+}
+
+/**
+ * get_pins_count - Get the number of selectable pins
+ * @dev:	SCMI pinctrl device to use
+ *
+ * Get the number of selectable named pins available in this driver
+ *
+ * Return: a number of pins
+ */
+static int scmi_get_pins_count(struct udevice *dev)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+
+	return priv->num_pins;
+}
+
+/**
+ * get_pin_name - Get the name of a pin
+ * @dev:	SCMI pinctrl device of the pin
+ * @selector:	The pin selector
+ *
+ * Get the name of a pin
+ *
+ * Return: a pointer to the name of the pin
+ */
+static const char *scmi_get_pin_name(struct udevice *dev, unsigned int selector)
+{
+	return pinctrl_get_name(dev, SCMI_PINCTRL_TYPE_PIN, selector);
+}
+
+/**
+ * get_pin_muxing - Show pin muxing
+ * @dev:	SCMI pinctrl device to use
+ * @selector:	Pin selector
+ * @buf:	Buffer to fill with pin muxing description
+ * @size:	Size of @buf
+ *
+ * Create a displayable information in @buf about the muxing of a given pin.
+ *
+ * @Return: 0 if OK, or negative error code on failure
+ */
+static int scmi_get_pin_muxing(struct udevice *dev, unsigned int selector,
+			       char *buf, int size)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	char tmp[100];
+	int i;
+
+	if (priv->pins[selector].function == UINT_MAX) {
+		strlcpy(buf, "<unknown>", size);
+		return 0;
+	}
+
+	sprintf(tmp, "%s", priv->functions[priv->pins[selector].function]);
+	strlcpy(buf, tmp, size);
+
+	for (i = 0; i < SCMI_PINCTRL_CONFIG_RESERVED; i++) {
+		/* TODO: distinguish 0 and "disabled" in status */
+		if (priv->pins[selector].status[i]) {
+			strlcat(buf, " ", size);
+			strlcat(buf, scmi_conf_params[i].property, size);
+		}
+	}
+	strlcat(buf, ".", size);
+
+	return 0;
+}
+
+/**
+ * get_groups_count - Get the number of selectable groups
+ * @dev:	SCMI pinctrl device to use
+ *
+ * Get a number of selectable groups
+ *
+ * Return: a number of selectable named groups available in the driver
+ */
+static int scmi_get_groups_count(struct udevice *dev)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+
+	return priv->num_groups;
+}
+
+/**
+ * get_group_name - Get the name of a group
+ * @dev:	SCMI pinctrl device of the group
+ * @selector:	The group selector
+ *
+ * Ge the name of a group
+ *
+ * Return: a pointer to the name of the group
+ */
+static const char *scmi_get_group_name(struct udevice *dev,
+				       unsigned int selector)
+{
+	return pinctrl_get_name(dev, SCMI_PINCTRL_TYPE_GROUP, selector);
+}
+
+/**
+ * get_functions_count - Get the number of selectable functions
+ * @dev:	SCMI pinctrl device to use
+ *
+ * Get a number of selectable functions
+ *
+ * Return: a number of selectable named functions available in this driver
+ */
+static int scmi_get_functions_count(struct udevice *dev)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+
+	return priv->num_functions;
+}
+
+/**
+ * get_function_name - Get the name of a function
+ * @dev:	SCMI pinmux device of the function
+ * @selector:	The function selector
+ *
+ * Get a name of a function
+ *
+ * Return: a pointer to the function name of the muxing selector
+ */
+static const char *scmi_get_function_name(struct udevice *dev,
+					  unsigned int selector)
+{
+	return pinctrl_get_name(dev, SCMI_PINCTRL_TYPE_FUNCTION, selector);
+}
+
+/**
+ * pinmux_set - Mux a pin to a function
+ * @dev:		SCMI pinctrl device to use
+ * @pin_selector:	The pin selector
+ * @func_selector:	The func selector
+ *
+ * Set a function, @function_selector, to @pin_selector.
+ *
+ * Return: 0 if OK, or negative error code on failure
+ */
+static int scmi_pinmux_set(struct udevice *dev, unsigned int pin_selector,
+			   unsigned int func_selector)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = scmi_pinctrl_function_select(dev, pin_selector, func_selector,
+					   SCMI_PINCTRL_TYPE_PIN);
+	if (ret) {
+		dev_err(dev, "failed to select function (%d)\n", ret);
+		return ret;
+	}
+
+	priv->pins[pin_selector].function = func_selector;
+
+	return 0;
+}
+
+/**
+ * pinmux_group_set - Mux a group of pins to a function
+ * @dev:		SCMI pinctrl device to use
+ * @group_selector:	The group selector
+ * @func_selector:	The func selector
+ *
+ * Set a function, @function_selector, to @group_selector.
+ *
+ * @Return: 0 if OK, or negative error code on failure
+ */
+static int scmi_pinmux_group_set(struct udevice *dev,
+				 unsigned int group_selector,
+				 unsigned int func_selector)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	int i, ret;
+
+	ret = scmi_pinctrl_function_select(dev, group_selector, func_selector,
+					   SCMI_PINCTRL_TYPE_GROUP);
+	if (ret) {
+		dev_err(dev, "failed to select function (%d)\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < priv->groups[group_selector].num_pins; i++)
+		priv->pins[priv->groups[group_selector].pins[i]].function =
+				func_selector;
+
+	return 0;
+}
+
+/* TODO: may be driver-specific */
+/**
+ * pinmux_property_set - Enable a pinmux group
+ * @dev:	SCMI pinctrl device to use
+ * @pinmux_group: A u32 representing the pin identifier and mux
+ *                settings.
+ *
+ * Mux a single pin to a single function based on a driver-specific
+ * pinmux group.
+ * The format of @pinmux_group follows ...
+ *
+ * Return: Pin selector for the muxed pin if OK, or negative error code on
+ *      failure
+ */
+static int scmi_pinmux_property_set(struct udevice *dev, u32 pinmux_group)
+{
+	unsigned int pin_selector = pinmux_group & 0xFFFF;
+	unsigned int func_selector = pinmux_group >> 16;
+	int ret;
+
+	ret = scmi_pinmux_set(dev, pin_selector, func_selector);
+
+	return ret ? ret : pin_selector;
+}
+
+/**
+ * pinconf_set - Configure an individual pin with a parameter
+ * @dev:		SCMI pinctrl device to use
+ * @pin_selector:	The pin selector
+ * @param:		An &enum pin_config_param from @pinconf_params
+ * @argument:		The argument to this param from the device tree, or
+ *			@pinconf_params.default_value
+ *
+ * Configure @param of a pin, @pin_selector, with @argument.
+ *
+ * @Return: 0 if OK, or negative error code on failure
+ */
+static int scmi_pinconf_set(struct udevice *dev, unsigned int pin_selector,
+			    unsigned int param, unsigned int argument)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	struct scmi_pin_entry config;
+	int ret;
+
+	config.type = param;
+	config.value = argument;
+	ret = scmi_pinctrl_config_set(dev, pin_selector, SCMI_PINCTRL_TYPE_PIN,
+				      1, &config);
+	if (ret) {
+		dev_err(dev, "failed to set config (%d)\n", ret);
+		return ret;
+	}
+
+	if (param < SCMI_PINCTRL_CONFIG_RESERVED)
+		priv->pins[pin_selector].status[param] = argument;
+
+	return 0;
+}
+
+/**
+ * pinconf_group_set - Configure all pins in a group with a parameter
+ * @dev:		SCmi pinctrl device to use
+ * @group_selector:	The group selector
+ * @param:		A &enum pin_config_param from @pinconf_params
+ * @argument:		The argument to this param from the device tree, or
+ *			@pinconf_params.default_value
+ *
+ * Configure @param of all the pins in a group, @group_selector, with @argument.
+ *
+ * @Return: 0 if OK, or negative error code on failure
+ */
+static int scmi_pinconf_group_set(struct udevice *dev,
+				  unsigned int group_selector,
+				  unsigned int param, unsigned int argument)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	struct scmi_pin_entry config;
+	int i, ret;
+
+	config.type = param;
+	config.value = argument;
+	ret = scmi_pinctrl_config_set(dev, group_selector,
+				      SCMI_PINCTRL_TYPE_GROUP, 1, &config);
+	if (ret) {
+		dev_err(dev, "failed to set config (%d)\n", ret);
+		return ret;
+	}
+
+	if (param >= SCMI_PINCTRL_CONFIG_RESERVED)
+		return 0;
+
+	for (i = 0; i < priv->groups[group_selector].num_pins; i++)
+		priv->pins[priv->groups[group_selector].pins[i]].status[param] =
+				argument;
+
+	return 0;
+}
+
+const struct pinctrl_ops scmi_pinctrl_ops = {
+	.get_pins_count = scmi_get_pins_count,
+	.get_pin_name = scmi_get_pin_name,
+	.get_pin_muxing = scmi_get_pin_muxing,
+	.get_groups_count = scmi_get_groups_count,
+	.get_group_name = scmi_get_group_name,
+	.get_functions_count = scmi_get_functions_count,
+	.get_function_name = scmi_get_function_name,
+	.pinmux_set = scmi_pinmux_set,
+	.pinmux_group_set = scmi_pinmux_group_set,
+	.pinmux_property_set = scmi_pinmux_property_set,
+	.pinconf_num_params = ARRAY_SIZE(scmi_conf_params),
+	.pinconf_params = scmi_conf_params,
+	.pinconf_set = scmi_pinconf_set,
+	.pinconf_group_set = scmi_pinconf_group_set,
+	.set_state = pinctrl_generic_set_state,
+};
+
+/**
+ * scmi_pinctrl_probe - probe a device
+ * @dev:	SCMI pinctrl device
+ *
+ * Probe and initialize a pinctrl device.
+ *
+ * Return:	0 on success, error code on failure
+ */
+static int scmi_pinctrl_probe(struct udevice *dev)
+{
+	struct scmi_pinctrl_priv *priv = dev_get_priv(dev);
+	u32 version;
+	char *name;
+	int i, ret;
+
+	ret = devm_scmi_of_get_channel(dev);
+	if (ret) {
+		dev_err(dev, "failed to get channel (%d)\n", ret);
+		return ret;
+	}
+
+	ret = scmi_generic_protocol_version(dev, SCMI_PROTOCOL_ID_PIN_CONTROL,
+					    &version);
+	if (ret || version < SCMI_PIN_CONTROL_PROTOCOL_VERSION) {
+		dev_err(dev, "protocol version doesn't match (%d)\n", version);
+		return -EINVAL;
+	}
+
+	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->pins = calloc(sizeof(struct scmi_pin), priv->num_pins);
+	if (!priv->pins) {
+		dev_err(dev, "memory not available\n");
+		return -ENOMEM;
+	}
+	for (i = 0; i < priv->num_pins; i++) {
+		priv->pins[i].function = UINT_MAX; /* unknown yet */
+		name = scmi_get_pin_name(dev, i);
+		if (!name) {
+			dev_err(dev, "failed to get pin name\n");
+			return ret;
+		}
+		priv->pins[i].name = strdup(name);
+		free(name);
+		if (!priv->pins[i].name) {
+			dev_err(dev, "memory not available\n");
+			return -ENOMEM;
+		}
+	}
+
+	priv->groups = calloc(sizeof(struct scmi_group), priv->num_groups);
+	if (!priv->groups)
+		return -ENOMEM;
+	for (i = 0; i < priv->num_groups; i++) {
+		name = scmi_get_group_name(dev, i);
+		if (!name) {
+			dev_err(dev, "failed to get group name\n");
+			return ret;
+		}
+		priv->groups[i].name = strdup(name);
+		free(name);
+		if (!priv->groups[i].name) {
+			dev_err(dev, "memory not available\n");
+			return -ENOMEM;
+		}
+
+		ret = scmi_pinctrl_list_assocs(dev, i, SCMI_PINCTRL_TYPE_GROUP,
+					       &priv->groups[i].pins);
+		if (ret < 0) {
+			dev_err(dev, "failed to enumerate pins (%d)\n", ret);
+			return ret;
+		}
+
+		priv->groups[i].num_pins = ret;
+	}
+
+	priv->functions = calloc(sizeof(char *), priv->num_functions);
+	if (!priv->functions) {
+		dev_err(dev, "memory not available\n");
+		return -ENOMEM;
+	}
+	for (i = 0; i < priv->num_functions; i++) {
+		name = scmi_get_function_name(dev, i);
+		if (!name) {
+			dev_err(dev, "failed to get group name\n");
+			return ret;
+		}
+		priv->functions[i] = strdup(name);
+		free(name);
+		if (!priv->functions[i]) {
+			dev_err(dev, "memory not available\n");
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+U_BOOT_DRIVER(scmi_pinctrl) = {
+	.name = "scmi_pinctrl",
+	.id = UCLASS_PINCTRL,
+	.ops = &scmi_pinctrl_ops,
+	.probe = scmi_pinctrl_probe,
+	.priv_auto	= sizeof(struct scmi_pinctrl_priv),
+};
-- 
2.34.1



More information about the U-Boot mailing list