GNU Linux-libre 5.19-rc6-gnu
[releases.git] / drivers / gpu / drm / sun4i / sun4i_dotclock.c
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_dotclock.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(dclk->regmap, SUN4I_TCON0_DCLK_REG,
31                            BIT(SUN4I_TCON0_DCLK_GATE_BIT), 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(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(dclk->regmap, SUN4I_TCON0_DCLK_REG, &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(dclk->regmap, SUN4I_TCON0_DCLK_REG, &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(clk_hw_get_parent(hw),
94                                             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(dclk->regmap, SUN4I_TCON0_DCLK_REG,
122                                   GENMASK(6, 0), 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(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &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(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(tcon->sclk0);
173         ret = of_property_read_string_index(dev->of_node,
174                                             "clock-output-names", 0,
175                                             &clk_name);
176         if (ret)
177                 return ret;
178
179         dclk = devm_kzalloc(dev, 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, &dclk->hw);
194         if (IS_ERR(tcon->dclk))
195                 return PTR_ERR(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(tcon->dclk);
204         return 0;
205 }
206 EXPORT_SYMBOL(sun4i_dclk_free);