// SPDX-License-Identifier: GPL-2.0+ /* * SCMI Power domain driver * * Copyright (C) 2023 Linaro Limited * author: AKASHI Takahiro */ #include #include #include #include #include #include #include /** * struct scmi_pwd_properties * @attributes: Power domain attributes * @name: Name of the domain */ struct scmi_pwd_properties { u32 attributes; u8 *name; /* not used now */ }; /** * struct scmi_power_domain_priv * @num_pwdoms: Number of power domains * @prop: Pointer to domain's properties * @stats_addr: Address of statistics memory region * @stats_len: Length of statistics memory region */ struct scmi_power_domain_priv { int num_pwdoms; struct scmi_pwd_properties *prop; u64 stats_addr; size_t stats_len; }; /** * async_is_supported - check asynchronous transition * @attributes: Power domain attributes * * Determine if the power transition can be done asynchronously. * * Return: true if supported, false if not */ static bool async_is_supported(u32 attributes) { if (attributes & SCMI_PWD_ATTR_PSTATE_ASYNC) return true; /* TODO: check attributes && SCMI_PWD_ATTR_PSTATE_SYNC */ return false; } /** * scmi_power_domain_on - Enable the power domain * @power_domain: Power domain * * Turn on the power domain. * * Return: 0 on success, error code on failure */ static int scmi_power_domain_on(struct power_domain *power_domain) { struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev); u32 flags, pstate; int ret; if (power_domain->id > priv->num_pwdoms) return -EINVAL; if (async_is_supported(priv->prop[power_domain->id].attributes)) flags = SCMI_PWD_SET_FLAGS_ASYNC; else flags = 0; /* ON */ pstate = 0; ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id, pstate); if (ret) { dev_err(power_domain->dev, "failed to set the state on (%d)\n", ret); return ret; } return 0; } /** * scmi_power_domain_off - Disable the power domain * @power_domain: Power domain * * Turn off the power domain. * * Return: 0 on success, error code on failure */ static int scmi_power_domain_off(struct power_domain *power_domain) { struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev); u32 flags, pstate; int ret; if (power_domain->id > priv->num_pwdoms) return -EINVAL; if (async_is_supported(priv->prop[power_domain->id].attributes)) flags = SCMI_PWD_SET_FLAGS_ASYNC; else flags = 0; /* OFF */ pstate = SCMI_PWD_PSTATE_TYPE_LOST; ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id, pstate); if (ret) { dev_err(power_domain->dev, "failed to set the state off (%d)\n", ret); return ret; } return 0; } /** * scmi_power_domain_probe - Probe the power domain * @dev: Power domain device * * Probe the power domain and initialize the properties. * * Return: 0 on success, error code on failure */ static int scmi_power_domain_probe(struct udevice *dev) { struct scmi_power_domain_priv *priv = dev_get_priv(dev); u32 version; 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_POWER_DOMAIN, &version); ret = scmi_pwd_protocol_attrs(dev, &priv->num_pwdoms, &priv->stats_addr, &priv->stats_len); if (ret) { dev_err(dev, "failed to get protocol attributes (%d)\n", ret); return ret; } priv->prop = calloc(sizeof(*priv->prop), priv->num_pwdoms); if (!priv->prop) return -ENOMEM; for (i = 0; i < priv->num_pwdoms; i++) { ret = scmi_pwd_attrs(dev, i, &priv->prop[i].attributes, &priv->prop[i].name); if (ret) { dev_err(dev, "failed to get attributes pwd:%d (%d)\n", i, ret); for (i--; i >= 0; i--) free(priv->prop[i].name); free(priv->prop); return ret; } } return 0; } struct power_domain_ops scmi_power_domain_ops = { .on = scmi_power_domain_on, .off = scmi_power_domain_off, }; U_BOOT_DRIVER(scmi_power_domain) = { .name = "scmi_power_domain", .id = UCLASS_POWER_DOMAIN, .ops = &scmi_power_domain_ops, .probe = scmi_power_domain_probe, .priv_auto = sizeof(struct scmi_power_domain_priv), };