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

Signed-off-by: AKASHI Takahiro <takahiro.aka...@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.aka...@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

Reply via email to