1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 Free Electrons |
4 | * Copyright (C) 2016 NextThing Co |
5 | * |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
7 | */ |
8 | |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/regmap.h> |
11 | |
12 | #include "sun4i_tcon.h" |
13 | #include "sun4i_tcon_dclk.h" |
14 | |
15 | struct sun4i_dclk { |
16 | struct clk_hw hw; |
17 | struct regmap *regmap; |
18 | struct sun4i_tcon *tcon; |
19 | }; |
20 | |
21 | static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) |
22 | { |
23 | return container_of(hw, struct sun4i_dclk, hw); |
24 | } |
25 | |
26 | static void sun4i_dclk_disable(struct clk_hw *hw) |
27 | { |
28 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
29 | |
30 | regmap_update_bits(map: dclk->regmap, SUN4I_TCON0_DCLK_REG, |
31 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), val: 0); |
32 | } |
33 | |
34 | static int sun4i_dclk_enable(struct clk_hw *hw) |
35 | { |
36 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
37 | |
38 | return regmap_update_bits(map: dclk->regmap, SUN4I_TCON0_DCLK_REG, |
39 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), |
40 | BIT(SUN4I_TCON0_DCLK_GATE_BIT)); |
41 | } |
42 | |
43 | static int sun4i_dclk_is_enabled(struct clk_hw *hw) |
44 | { |
45 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
46 | u32 val; |
47 | |
48 | regmap_read(map: dclk->regmap, SUN4I_TCON0_DCLK_REG, val: &val); |
49 | |
50 | return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); |
51 | } |
52 | |
53 | static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, |
54 | unsigned long parent_rate) |
55 | { |
56 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
57 | u32 val; |
58 | |
59 | regmap_read(map: dclk->regmap, SUN4I_TCON0_DCLK_REG, val: &val); |
60 | |
61 | val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; |
62 | val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1; |
63 | |
64 | if (!val) |
65 | val = 1; |
66 | |
67 | return parent_rate / val; |
68 | } |
69 | |
70 | static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, |
71 | unsigned long *parent_rate) |
72 | { |
73 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
74 | struct sun4i_tcon *tcon = dclk->tcon; |
75 | unsigned long best_parent = 0; |
76 | u8 best_div = 1; |
77 | int i; |
78 | |
79 | for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) { |
80 | u64 ideal = (u64)rate * i; |
81 | unsigned long rounded; |
82 | |
83 | /* |
84 | * ideal has overflowed the max value that can be stored in an |
85 | * unsigned long, and every clk operation we might do on a |
86 | * truncated u64 value will give us incorrect results. |
87 | * Let's just stop there since bigger dividers will result in |
88 | * the same overflow issue. |
89 | */ |
90 | if (ideal > ULONG_MAX) |
91 | goto out; |
92 | |
93 | rounded = clk_hw_round_rate(hw: clk_hw_get_parent(hw), |
94 | rate: ideal); |
95 | |
96 | if (rounded == ideal) { |
97 | best_parent = rounded; |
98 | best_div = i; |
99 | goto out; |
100 | } |
101 | |
102 | if (abs(rate - rounded / i) < |
103 | abs(rate - best_parent / best_div)) { |
104 | best_parent = rounded; |
105 | best_div = i; |
106 | } |
107 | } |
108 | |
109 | out: |
110 | *parent_rate = best_parent; |
111 | |
112 | return best_parent / best_div; |
113 | } |
114 | |
115 | static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, |
116 | unsigned long parent_rate) |
117 | { |
118 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
119 | u8 div = parent_rate / rate; |
120 | |
121 | return regmap_update_bits(map: dclk->regmap, SUN4I_TCON0_DCLK_REG, |
122 | GENMASK(6, 0), val: div); |
123 | } |
124 | |
125 | static int sun4i_dclk_get_phase(struct clk_hw *hw) |
126 | { |
127 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
128 | u32 val; |
129 | |
130 | regmap_read(map: dclk->regmap, SUN4I_TCON0_IO_POL_REG, val: &val); |
131 | |
132 | val >>= 28; |
133 | val &= 3; |
134 | |
135 | return val * 120; |
136 | } |
137 | |
138 | static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) |
139 | { |
140 | struct sun4i_dclk *dclk = hw_to_dclk(hw); |
141 | u32 val = degrees / 120; |
142 | |
143 | val <<= 28; |
144 | |
145 | regmap_update_bits(map: dclk->regmap, SUN4I_TCON0_IO_POL_REG, |
146 | GENMASK(29, 28), |
147 | val); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static const struct clk_ops sun4i_dclk_ops = { |
153 | .disable = sun4i_dclk_disable, |
154 | .enable = sun4i_dclk_enable, |
155 | .is_enabled = sun4i_dclk_is_enabled, |
156 | |
157 | .recalc_rate = sun4i_dclk_recalc_rate, |
158 | .round_rate = sun4i_dclk_round_rate, |
159 | .set_rate = sun4i_dclk_set_rate, |
160 | |
161 | .get_phase = sun4i_dclk_get_phase, |
162 | .set_phase = sun4i_dclk_set_phase, |
163 | }; |
164 | |
165 | int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) |
166 | { |
167 | const char *clk_name, *parent_name; |
168 | struct clk_init_data init; |
169 | struct sun4i_dclk *dclk; |
170 | int ret; |
171 | |
172 | parent_name = __clk_get_name(clk: tcon->sclk0); |
173 | ret = of_property_read_string_index(np: dev->of_node, |
174 | propname: "clock-output-names" , index: 0, |
175 | output: &clk_name); |
176 | if (ret) |
177 | return ret; |
178 | |
179 | dclk = devm_kzalloc(dev, size: sizeof(*dclk), GFP_KERNEL); |
180 | if (!dclk) |
181 | return -ENOMEM; |
182 | dclk->tcon = tcon; |
183 | |
184 | init.name = clk_name; |
185 | init.ops = &sun4i_dclk_ops; |
186 | init.parent_names = &parent_name; |
187 | init.num_parents = 1; |
188 | init.flags = CLK_SET_RATE_PARENT; |
189 | |
190 | dclk->regmap = tcon->regs; |
191 | dclk->hw.init = &init; |
192 | |
193 | tcon->dclk = clk_register(dev, hw: &dclk->hw); |
194 | if (IS_ERR(ptr: tcon->dclk)) |
195 | return PTR_ERR(ptr: tcon->dclk); |
196 | |
197 | return 0; |
198 | } |
199 | EXPORT_SYMBOL(sun4i_dclk_create); |
200 | |
201 | int sun4i_dclk_free(struct sun4i_tcon *tcon) |
202 | { |
203 | clk_unregister(clk: tcon->dclk); |
204 | return 0; |
205 | } |
206 | EXPORT_SYMBOL(sun4i_dclk_free); |
207 | |