1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. |
4 | * Copyright 2017~2018 NXP |
5 | * |
6 | * Author: Dong Aisheng <aisheng.dong@nxp.com> |
7 | * |
8 | */ |
9 | |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include "clk.h" |
17 | |
18 | /** |
19 | * struct clk_pfdv2 - IMX PFD clock |
20 | * @hw: clock source |
21 | * @reg: PFD register address |
22 | * @gate_bit: Gate bit offset |
23 | * @vld_bit: Valid bit offset |
24 | * @frac_off: PLL Fractional Divider offset |
25 | */ |
26 | |
27 | struct clk_pfdv2 { |
28 | struct clk_hw hw; |
29 | void __iomem *reg; |
30 | u8 gate_bit; |
31 | u8 vld_bit; |
32 | u8 frac_off; |
33 | }; |
34 | |
35 | #define to_clk_pfdv2(_hw) container_of(_hw, struct clk_pfdv2, hw) |
36 | |
37 | #define CLK_PFDV2_FRAC_MASK 0x3f |
38 | |
39 | #define LOCK_TIMEOUT_US USEC_PER_MSEC |
40 | |
41 | static DEFINE_SPINLOCK(pfd_lock); |
42 | |
43 | static int clk_pfdv2_wait(struct clk_pfdv2 *pfd) |
44 | { |
45 | u32 val; |
46 | |
47 | return readl_poll_timeout(pfd->reg, val, val & (1 << pfd->vld_bit), |
48 | 0, LOCK_TIMEOUT_US); |
49 | } |
50 | |
51 | static int clk_pfdv2_enable(struct clk_hw *hw) |
52 | { |
53 | struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); |
54 | unsigned long flags; |
55 | u32 val; |
56 | |
57 | spin_lock_irqsave(&pfd_lock, flags); |
58 | val = readl_relaxed(pfd->reg); |
59 | val &= ~(1 << pfd->gate_bit); |
60 | writel_relaxed(val, pfd->reg); |
61 | spin_unlock_irqrestore(lock: &pfd_lock, flags); |
62 | |
63 | return clk_pfdv2_wait(pfd); |
64 | } |
65 | |
66 | static void clk_pfdv2_disable(struct clk_hw *hw) |
67 | { |
68 | struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); |
69 | unsigned long flags; |
70 | u32 val; |
71 | |
72 | spin_lock_irqsave(&pfd_lock, flags); |
73 | val = readl_relaxed(pfd->reg); |
74 | val |= (1 << pfd->gate_bit); |
75 | writel_relaxed(val, pfd->reg); |
76 | spin_unlock_irqrestore(lock: &pfd_lock, flags); |
77 | } |
78 | |
79 | static unsigned long clk_pfdv2_recalc_rate(struct clk_hw *hw, |
80 | unsigned long parent_rate) |
81 | { |
82 | struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); |
83 | u64 tmp = parent_rate; |
84 | u8 frac; |
85 | |
86 | frac = (readl_relaxed(pfd->reg) >> pfd->frac_off) |
87 | & CLK_PFDV2_FRAC_MASK; |
88 | |
89 | if (!frac) { |
90 | pr_debug("clk_pfdv2: %s invalid pfd frac value 0\n" , |
91 | clk_hw_get_name(hw)); |
92 | return 0; |
93 | } |
94 | |
95 | tmp *= 18; |
96 | do_div(tmp, frac); |
97 | |
98 | return tmp; |
99 | } |
100 | |
101 | static int clk_pfdv2_determine_rate(struct clk_hw *hw, |
102 | struct clk_rate_request *req) |
103 | { |
104 | unsigned long parent_rates[] = { |
105 | 480000000, |
106 | 528000000, |
107 | req->best_parent_rate |
108 | }; |
109 | unsigned long best_rate = -1UL, rate = req->rate; |
110 | unsigned long best_parent_rate = req->best_parent_rate; |
111 | u64 tmp; |
112 | u8 frac; |
113 | int i; |
114 | |
115 | for (i = 0; i < ARRAY_SIZE(parent_rates); i++) { |
116 | tmp = parent_rates[i]; |
117 | tmp = tmp * 18 + rate / 2; |
118 | do_div(tmp, rate); |
119 | frac = tmp; |
120 | |
121 | if (frac < 12) |
122 | frac = 12; |
123 | else if (frac > 35) |
124 | frac = 35; |
125 | |
126 | tmp = parent_rates[i]; |
127 | tmp *= 18; |
128 | do_div(tmp, frac); |
129 | |
130 | if (abs(tmp - req->rate) < abs(best_rate - req->rate)) { |
131 | best_rate = tmp; |
132 | best_parent_rate = parent_rates[i]; |
133 | } |
134 | } |
135 | |
136 | req->best_parent_rate = best_parent_rate; |
137 | req->rate = best_rate; |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | static int clk_pfdv2_is_enabled(struct clk_hw *hw) |
143 | { |
144 | struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); |
145 | |
146 | if (readl_relaxed(pfd->reg) & (1 << pfd->gate_bit)) |
147 | return 0; |
148 | |
149 | return 1; |
150 | } |
151 | |
152 | static int clk_pfdv2_set_rate(struct clk_hw *hw, unsigned long rate, |
153 | unsigned long parent_rate) |
154 | { |
155 | struct clk_pfdv2 *pfd = to_clk_pfdv2(hw); |
156 | unsigned long flags; |
157 | u64 tmp = parent_rate; |
158 | u32 val; |
159 | u8 frac; |
160 | |
161 | if (!rate) |
162 | return -EINVAL; |
163 | |
164 | /* |
165 | * PFD can NOT change rate without gating. |
166 | * as the PFDs may enabled in HW by default but no |
167 | * consumer used it, the enable count is '0', so the |
168 | * 'SET_RATE_GATE' can NOT help on blocking the set_rate |
169 | * ops especially for 'assigned-clock-xxx'. In order |
170 | * to simplify the case, just disable the PFD if it is |
171 | * enabled in HW but not in SW. |
172 | */ |
173 | if (clk_pfdv2_is_enabled(hw)) |
174 | clk_pfdv2_disable(hw); |
175 | |
176 | tmp = tmp * 18 + rate / 2; |
177 | do_div(tmp, rate); |
178 | frac = tmp; |
179 | if (frac < 12) |
180 | frac = 12; |
181 | else if (frac > 35) |
182 | frac = 35; |
183 | |
184 | spin_lock_irqsave(&pfd_lock, flags); |
185 | val = readl_relaxed(pfd->reg); |
186 | val &= ~(CLK_PFDV2_FRAC_MASK << pfd->frac_off); |
187 | val |= frac << pfd->frac_off; |
188 | writel_relaxed(val, pfd->reg); |
189 | spin_unlock_irqrestore(lock: &pfd_lock, flags); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static const struct clk_ops clk_pfdv2_ops = { |
195 | .enable = clk_pfdv2_enable, |
196 | .disable = clk_pfdv2_disable, |
197 | .recalc_rate = clk_pfdv2_recalc_rate, |
198 | .determine_rate = clk_pfdv2_determine_rate, |
199 | .set_rate = clk_pfdv2_set_rate, |
200 | .is_enabled = clk_pfdv2_is_enabled, |
201 | }; |
202 | |
203 | struct clk_hw *imx_clk_hw_pfdv2(enum imx_pfdv2_type type, const char *name, |
204 | const char *parent_name, void __iomem *reg, u8 idx) |
205 | { |
206 | struct clk_init_data init; |
207 | struct clk_pfdv2 *pfd; |
208 | struct clk_hw *hw; |
209 | int ret; |
210 | |
211 | WARN_ON(idx > 3); |
212 | |
213 | pfd = kzalloc(size: sizeof(*pfd), GFP_KERNEL); |
214 | if (!pfd) |
215 | return ERR_PTR(error: -ENOMEM); |
216 | |
217 | pfd->reg = reg; |
218 | pfd->gate_bit = (idx + 1) * 8 - 1; |
219 | pfd->vld_bit = pfd->gate_bit - 1; |
220 | pfd->frac_off = idx * 8; |
221 | |
222 | init.name = name; |
223 | init.ops = &clk_pfdv2_ops; |
224 | init.parent_names = &parent_name; |
225 | init.num_parents = 1; |
226 | if (type == IMX_PFDV2_IMX7ULP) |
227 | init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT; |
228 | else |
229 | init.flags = CLK_SET_RATE_GATE; |
230 | |
231 | pfd->hw.init = &init; |
232 | |
233 | hw = &pfd->hw; |
234 | ret = clk_hw_register(NULL, hw); |
235 | if (ret) { |
236 | kfree(objp: pfd); |
237 | hw = ERR_PTR(error: ret); |
238 | } |
239 | |
240 | return hw; |
241 | } |
242 | EXPORT_SYMBOL_GPL(imx_clk_hw_pfdv2); |
243 | |