1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * SCMI Generic power domain support.
4  *
5  * Copyright (C) 2018 ARM Ltd.
6  */
7 
8 #include <linux/err.h>
9 #include <linux/io.h>
10 #include <linux/module.h>
11 #include <linux/pm_domain.h>
12 #include <linux/scmi_protocol.h>
13 
14 struct scmi_pm_domain {
15 	struct generic_pm_domain genpd;
16 	const struct scmi_handle *handle;
17 	const char *name;
18 	u32 domain;
19 };
20 
21 #define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd)
22 
23 static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
24 {
25 	int ret;
26 	u32 state, ret_state;
27 	struct scmi_pm_domain *pd = to_scmi_pd(domain);
28 	const struct scmi_power_ops *ops = pd->handle->power_ops;
29 
30 	if (power_on)
31 		state = SCMI_POWER_STATE_GENERIC_ON;
32 	else
33 		state = SCMI_POWER_STATE_GENERIC_OFF;
34 
35 	ret = ops->state_set(pd->handle, pd->domain, state);
36 	if (!ret)
37 		ret = ops->state_get(pd->handle, pd->domain, &ret_state);
38 	if (!ret && state != ret_state)
39 		return -EIO;
40 
41 	return ret;
42 }
43 
44 static int scmi_pd_power_on(struct generic_pm_domain *domain)
45 {
46 	return scmi_pd_power(domain, true);
47 }
48 
49 static int scmi_pd_power_off(struct generic_pm_domain *domain)
50 {
51 	return scmi_pd_power(domain, false);
52 }
53 
54 static int scmi_pm_domain_probe(struct scmi_device *sdev)
55 {
56 	int num_domains, i;
57 	struct device *dev = &sdev->dev;
58 	struct device_node *np = dev->of_node;
59 	struct scmi_pm_domain *scmi_pd;
60 	struct genpd_onecell_data *scmi_pd_data;
61 	struct generic_pm_domain **domains;
62 	const struct scmi_handle *handle = sdev->handle;
63 
64 	if (!handle || !handle->power_ops)
65 		return -ENODEV;
66 
67 	num_domains = handle->power_ops->num_domains_get(handle);
68 	if (num_domains < 0) {
69 		dev_err(dev, "number of domains not found\n");
70 		return num_domains;
71 	}
72 
73 	scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
74 	if (!scmi_pd)
75 		return -ENOMEM;
76 
77 	scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
78 	if (!scmi_pd_data)
79 		return -ENOMEM;
80 
81 	domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
82 	if (!domains)
83 		return -ENOMEM;
84 
85 	for (i = 0; i < num_domains; i++, scmi_pd++) {
86 		u32 state;
87 
88 		if (handle->power_ops->state_get(handle, i, &state)) {
89 			dev_warn(dev, "failed to get state for domain %d\n", i);
90 			continue;
91 		}
92 
93 		scmi_pd->domain = i;
94 		scmi_pd->handle = handle;
95 		scmi_pd->name = handle->power_ops->name_get(handle, i);
96 		scmi_pd->genpd.name = scmi_pd->name;
97 		scmi_pd->genpd.power_off = scmi_pd_power_off;
98 		scmi_pd->genpd.power_on = scmi_pd_power_on;
99 
100 		pm_genpd_init(&scmi_pd->genpd, NULL,
101 			      state == SCMI_POWER_STATE_GENERIC_OFF);
102 
103 		domains[i] = &scmi_pd->genpd;
104 	}
105 
106 	scmi_pd_data->domains = domains;
107 	scmi_pd_data->num_domains = num_domains;
108 
109 	of_genpd_add_provider_onecell(np, scmi_pd_data);
110 
111 	return 0;
112 }
113 
114 static const struct scmi_device_id scmi_id_table[] = {
115 	{ SCMI_PROTOCOL_POWER, "genpd" },
116 	{ },
117 };
118 MODULE_DEVICE_TABLE(scmi, scmi_id_table);
119 
120 static struct scmi_driver scmi_power_domain_driver = {
121 	.name = "scmi-power-domain",
122 	.probe = scmi_pm_domain_probe,
123 	.id_table = scmi_id_table,
124 };
125 module_scmi_driver(scmi_power_domain_driver);
126 
127 MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
128 MODULE_DESCRIPTION("ARM SCMI power domain driver");
129 MODULE_LICENSE("GPL v2");
130