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