1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2015 Maxime Ripard |
4 | * |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/io.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/spinlock.h> |
14 | |
15 | #define TCON_CH1_SCLK2_PARENTS 4 |
16 | |
17 | #define TCON_CH1_SCLK2_GATE_BIT BIT(31) |
18 | #define TCON_CH1_SCLK2_MUX_MASK 3 |
19 | #define TCON_CH1_SCLK2_MUX_SHIFT 24 |
20 | #define TCON_CH1_SCLK2_DIV_MASK 0xf |
21 | #define TCON_CH1_SCLK2_DIV_SHIFT 0 |
22 | |
23 | #define TCON_CH1_SCLK1_GATE_BIT BIT(15) |
24 | #define TCON_CH1_SCLK1_HALF_BIT BIT(11) |
25 | |
26 | struct tcon_ch1_clk { |
27 | struct clk_hw hw; |
28 | spinlock_t lock; |
29 | void __iomem *reg; |
30 | }; |
31 | |
32 | #define hw_to_tclk(hw) container_of(hw, struct tcon_ch1_clk, hw) |
33 | |
34 | static void tcon_ch1_disable(struct clk_hw *hw) |
35 | { |
36 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
37 | unsigned long flags; |
38 | u32 reg; |
39 | |
40 | spin_lock_irqsave(&tclk->lock, flags); |
41 | reg = readl(addr: tclk->reg); |
42 | reg &= ~(TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT); |
43 | writel(val: reg, addr: tclk->reg); |
44 | spin_unlock_irqrestore(lock: &tclk->lock, flags); |
45 | } |
46 | |
47 | static int tcon_ch1_enable(struct clk_hw *hw) |
48 | { |
49 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
50 | unsigned long flags; |
51 | u32 reg; |
52 | |
53 | spin_lock_irqsave(&tclk->lock, flags); |
54 | reg = readl(addr: tclk->reg); |
55 | reg |= TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT; |
56 | writel(val: reg, addr: tclk->reg); |
57 | spin_unlock_irqrestore(lock: &tclk->lock, flags); |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | static int tcon_ch1_is_enabled(struct clk_hw *hw) |
63 | { |
64 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
65 | u32 reg; |
66 | |
67 | reg = readl(addr: tclk->reg); |
68 | return reg & (TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT); |
69 | } |
70 | |
71 | static u8 tcon_ch1_get_parent(struct clk_hw *hw) |
72 | { |
73 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
74 | u32 reg; |
75 | |
76 | reg = readl(addr: tclk->reg) >> TCON_CH1_SCLK2_MUX_SHIFT; |
77 | reg &= reg >> TCON_CH1_SCLK2_MUX_MASK; |
78 | |
79 | return reg; |
80 | } |
81 | |
82 | static int tcon_ch1_set_parent(struct clk_hw *hw, u8 index) |
83 | { |
84 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
85 | unsigned long flags; |
86 | u32 reg; |
87 | |
88 | spin_lock_irqsave(&tclk->lock, flags); |
89 | reg = readl(addr: tclk->reg); |
90 | reg &= ~(TCON_CH1_SCLK2_MUX_MASK << TCON_CH1_SCLK2_MUX_SHIFT); |
91 | reg |= index << TCON_CH1_SCLK2_MUX_SHIFT; |
92 | writel(val: reg, addr: tclk->reg); |
93 | spin_unlock_irqrestore(lock: &tclk->lock, flags); |
94 | |
95 | return 0; |
96 | }; |
97 | |
98 | static unsigned long tcon_ch1_calc_divider(unsigned long rate, |
99 | unsigned long parent_rate, |
100 | u8 *div, |
101 | bool *half) |
102 | { |
103 | unsigned long best_rate = 0; |
104 | u8 best_m = 0, m; |
105 | bool is_double; |
106 | |
107 | for (m = 1; m < 16; m++) { |
108 | u8 d; |
109 | |
110 | for (d = 1; d < 3; d++) { |
111 | unsigned long tmp_rate; |
112 | |
113 | tmp_rate = parent_rate / m / d; |
114 | |
115 | if (tmp_rate > rate) |
116 | continue; |
117 | |
118 | if (!best_rate || |
119 | (rate - tmp_rate) < (rate - best_rate)) { |
120 | best_rate = tmp_rate; |
121 | best_m = m; |
122 | is_double = d; |
123 | } |
124 | } |
125 | } |
126 | |
127 | if (div && half) { |
128 | *div = best_m; |
129 | *half = is_double; |
130 | } |
131 | |
132 | return best_rate; |
133 | } |
134 | |
135 | static int tcon_ch1_determine_rate(struct clk_hw *hw, |
136 | struct clk_rate_request *req) |
137 | { |
138 | long best_rate = -EINVAL; |
139 | int i; |
140 | |
141 | for (i = 0; i < clk_hw_get_num_parents(hw); i++) { |
142 | unsigned long parent_rate; |
143 | unsigned long tmp_rate; |
144 | struct clk_hw *parent; |
145 | |
146 | parent = clk_hw_get_parent_by_index(hw, index: i); |
147 | if (!parent) |
148 | continue; |
149 | |
150 | parent_rate = clk_hw_get_rate(hw: parent); |
151 | |
152 | tmp_rate = tcon_ch1_calc_divider(rate: req->rate, parent_rate, |
153 | NULL, NULL); |
154 | |
155 | if (best_rate < 0 || |
156 | (req->rate - tmp_rate) < (req->rate - best_rate)) { |
157 | best_rate = tmp_rate; |
158 | req->best_parent_rate = parent_rate; |
159 | req->best_parent_hw = parent; |
160 | } |
161 | } |
162 | |
163 | if (best_rate < 0) |
164 | return best_rate; |
165 | |
166 | req->rate = best_rate; |
167 | return 0; |
168 | } |
169 | |
170 | static unsigned long tcon_ch1_recalc_rate(struct clk_hw *hw, |
171 | unsigned long parent_rate) |
172 | { |
173 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
174 | u32 reg; |
175 | |
176 | reg = readl(addr: tclk->reg); |
177 | |
178 | parent_rate /= (reg & TCON_CH1_SCLK2_DIV_MASK) + 1; |
179 | |
180 | if (reg & TCON_CH1_SCLK1_HALF_BIT) |
181 | parent_rate /= 2; |
182 | |
183 | return parent_rate; |
184 | } |
185 | |
186 | static int tcon_ch1_set_rate(struct clk_hw *hw, unsigned long rate, |
187 | unsigned long parent_rate) |
188 | { |
189 | struct tcon_ch1_clk *tclk = hw_to_tclk(hw); |
190 | unsigned long flags; |
191 | bool half; |
192 | u8 div_m; |
193 | u32 reg; |
194 | |
195 | tcon_ch1_calc_divider(rate, parent_rate, div: &div_m, half: &half); |
196 | |
197 | spin_lock_irqsave(&tclk->lock, flags); |
198 | reg = readl(addr: tclk->reg); |
199 | reg &= ~(TCON_CH1_SCLK2_DIV_MASK | TCON_CH1_SCLK1_HALF_BIT); |
200 | reg |= (div_m - 1) & TCON_CH1_SCLK2_DIV_MASK; |
201 | |
202 | if (half) |
203 | reg |= TCON_CH1_SCLK1_HALF_BIT; |
204 | |
205 | writel(val: reg, addr: tclk->reg); |
206 | spin_unlock_irqrestore(lock: &tclk->lock, flags); |
207 | |
208 | return 0; |
209 | } |
210 | |
211 | static const struct clk_ops tcon_ch1_ops = { |
212 | .disable = tcon_ch1_disable, |
213 | .enable = tcon_ch1_enable, |
214 | .is_enabled = tcon_ch1_is_enabled, |
215 | |
216 | .get_parent = tcon_ch1_get_parent, |
217 | .set_parent = tcon_ch1_set_parent, |
218 | |
219 | .determine_rate = tcon_ch1_determine_rate, |
220 | .recalc_rate = tcon_ch1_recalc_rate, |
221 | .set_rate = tcon_ch1_set_rate, |
222 | }; |
223 | |
224 | static void __init tcon_ch1_setup(struct device_node *node) |
225 | { |
226 | const char *parents[TCON_CH1_SCLK2_PARENTS]; |
227 | const char *clk_name = node->name; |
228 | struct clk_init_data init; |
229 | struct tcon_ch1_clk *tclk; |
230 | struct resource res; |
231 | struct clk *clk; |
232 | void __iomem *reg; |
233 | int ret; |
234 | |
235 | of_property_read_string(np: node, propname: "clock-output-names" , out_string: &clk_name); |
236 | |
237 | reg = of_io_request_and_map(device: node, index: 0, name: of_node_full_name(np: node)); |
238 | if (IS_ERR(ptr: reg)) { |
239 | pr_err("%s: Could not map the clock registers\n" , clk_name); |
240 | return; |
241 | } |
242 | |
243 | ret = of_clk_parent_fill(np: node, parents, TCON_CH1_SCLK2_PARENTS); |
244 | if (ret != TCON_CH1_SCLK2_PARENTS) { |
245 | pr_err("%s Could not retrieve the parents\n" , clk_name); |
246 | goto err_unmap; |
247 | } |
248 | |
249 | tclk = kzalloc(size: sizeof(*tclk), GFP_KERNEL); |
250 | if (!tclk) |
251 | goto err_unmap; |
252 | |
253 | init.name = clk_name; |
254 | init.ops = &tcon_ch1_ops; |
255 | init.parent_names = parents; |
256 | init.num_parents = TCON_CH1_SCLK2_PARENTS; |
257 | init.flags = CLK_SET_RATE_PARENT; |
258 | |
259 | tclk->reg = reg; |
260 | tclk->hw.init = &init; |
261 | spin_lock_init(&tclk->lock); |
262 | |
263 | clk = clk_register(NULL, hw: &tclk->hw); |
264 | if (IS_ERR(ptr: clk)) { |
265 | pr_err("%s: Couldn't register the clock\n" , clk_name); |
266 | goto err_free_data; |
267 | } |
268 | |
269 | ret = of_clk_add_provider(np: node, clk_src_get: of_clk_src_simple_get, data: clk); |
270 | if (ret) { |
271 | pr_err("%s: Couldn't register our clock provider\n" , clk_name); |
272 | goto err_unregister_clk; |
273 | } |
274 | |
275 | return; |
276 | |
277 | err_unregister_clk: |
278 | clk_unregister(clk); |
279 | err_free_data: |
280 | kfree(objp: tclk); |
281 | err_unmap: |
282 | iounmap(addr: reg); |
283 | of_address_to_resource(dev: node, index: 0, r: &res); |
284 | release_mem_region(res.start, resource_size(&res)); |
285 | } |
286 | |
287 | CLK_OF_DECLARE(tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk" , |
288 | tcon_ch1_setup); |
289 | |