1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2016 Maxime Ripard |
4 | * |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/device.h> |
11 | #include <linux/iopoll.h> |
12 | #include <linux/module.h> |
13 | #include <linux/slab.h> |
14 | |
15 | #include "ccu_common.h" |
16 | #include "ccu_gate.h" |
17 | #include "ccu_reset.h" |
18 | |
19 | struct sunxi_ccu { |
20 | const struct sunxi_ccu_desc *desc; |
21 | spinlock_t lock; |
22 | struct ccu_reset reset; |
23 | }; |
24 | |
25 | void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock) |
26 | { |
27 | void __iomem *addr; |
28 | u32 reg; |
29 | |
30 | if (!lock) |
31 | return; |
32 | |
33 | if (common->features & CCU_FEATURE_LOCK_REG) |
34 | addr = common->base + common->lock_reg; |
35 | else |
36 | addr = common->base + common->reg; |
37 | |
38 | WARN_ON(readl_relaxed_poll_timeout(addr, reg, reg & lock, 100, 70000)); |
39 | } |
40 | EXPORT_SYMBOL_NS_GPL(ccu_helper_wait_for_lock, SUNXI_CCU); |
41 | |
42 | bool ccu_is_better_rate(struct ccu_common *common, |
43 | unsigned long target_rate, |
44 | unsigned long current_rate, |
45 | unsigned long best_rate) |
46 | { |
47 | if (common->features & CCU_FEATURE_CLOSEST_RATE) |
48 | return abs(current_rate - target_rate) < abs(best_rate - target_rate); |
49 | |
50 | return current_rate <= target_rate && current_rate > best_rate; |
51 | } |
52 | EXPORT_SYMBOL_NS_GPL(ccu_is_better_rate, SUNXI_CCU); |
53 | |
54 | /* |
55 | * This clock notifier is called when the frequency of a PLL clock is |
56 | * changed. In common PLL designs, changes to the dividers take effect |
57 | * almost immediately, while changes to the multipliers (implemented |
58 | * as dividers in the feedback loop) take a few cycles to work into |
59 | * the feedback loop for the PLL to stablize. |
60 | * |
61 | * Sometimes when the PLL clock rate is changed, the decrease in the |
62 | * divider is too much for the decrease in the multiplier to catch up. |
63 | * The PLL clock rate will spike, and in some cases, might lock up |
64 | * completely. |
65 | * |
66 | * This notifier callback will gate and then ungate the clock, |
67 | * effectively resetting it, so it proceeds to work. Care must be |
68 | * taken to reparent consumers to other temporary clocks during the |
69 | * rate change, and that this notifier callback must be the first |
70 | * to be registered. |
71 | */ |
72 | static int ccu_pll_notifier_cb(struct notifier_block *nb, |
73 | unsigned long event, void *data) |
74 | { |
75 | struct ccu_pll_nb *pll = to_ccu_pll_nb(nb); |
76 | int ret = 0; |
77 | |
78 | if (event != POST_RATE_CHANGE) |
79 | goto out; |
80 | |
81 | ccu_gate_helper_disable(common: pll->common, gate: pll->enable); |
82 | |
83 | ret = ccu_gate_helper_enable(common: pll->common, gate: pll->enable); |
84 | if (ret) |
85 | goto out; |
86 | |
87 | ccu_helper_wait_for_lock(pll->common, pll->lock); |
88 | |
89 | out: |
90 | return notifier_from_errno(err: ret); |
91 | } |
92 | |
93 | int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb) |
94 | { |
95 | pll_nb->clk_nb.notifier_call = ccu_pll_notifier_cb; |
96 | |
97 | return clk_notifier_register(clk: pll_nb->common->hw.clk, |
98 | nb: &pll_nb->clk_nb); |
99 | } |
100 | EXPORT_SYMBOL_NS_GPL(ccu_pll_notifier_register, SUNXI_CCU); |
101 | |
102 | static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev, |
103 | struct device_node *node, void __iomem *reg, |
104 | const struct sunxi_ccu_desc *desc) |
105 | { |
106 | struct ccu_reset *reset; |
107 | int i, ret; |
108 | |
109 | ccu->desc = desc; |
110 | |
111 | spin_lock_init(&ccu->lock); |
112 | |
113 | for (i = 0; i < desc->num_ccu_clks; i++) { |
114 | struct ccu_common *cclk = desc->ccu_clks[i]; |
115 | |
116 | if (!cclk) |
117 | continue; |
118 | |
119 | cclk->base = reg; |
120 | cclk->lock = &ccu->lock; |
121 | } |
122 | |
123 | for (i = 0; i < desc->hw_clks->num ; i++) { |
124 | struct clk_hw *hw = desc->hw_clks->hws[i]; |
125 | const char *name; |
126 | |
127 | if (!hw) |
128 | continue; |
129 | |
130 | name = hw->init->name; |
131 | if (dev) |
132 | ret = clk_hw_register(dev, hw); |
133 | else |
134 | ret = of_clk_hw_register(node, hw); |
135 | if (ret) { |
136 | pr_err("Couldn't register clock %d - %s\n" , i, name); |
137 | goto err_clk_unreg; |
138 | } |
139 | } |
140 | |
141 | ret = of_clk_add_hw_provider(np: node, get: of_clk_hw_onecell_get, |
142 | data: desc->hw_clks); |
143 | if (ret) |
144 | goto err_clk_unreg; |
145 | |
146 | reset = &ccu->reset; |
147 | reset->rcdev.of_node = node; |
148 | reset->rcdev.ops = &ccu_reset_ops; |
149 | reset->rcdev.owner = dev ? dev->driver->owner : THIS_MODULE; |
150 | reset->rcdev.nr_resets = desc->num_resets; |
151 | reset->base = reg; |
152 | reset->lock = &ccu->lock; |
153 | reset->reset_map = desc->resets; |
154 | |
155 | ret = reset_controller_register(rcdev: &reset->rcdev); |
156 | if (ret) |
157 | goto err_del_provider; |
158 | |
159 | return 0; |
160 | |
161 | err_del_provider: |
162 | of_clk_del_provider(np: node); |
163 | err_clk_unreg: |
164 | while (--i >= 0) { |
165 | struct clk_hw *hw = desc->hw_clks->hws[i]; |
166 | |
167 | if (!hw) |
168 | continue; |
169 | clk_hw_unregister(hw); |
170 | } |
171 | return ret; |
172 | } |
173 | |
174 | static void devm_sunxi_ccu_release(struct device *dev, void *res) |
175 | { |
176 | struct sunxi_ccu *ccu = res; |
177 | const struct sunxi_ccu_desc *desc = ccu->desc; |
178 | int i; |
179 | |
180 | reset_controller_unregister(rcdev: &ccu->reset.rcdev); |
181 | of_clk_del_provider(np: dev->of_node); |
182 | |
183 | for (i = 0; i < desc->hw_clks->num; i++) { |
184 | struct clk_hw *hw = desc->hw_clks->hws[i]; |
185 | |
186 | if (!hw) |
187 | continue; |
188 | clk_hw_unregister(hw); |
189 | } |
190 | } |
191 | |
192 | int devm_sunxi_ccu_probe(struct device *dev, void __iomem *reg, |
193 | const struct sunxi_ccu_desc *desc) |
194 | { |
195 | struct sunxi_ccu *ccu; |
196 | int ret; |
197 | |
198 | ccu = devres_alloc(devm_sunxi_ccu_release, sizeof(*ccu), GFP_KERNEL); |
199 | if (!ccu) |
200 | return -ENOMEM; |
201 | |
202 | ret = sunxi_ccu_probe(ccu, dev, node: dev->of_node, reg, desc); |
203 | if (ret) { |
204 | devres_free(res: ccu); |
205 | return ret; |
206 | } |
207 | |
208 | devres_add(dev, res: ccu); |
209 | |
210 | return 0; |
211 | } |
212 | EXPORT_SYMBOL_NS_GPL(devm_sunxi_ccu_probe, SUNXI_CCU); |
213 | |
214 | void of_sunxi_ccu_probe(struct device_node *node, void __iomem *reg, |
215 | const struct sunxi_ccu_desc *desc) |
216 | { |
217 | struct sunxi_ccu *ccu; |
218 | int ret; |
219 | |
220 | ccu = kzalloc(size: sizeof(*ccu), GFP_KERNEL); |
221 | if (!ccu) |
222 | return; |
223 | |
224 | ret = sunxi_ccu_probe(ccu, NULL, node, reg, desc); |
225 | if (ret) { |
226 | pr_err("%pOF: probing clocks failed: %d\n" , node, ret); |
227 | kfree(objp: ccu); |
228 | } |
229 | } |
230 | |
231 | MODULE_LICENSE("GPL" ); |
232 | |