xref: /linux/drivers/pmdomain/arm/scmi_perf_domain.c (revision f6e0a4984c2e7244689ea87b62b433bed9d07e94)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * SCMI performance domain support.
4  *
5  * Copyright (C) 2023 Linaro Ltd.
6  */
7 
8 #include <linux/err.h>
9 #include <linux/device.h>
10 #include <linux/module.h>
11 #include <linux/pm_domain.h>
12 #include <linux/pm_opp.h>
13 #include <linux/scmi_protocol.h>
14 #include <linux/slab.h>
15 
16 struct scmi_perf_domain {
17 	struct generic_pm_domain genpd;
18 	const struct scmi_perf_proto_ops *perf_ops;
19 	const struct scmi_protocol_handle *ph;
20 	const struct scmi_perf_domain_info *info;
21 	u32 domain_id;
22 };
23 
24 #define to_scmi_pd(pd) container_of(pd, struct scmi_perf_domain, genpd)
25 
26 static int
27 scmi_pd_set_perf_state(struct generic_pm_domain *genpd, unsigned int state)
28 {
29 	struct scmi_perf_domain *pd = to_scmi_pd(genpd);
30 	int ret;
31 
32 	if (!pd->info->set_perf)
33 		return 0;
34 
35 	if (!state)
36 		return -EINVAL;
37 
38 	ret = pd->perf_ops->level_set(pd->ph, pd->domain_id, state, false);
39 	if (ret)
40 		dev_warn(&genpd->dev, "Failed with %d when trying to set %d perf level",
41 			 ret, state);
42 
43 	return ret;
44 }
45 
46 static int
47 scmi_pd_attach_dev(struct generic_pm_domain *genpd, struct device *dev)
48 {
49 	struct scmi_perf_domain *pd = to_scmi_pd(genpd);
50 	int ret;
51 
52 	/*
53 	 * Allow the device to be attached, but don't add the OPP table unless
54 	 * the performance level can be changed.
55 	 */
56 	if (!pd->info->set_perf)
57 		return 0;
58 
59 	ret = pd->perf_ops->device_opps_add(pd->ph, dev, pd->domain_id);
60 	if (ret)
61 		dev_warn(dev, "failed to add OPPs for the device\n");
62 
63 	return ret;
64 }
65 
66 static void
67 scmi_pd_detach_dev(struct generic_pm_domain *genpd, struct device *dev)
68 {
69 	struct scmi_perf_domain *pd = to_scmi_pd(genpd);
70 
71 	if (!pd->info->set_perf)
72 		return;
73 
74 	dev_pm_opp_remove_all_dynamic(dev);
75 }
76 
77 static int scmi_perf_domain_probe(struct scmi_device *sdev)
78 {
79 	struct device *dev = &sdev->dev;
80 	const struct scmi_handle *handle = sdev->handle;
81 	const struct scmi_perf_proto_ops *perf_ops;
82 	struct scmi_protocol_handle *ph;
83 	struct scmi_perf_domain *scmi_pd;
84 	struct genpd_onecell_data *scmi_pd_data;
85 	struct generic_pm_domain **domains;
86 	int num_domains, i, ret = 0;
87 
88 	if (!handle)
89 		return -ENODEV;
90 
91 	/* The OF node must specify us as a power-domain provider. */
92 	if (!of_find_property(dev->of_node, "#power-domain-cells", NULL))
93 		return 0;
94 
95 	perf_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_PERF, &ph);
96 	if (IS_ERR(perf_ops))
97 		return PTR_ERR(perf_ops);
98 
99 	num_domains = perf_ops->num_domains_get(ph);
100 	if (num_domains < 0) {
101 		dev_warn(dev, "Failed with %d when getting num perf domains\n",
102 			 num_domains);
103 		return num_domains;
104 	} else if (!num_domains) {
105 		return 0;
106 	}
107 
108 	scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
109 	if (!scmi_pd)
110 		return -ENOMEM;
111 
112 	scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
113 	if (!scmi_pd_data)
114 		return -ENOMEM;
115 
116 	domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
117 	if (!domains)
118 		return -ENOMEM;
119 
120 	for (i = 0; i < num_domains; i++, scmi_pd++) {
121 		scmi_pd->info = perf_ops->info_get(ph, i);
122 
123 		scmi_pd->domain_id = i;
124 		scmi_pd->perf_ops = perf_ops;
125 		scmi_pd->ph = ph;
126 		scmi_pd->genpd.name = scmi_pd->info->name;
127 		scmi_pd->genpd.flags = GENPD_FLAG_ALWAYS_ON |
128 				       GENPD_FLAG_OPP_TABLE_FW;
129 		scmi_pd->genpd.set_performance_state = scmi_pd_set_perf_state;
130 		scmi_pd->genpd.attach_dev = scmi_pd_attach_dev;
131 		scmi_pd->genpd.detach_dev = scmi_pd_detach_dev;
132 
133 		ret = pm_genpd_init(&scmi_pd->genpd, NULL, false);
134 		if (ret)
135 			goto err;
136 
137 		domains[i] = &scmi_pd->genpd;
138 	}
139 
140 	scmi_pd_data->domains = domains;
141 	scmi_pd_data->num_domains = num_domains;
142 
143 	ret = of_genpd_add_provider_onecell(dev->of_node, scmi_pd_data);
144 	if (ret)
145 		goto err;
146 
147 	dev_set_drvdata(dev, scmi_pd_data);
148 	dev_info(dev, "Initialized %d performance domains", num_domains);
149 	return 0;
150 err:
151 	for (i--; i >= 0; i--)
152 		pm_genpd_remove(domains[i]);
153 	return ret;
154 }
155 
156 static void scmi_perf_domain_remove(struct scmi_device *sdev)
157 {
158 	struct device *dev = &sdev->dev;
159 	struct genpd_onecell_data *scmi_pd_data = dev_get_drvdata(dev);
160 	int i;
161 
162 	if (!scmi_pd_data)
163 		return;
164 
165 	of_genpd_del_provider(dev->of_node);
166 
167 	for (i = 0; i < scmi_pd_data->num_domains; i++)
168 		pm_genpd_remove(scmi_pd_data->domains[i]);
169 }
170 
171 static const struct scmi_device_id scmi_id_table[] = {
172 	{ SCMI_PROTOCOL_PERF, "perf" },
173 	{ },
174 };
175 MODULE_DEVICE_TABLE(scmi, scmi_id_table);
176 
177 static struct scmi_driver scmi_perf_domain_driver = {
178 	.name		= "scmi-perf-domain",
179 	.probe		= scmi_perf_domain_probe,
180 	.remove		= scmi_perf_domain_remove,
181 	.id_table	= scmi_id_table,
182 };
183 module_scmi_driver(scmi_perf_domain_driver);
184 
185 MODULE_AUTHOR("Ulf Hansson <ulf.hansson@linaro.org>");
186 MODULE_DESCRIPTION("ARM SCMI perf domain driver");
187 MODULE_LICENSE("GPL v2");
188