
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@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@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), +};