1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2021 Linaro Ltd. |
4 | * Copyright (C) 2021 Dávid Virág <virag.david003@gmail.com> |
5 | * Author: Sam Protsenko <semen.protsenko@linaro.org> |
6 | * Author: Dávid Virág <virag.david003@gmail.com> |
7 | * |
8 | * This file contains shared functions used by some arm64 Exynos SoCs, |
9 | * such as Exynos7885 or Exynos850 to register and init CMUs. |
10 | */ |
11 | #include <linux/clk.h> |
12 | #include <linux/of_address.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #include "clk-exynos-arm64.h" |
19 | |
20 | /* Gate register bits */ |
21 | #define GATE_MANUAL BIT(20) |
22 | #define GATE_ENABLE_HWACG BIT(28) |
23 | |
24 | /* Gate register offsets range */ |
25 | #define GATE_OFF_START 0x2000 |
26 | #define GATE_OFF_END 0x2fff |
27 | |
28 | struct exynos_arm64_cmu_data { |
29 | struct samsung_clk_reg_dump *clk_save; |
30 | unsigned int nr_clk_save; |
31 | const struct samsung_clk_reg_dump *clk_suspend; |
32 | unsigned int nr_clk_suspend; |
33 | |
34 | struct clk *clk; |
35 | struct clk **pclks; |
36 | int nr_pclks; |
37 | |
38 | struct samsung_clk_provider *ctx; |
39 | }; |
40 | |
41 | /** |
42 | * exynos_arm64_init_clocks - Set clocks initial configuration |
43 | * @np: CMU device tree node with "reg" property (CMU addr) |
44 | * @reg_offs: Register offsets array for clocks to init |
45 | * @reg_offs_len: Number of register offsets in reg_offs array |
46 | * |
47 | * Set manual control mode for all gate clocks. |
48 | */ |
49 | static void __init exynos_arm64_init_clocks(struct device_node *np, |
50 | const unsigned long *reg_offs, size_t reg_offs_len) |
51 | { |
52 | void __iomem *reg_base; |
53 | size_t i; |
54 | |
55 | reg_base = of_iomap(node: np, index: 0); |
56 | if (!reg_base) |
57 | panic(fmt: "%s: failed to map registers\n" , __func__); |
58 | |
59 | for (i = 0; i < reg_offs_len; ++i) { |
60 | void __iomem *reg = reg_base + reg_offs[i]; |
61 | u32 val; |
62 | |
63 | /* Modify only gate clock registers */ |
64 | if (reg_offs[i] < GATE_OFF_START || reg_offs[i] > GATE_OFF_END) |
65 | continue; |
66 | |
67 | val = readl(addr: reg); |
68 | val |= GATE_MANUAL; |
69 | val &= ~GATE_ENABLE_HWACG; |
70 | writel(val, addr: reg); |
71 | } |
72 | |
73 | iounmap(addr: reg_base); |
74 | } |
75 | |
76 | /** |
77 | * exynos_arm64_enable_bus_clk - Enable parent clock of specified CMU |
78 | * |
79 | * @dev: Device object; may be NULL if this function is not being |
80 | * called from platform driver probe function |
81 | * @np: CMU device tree node |
82 | * @cmu: CMU data |
83 | * |
84 | * Keep CMU parent clock running (needed for CMU registers access). |
85 | * |
86 | * Return: 0 on success or a negative error code on failure. |
87 | */ |
88 | static int __init exynos_arm64_enable_bus_clk(struct device *dev, |
89 | struct device_node *np, const struct samsung_cmu_info *cmu) |
90 | { |
91 | struct clk *parent_clk; |
92 | |
93 | if (!cmu->clk_name) |
94 | return 0; |
95 | |
96 | if (dev) { |
97 | struct exynos_arm64_cmu_data *data; |
98 | |
99 | parent_clk = clk_get(dev, id: cmu->clk_name); |
100 | data = dev_get_drvdata(dev); |
101 | if (data) |
102 | data->clk = parent_clk; |
103 | } else { |
104 | parent_clk = of_clk_get_by_name(np, name: cmu->clk_name); |
105 | } |
106 | |
107 | if (IS_ERR(ptr: parent_clk)) |
108 | return PTR_ERR(ptr: parent_clk); |
109 | |
110 | return clk_prepare_enable(clk: parent_clk); |
111 | } |
112 | |
113 | static int __init exynos_arm64_cmu_prepare_pm(struct device *dev, |
114 | const struct samsung_cmu_info *cmu) |
115 | { |
116 | struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
117 | int i; |
118 | |
119 | data->clk_save = samsung_clk_alloc_reg_dump(rdump: cmu->clk_regs, |
120 | nr_rdump: cmu->nr_clk_regs); |
121 | if (!data->clk_save) |
122 | return -ENOMEM; |
123 | |
124 | data->nr_clk_save = cmu->nr_clk_regs; |
125 | data->clk_suspend = cmu->suspend_regs; |
126 | data->nr_clk_suspend = cmu->nr_suspend_regs; |
127 | data->nr_pclks = of_clk_get_parent_count(np: dev->of_node); |
128 | if (!data->nr_pclks) |
129 | return 0; |
130 | |
131 | data->pclks = devm_kcalloc(dev, n: sizeof(struct clk *), size: data->nr_pclks, |
132 | GFP_KERNEL); |
133 | if (!data->pclks) { |
134 | kfree(objp: data->clk_save); |
135 | return -ENOMEM; |
136 | } |
137 | |
138 | for (i = 0; i < data->nr_pclks; i++) { |
139 | struct clk *clk = of_clk_get(np: dev->of_node, index: i); |
140 | |
141 | if (IS_ERR(ptr: clk)) { |
142 | kfree(objp: data->clk_save); |
143 | while (--i >= 0) |
144 | clk_put(clk: data->pclks[i]); |
145 | return PTR_ERR(ptr: clk); |
146 | } |
147 | data->pclks[i] = clk; |
148 | } |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | /** |
154 | * exynos_arm64_register_cmu - Register specified Exynos CMU domain |
155 | * @dev: Device object; may be NULL if this function is not being |
156 | * called from platform driver probe function |
157 | * @np: CMU device tree node |
158 | * @cmu: CMU data |
159 | * |
160 | * Register specified CMU domain, which includes next steps: |
161 | * |
162 | * 1. Enable parent clock of @cmu CMU |
163 | * 2. Set initial registers configuration for @cmu CMU clocks |
164 | * 3. Register @cmu CMU clocks using Samsung clock framework API |
165 | */ |
166 | void __init exynos_arm64_register_cmu(struct device *dev, |
167 | struct device_node *np, const struct samsung_cmu_info *cmu) |
168 | { |
169 | int err; |
170 | |
171 | /* |
172 | * Try to boot even if the parent clock enablement fails, as it might be |
173 | * already enabled by bootloader. |
174 | */ |
175 | err = exynos_arm64_enable_bus_clk(dev, np, cmu); |
176 | if (err) |
177 | pr_err("%s: could not enable bus clock %s; err = %d\n" , |
178 | __func__, cmu->clk_name, err); |
179 | |
180 | exynos_arm64_init_clocks(np, reg_offs: cmu->clk_regs, reg_offs_len: cmu->nr_clk_regs); |
181 | samsung_cmu_register_one(np, cmu); |
182 | } |
183 | |
184 | /** |
185 | * exynos_arm64_register_cmu_pm - Register Exynos CMU domain with PM support |
186 | * |
187 | * @pdev: Platform device object |
188 | * @set_manual: If true, set gate clocks to manual mode |
189 | * |
190 | * It's a version of exynos_arm64_register_cmu() with PM support. Should be |
191 | * called from probe function of platform driver. |
192 | * |
193 | * Return: 0 on success, or negative error code on error. |
194 | */ |
195 | int __init exynos_arm64_register_cmu_pm(struct platform_device *pdev, |
196 | bool set_manual) |
197 | { |
198 | const struct samsung_cmu_info *cmu; |
199 | struct device *dev = &pdev->dev; |
200 | struct device_node *np = dev->of_node; |
201 | struct exynos_arm64_cmu_data *data; |
202 | void __iomem *reg_base; |
203 | int ret; |
204 | |
205 | cmu = of_device_get_match_data(dev); |
206 | |
207 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
208 | if (!data) |
209 | return -ENOMEM; |
210 | |
211 | platform_set_drvdata(pdev, data); |
212 | |
213 | ret = exynos_arm64_cmu_prepare_pm(dev, cmu); |
214 | if (ret) |
215 | return ret; |
216 | |
217 | /* |
218 | * Try to boot even if the parent clock enablement fails, as it might be |
219 | * already enabled by bootloader. |
220 | */ |
221 | ret = exynos_arm64_enable_bus_clk(dev, NULL, cmu); |
222 | if (ret) |
223 | dev_err(dev, "%s: could not enable bus clock %s; err = %d\n" , |
224 | __func__, cmu->clk_name, ret); |
225 | |
226 | if (set_manual) |
227 | exynos_arm64_init_clocks(np, reg_offs: cmu->clk_regs, reg_offs_len: cmu->nr_clk_regs); |
228 | |
229 | reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
230 | if (IS_ERR(ptr: reg_base)) |
231 | return PTR_ERR(ptr: reg_base); |
232 | |
233 | data->ctx = samsung_clk_init(dev, base: reg_base, nr_clks: cmu->nr_clk_ids); |
234 | |
235 | /* |
236 | * Enable runtime PM here to allow the clock core using runtime PM |
237 | * for the registered clocks. Additionally, we increase the runtime |
238 | * PM usage count before registering the clocks, to prevent the |
239 | * clock core from runtime suspending the device. |
240 | */ |
241 | pm_runtime_get_noresume(dev); |
242 | pm_runtime_set_active(dev); |
243 | pm_runtime_enable(dev); |
244 | |
245 | samsung_cmu_register_clocks(ctx: data->ctx, cmu); |
246 | samsung_clk_of_add_provider(np: dev->of_node, ctx: data->ctx); |
247 | pm_runtime_put_sync(dev); |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | int exynos_arm64_cmu_suspend(struct device *dev) |
253 | { |
254 | struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
255 | int i; |
256 | |
257 | samsung_clk_save(base: data->ctx->reg_base, rd: data->clk_save, |
258 | num_regs: data->nr_clk_save); |
259 | |
260 | for (i = 0; i < data->nr_pclks; i++) |
261 | clk_prepare_enable(clk: data->pclks[i]); |
262 | |
263 | /* For suspend some registers have to be set to certain values */ |
264 | samsung_clk_restore(base: data->ctx->reg_base, rd: data->clk_suspend, |
265 | num_regs: data->nr_clk_suspend); |
266 | |
267 | for (i = 0; i < data->nr_pclks; i++) |
268 | clk_disable_unprepare(clk: data->pclks[i]); |
269 | |
270 | clk_disable_unprepare(clk: data->clk); |
271 | |
272 | return 0; |
273 | } |
274 | |
275 | int exynos_arm64_cmu_resume(struct device *dev) |
276 | { |
277 | struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
278 | int i; |
279 | |
280 | clk_prepare_enable(clk: data->clk); |
281 | |
282 | for (i = 0; i < data->nr_pclks; i++) |
283 | clk_prepare_enable(clk: data->pclks[i]); |
284 | |
285 | samsung_clk_restore(base: data->ctx->reg_base, rd: data->clk_save, |
286 | num_regs: data->nr_clk_save); |
287 | |
288 | for (i = 0; i < data->nr_pclks; i++) |
289 | clk_disable_unprepare(clk: data->pclks[i]); |
290 | |
291 | return 0; |
292 | } |
293 | |