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
14struct 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
23static 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
44static int scmi_pd_power_on(struct generic_pm_domain *domain)
45{
46	return scmi_pd_power(domain, true);
47}
48
49static int scmi_pd_power_off(struct generic_pm_domain *domain)
50{
51	return scmi_pd_power(domain, false);
52}
53
54static 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	dev_set_drvdata(dev, scmi_pd_data);
110
111	return of_genpd_add_provider_onecell(np, scmi_pd_data);
112}
113
114static void scmi_pm_domain_remove(struct scmi_device *sdev)
115{
116	int i;
117	struct genpd_onecell_data *scmi_pd_data;
118	struct device *dev = &sdev->dev;
119	struct device_node *np = dev->of_node;
120
121	of_genpd_del_provider(np);
122
123	scmi_pd_data = dev_get_drvdata(dev);
124	for (i = 0; i < scmi_pd_data->num_domains; i++) {
125		if (!scmi_pd_data->domains[i])
126			continue;
127		pm_genpd_remove(scmi_pd_data->domains[i]);
128	}
129}
130
131static const struct scmi_device_id scmi_id_table[] = {
132	{ SCMI_PROTOCOL_POWER, "genpd" },
133	{ },
134};
135MODULE_DEVICE_TABLE(scmi, scmi_id_table);
136
137static struct scmi_driver scmi_power_domain_driver = {
138	.name = "scmi-power-domain",
139	.probe = scmi_pm_domain_probe,
140	.remove = scmi_pm_domain_remove,
141	.id_table = scmi_id_table,
142};
143module_scmi_driver(scmi_power_domain_driver);
144
145MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
146MODULE_DESCRIPTION("ARM SCMI power domain driver");
147MODULE_LICENSE("GPL v2");
148