]>
Commit | Line | Data |
---|---|---|
b3dde501 JP |
1 | // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
2 | /* | |
3 | * Copyright (c) 2019 Amlogic, Inc. | |
4 | * Author: Jianxin Pan <jianxin.pan@amlogic.com> | |
5 | */ | |
6 | ||
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
8 | ||
9 | #include <linux/io.h> | |
10 | #include <linux/of_device.h> | |
11 | #include <linux/platform_device.h> | |
12 | #include <linux/pm_domain.h> | |
13 | #include <dt-bindings/power/meson-a1-power.h> | |
14 | #include <linux/arm-smccc.h> | |
15 | #include <linux/firmware/meson/meson_sm.h> | |
d9da1785 | 16 | #include <linux/module.h> |
b3dde501 JP |
17 | |
18 | #define PWRC_ON 1 | |
19 | #define PWRC_OFF 0 | |
20 | ||
21 | struct meson_secure_pwrc_domain { | |
22 | struct generic_pm_domain base; | |
23 | unsigned int index; | |
24 | struct meson_secure_pwrc *pwrc; | |
25 | }; | |
26 | ||
27 | struct meson_secure_pwrc { | |
28 | struct meson_secure_pwrc_domain *domains; | |
29 | struct genpd_onecell_data xlate; | |
30 | struct meson_sm_firmware *fw; | |
31 | }; | |
32 | ||
33 | struct meson_secure_pwrc_domain_desc { | |
34 | unsigned int index; | |
35 | unsigned int flags; | |
36 | char *name; | |
37 | bool (*is_off)(struct meson_secure_pwrc_domain *pwrc_domain); | |
38 | }; | |
39 | ||
40 | struct meson_secure_pwrc_domain_data { | |
41 | unsigned int count; | |
42 | struct meson_secure_pwrc_domain_desc *domains; | |
43 | }; | |
44 | ||
45 | static bool pwrc_secure_is_off(struct meson_secure_pwrc_domain *pwrc_domain) | |
46 | { | |
47 | int is_off = 1; | |
48 | ||
49 | if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_GET, &is_off, | |
50 | pwrc_domain->index, 0, 0, 0, 0) < 0) | |
51 | pr_err("failed to get power domain status\n"); | |
52 | ||
53 | return is_off; | |
54 | } | |
55 | ||
56 | static int meson_secure_pwrc_off(struct generic_pm_domain *domain) | |
57 | { | |
58 | int ret = 0; | |
59 | struct meson_secure_pwrc_domain *pwrc_domain = | |
60 | container_of(domain, struct meson_secure_pwrc_domain, base); | |
61 | ||
62 | if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL, | |
63 | pwrc_domain->index, PWRC_OFF, 0, 0, 0) < 0) { | |
64 | pr_err("failed to set power domain off\n"); | |
65 | ret = -EINVAL; | |
66 | } | |
67 | ||
68 | return ret; | |
69 | } | |
70 | ||
71 | static int meson_secure_pwrc_on(struct generic_pm_domain *domain) | |
72 | { | |
73 | int ret = 0; | |
74 | struct meson_secure_pwrc_domain *pwrc_domain = | |
75 | container_of(domain, struct meson_secure_pwrc_domain, base); | |
76 | ||
77 | if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL, | |
78 | pwrc_domain->index, PWRC_ON, 0, 0, 0) < 0) { | |
79 | pr_err("failed to set power domain on\n"); | |
80 | ret = -EINVAL; | |
81 | } | |
82 | ||
83 | return ret; | |
84 | } | |
85 | ||
86 | #define SEC_PD(__name, __flag) \ | |
87 | [PWRC_##__name##_ID] = \ | |
88 | { \ | |
89 | .name = #__name, \ | |
90 | .index = PWRC_##__name##_ID, \ | |
91 | .is_off = pwrc_secure_is_off, \ | |
92 | .flags = __flag, \ | |
93 | } | |
94 | ||
95 | static struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = { | |
96 | SEC_PD(DSPA, 0), | |
97 | SEC_PD(DSPB, 0), | |
98 | /* UART should keep working in ATF after suspend and before resume */ | |
99 | SEC_PD(UART, GENPD_FLAG_ALWAYS_ON), | |
100 | /* DMC is for DDR PHY ana/dig and DMC, and should be always on */ | |
101 | SEC_PD(DMC, GENPD_FLAG_ALWAYS_ON), | |
102 | SEC_PD(I2C, 0), | |
103 | SEC_PD(PSRAM, 0), | |
104 | SEC_PD(ACODEC, 0), | |
105 | SEC_PD(AUDIO, 0), | |
106 | SEC_PD(OTP, 0), | |
107 | SEC_PD(DMA, 0), | |
108 | SEC_PD(SD_EMMC, 0), | |
109 | SEC_PD(RAMA, 0), | |
110 | /* SRAMB is used as ATF runtime memory, and should be always on */ | |
111 | SEC_PD(RAMB, GENPD_FLAG_ALWAYS_ON), | |
112 | SEC_PD(IR, 0), | |
113 | SEC_PD(SPICC, 0), | |
114 | SEC_PD(SPIFC, 0), | |
115 | SEC_PD(USB, 0), | |
116 | /* NIC is for the Arm NIC-400 interconnect, and should be always on */ | |
117 | SEC_PD(NIC, GENPD_FLAG_ALWAYS_ON), | |
118 | SEC_PD(PDMIN, 0), | |
119 | SEC_PD(RSA, 0), | |
120 | }; | |
121 | ||
122 | static int meson_secure_pwrc_probe(struct platform_device *pdev) | |
123 | { | |
124 | int i; | |
125 | struct device_node *sm_np; | |
126 | struct meson_secure_pwrc *pwrc; | |
127 | const struct meson_secure_pwrc_domain_data *match; | |
128 | ||
129 | match = of_device_get_match_data(&pdev->dev); | |
130 | if (!match) { | |
131 | dev_err(&pdev->dev, "failed to get match data\n"); | |
132 | return -ENODEV; | |
133 | } | |
134 | ||
135 | sm_np = of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm"); | |
136 | if (!sm_np) { | |
137 | dev_err(&pdev->dev, "no secure-monitor node\n"); | |
138 | return -ENODEV; | |
139 | } | |
140 | ||
141 | pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL); | |
142 | if (!pwrc) | |
143 | return -ENOMEM; | |
144 | ||
145 | pwrc->fw = meson_sm_get(sm_np); | |
146 | of_node_put(sm_np); | |
147 | if (!pwrc->fw) | |
148 | return -EPROBE_DEFER; | |
149 | ||
150 | pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count, | |
151 | sizeof(*pwrc->xlate.domains), | |
152 | GFP_KERNEL); | |
153 | if (!pwrc->xlate.domains) | |
154 | return -ENOMEM; | |
155 | ||
156 | pwrc->domains = devm_kcalloc(&pdev->dev, match->count, | |
157 | sizeof(*pwrc->domains), GFP_KERNEL); | |
158 | if (!pwrc->domains) | |
159 | return -ENOMEM; | |
160 | ||
161 | pwrc->xlate.num_domains = match->count; | |
162 | platform_set_drvdata(pdev, pwrc); | |
163 | ||
164 | for (i = 0 ; i < match->count ; ++i) { | |
165 | struct meson_secure_pwrc_domain *dom = &pwrc->domains[i]; | |
166 | ||
167 | if (!match->domains[i].index) | |
168 | continue; | |
169 | ||
170 | dom->pwrc = pwrc; | |
171 | dom->index = match->domains[i].index; | |
172 | dom->base.name = match->domains[i].name; | |
173 | dom->base.flags = match->domains[i].flags; | |
174 | dom->base.power_on = meson_secure_pwrc_on; | |
175 | dom->base.power_off = meson_secure_pwrc_off; | |
176 | ||
177 | pm_genpd_init(&dom->base, NULL, match->domains[i].is_off(dom)); | |
178 | ||
179 | pwrc->xlate.domains[i] = &dom->base; | |
180 | } | |
181 | ||
182 | return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate); | |
183 | } | |
184 | ||
185 | static struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = { | |
186 | .domains = a1_pwrc_domains, | |
187 | .count = ARRAY_SIZE(a1_pwrc_domains), | |
188 | }; | |
189 | ||
190 | static const struct of_device_id meson_secure_pwrc_match_table[] = { | |
191 | { | |
192 | .compatible = "amlogic,meson-a1-pwrc", | |
193 | .data = &meson_secure_a1_pwrc_data, | |
194 | }, | |
195 | { /* sentinel */ } | |
196 | }; | |
d9da1785 | 197 | MODULE_DEVICE_TABLE(of, meson_secure_pwrc_match_table); |
b3dde501 JP |
198 | |
199 | static struct platform_driver meson_secure_pwrc_driver = { | |
200 | .probe = meson_secure_pwrc_probe, | |
201 | .driver = { | |
202 | .name = "meson_secure_pwrc", | |
203 | .of_match_table = meson_secure_pwrc_match_table, | |
204 | }, | |
205 | }; | |
d9da1785 KH |
206 | module_platform_driver(meson_secure_pwrc_driver); |
207 | MODULE_LICENSE("Dual MIT/GPL"); |