1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * SCMI Power domain driver
4 *
5 * Copyright (C) 2023 Linaro Limited
6 *              author: AKASHI Takahiro <takahiro.akashi@linaro.org>
7 */
8
9#include <dm.h>
10#include <malloc.h>
11#include <power-domain.h>
12#include <power-domain-uclass.h>
13#include <scmi_agent.h>
14#include <scmi_protocols.h>
15#include <dm/device_compat.h>
16
17/**
18 * struct scmi_pwd_properties
19 * @attributes:	Power domain attributes
20 * @name:	Name of the domain
21 */
22struct scmi_pwd_properties {
23	u32 attributes;
24	u8 *name; /* not used now */
25};
26
27/**
28 * struct scmi_power_domain_priv
29 * @num_pwdoms:	Number of power domains
30 * @prop:	Pointer to domain's properties
31 * @stats_addr:	Address of statistics memory region
32 * @stats_len:	Length of statistics memory region
33 */
34struct scmi_power_domain_priv {
35	int num_pwdoms;
36	struct scmi_pwd_properties *prop;
37	u64 stats_addr;
38	size_t stats_len;
39};
40
41/**
42 * async_is_supported - check asynchronous transition
43 * @attributes:	Power domain attributes
44 *
45 * Determine if the power transition can be done asynchronously.
46 *
47 * Return: true if supported, false if not
48 */
49static bool async_is_supported(u32 attributes)
50{
51	if (attributes & SCMI_PWD_ATTR_PSTATE_ASYNC)
52		return true;
53
54	/* TODO: check attributes && SCMI_PWD_ATTR_PSTATE_SYNC */
55	return false;
56}
57
58/**
59 * scmi_power_domain_on - Enable the power domain
60 * @power_domain:	Power domain
61 *
62 * Turn on the power domain.
63 *
64 * Return: 0 on success, error code on failure
65 */
66static int scmi_power_domain_on(struct power_domain *power_domain)
67{
68	struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev);
69	u32 flags, pstate;
70	int ret;
71
72	if (power_domain->id > priv->num_pwdoms)
73		return -EINVAL;
74
75	if (async_is_supported(priv->prop[power_domain->id].attributes))
76		flags = SCMI_PWD_SET_FLAGS_ASYNC;
77	else
78		flags = 0;
79
80	/* ON */
81	pstate = 0;
82
83	ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id,
84				 pstate);
85	if (ret) {
86		dev_err(power_domain->dev, "failed to set the state on (%d)\n",
87			ret);
88		return ret;
89	}
90
91	return 0;
92}
93
94/**
95 * scmi_power_domain_off - Disable the power domain
96 * @power_domain:	Power domain
97 *
98 * Turn off the power domain.
99 *
100 * Return: 0 on success, error code on failure
101 */
102static int scmi_power_domain_off(struct power_domain *power_domain)
103{
104	struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev);
105	u32 flags, pstate;
106	int ret;
107
108	if (power_domain->id > priv->num_pwdoms)
109		return -EINVAL;
110
111	if (async_is_supported(priv->prop[power_domain->id].attributes))
112		flags = SCMI_PWD_SET_FLAGS_ASYNC;
113	else
114		flags = 0;
115
116	/* OFF */
117	pstate = SCMI_PWD_PSTATE_TYPE_LOST;
118
119	ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id,
120				 pstate);
121	if (ret) {
122		dev_err(power_domain->dev, "failed to set the state off (%d)\n",
123			ret);
124		return ret;
125	}
126
127	return 0;
128}
129
130/**
131 * scmi_power_domain_probe - Probe the power domain
132 * @dev:	Power domain device
133 *
134 * Probe the power domain and initialize the properties.
135 *
136 * Return: 0 on success, error code on failure
137 */
138static int scmi_power_domain_probe(struct udevice *dev)
139{
140	struct scmi_power_domain_priv *priv = dev_get_priv(dev);
141	u32 version;
142	int i, ret;
143
144	ret = devm_scmi_of_get_channel(dev);
145	if (ret) {
146		dev_err(dev, "failed to get channel (%d)\n", ret);
147		return ret;
148	}
149
150	ret = scmi_generic_protocol_version(dev, SCMI_PROTOCOL_ID_POWER_DOMAIN,
151					    &version);
152
153	ret = scmi_pwd_protocol_attrs(dev, &priv->num_pwdoms, &priv->stats_addr,
154				      &priv->stats_len);
155	if (ret) {
156		dev_err(dev, "failed to get protocol attributes (%d)\n", ret);
157		return ret;
158	}
159
160	priv->prop = calloc(sizeof(*priv->prop), priv->num_pwdoms);
161	if (!priv->prop)
162		return -ENOMEM;
163
164	for (i = 0; i < priv->num_pwdoms; i++) {
165		ret = scmi_pwd_attrs(dev, i, &priv->prop[i].attributes,
166				     &priv->prop[i].name);
167		if (ret) {
168			dev_err(dev, "failed to get attributes pwd:%d (%d)\n",
169				i, ret);
170			for (i--; i >= 0; i--)
171				free(priv->prop[i].name);
172			free(priv->prop);
173
174			return ret;
175		}
176	}
177
178	return 0;
179}
180
181struct power_domain_ops scmi_power_domain_ops = {
182	.on = scmi_power_domain_on,
183	.off = scmi_power_domain_off,
184};
185
186U_BOOT_DRIVER(scmi_power_domain) = {
187	.name = "scmi_power_domain",
188	.id = UCLASS_POWER_DOMAIN,
189	.ops = &scmi_power_domain_ops,
190	.probe = scmi_power_domain_probe,
191	.priv_auto = sizeof(struct scmi_power_domain_priv),
192};
193