1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2016 Free Electrons
4 * Copyright (C) 2016 NextThing Co
6 * Maxime Ripard <maxime.ripard@free-electrons.com>
9 #include <linux/clk-provider.h>
10 #include <linux/regmap.h>
12 #include "sun4i_tcon.h"
13 #include "sun4i_dotclock.h"
17 struct regmap
*regmap
;
18 struct sun4i_tcon
*tcon
;
21 static inline struct sun4i_dclk
*hw_to_dclk(struct clk_hw
*hw
)
23 return container_of(hw
, struct sun4i_dclk
, hw
);
26 static void sun4i_dclk_disable(struct clk_hw
*hw
)
28 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
30 regmap_update_bits(dclk
->regmap
, SUN4I_TCON0_DCLK_REG
,
31 BIT(SUN4I_TCON0_DCLK_GATE_BIT
), 0);
34 static int sun4i_dclk_enable(struct clk_hw
*hw
)
36 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
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
));
43 static int sun4i_dclk_is_enabled(struct clk_hw
*hw
)
45 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
48 regmap_read(dclk
->regmap
, SUN4I_TCON0_DCLK_REG
, &val
);
50 return val
& BIT(SUN4I_TCON0_DCLK_GATE_BIT
);
53 static unsigned long sun4i_dclk_recalc_rate(struct clk_hw
*hw
,
54 unsigned long parent_rate
)
56 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
59 regmap_read(dclk
->regmap
, SUN4I_TCON0_DCLK_REG
, &val
);
61 val
>>= SUN4I_TCON0_DCLK_DIV_SHIFT
;
62 val
&= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH
) - 1;
67 return parent_rate
/ val
;
70 static long sun4i_dclk_round_rate(struct clk_hw
*hw
, unsigned long rate
,
71 unsigned long *parent_rate
)
73 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
74 struct sun4i_tcon
*tcon
= dclk
->tcon
;
75 unsigned long best_parent
= 0;
79 for (i
= tcon
->dclk_min_div
; i
<= tcon
->dclk_max_div
; i
++) {
80 u64 ideal
= (u64
)rate
* i
;
81 unsigned long rounded
;
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.
90 if (ideal
> ULONG_MAX
)
93 rounded
= clk_hw_round_rate(clk_hw_get_parent(hw
),
96 if (rounded
== ideal
) {
97 best_parent
= rounded
;
102 if (abs(rate
- rounded
/ i
) <
103 abs(rate
- best_parent
/ best_div
)) {
104 best_parent
= rounded
;
110 *parent_rate
= best_parent
;
112 return best_parent
/ best_div
;
115 static int sun4i_dclk_set_rate(struct clk_hw
*hw
, unsigned long rate
,
116 unsigned long parent_rate
)
118 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
119 u8 div
= parent_rate
/ rate
;
121 return regmap_update_bits(dclk
->regmap
, SUN4I_TCON0_DCLK_REG
,
125 static int sun4i_dclk_get_phase(struct clk_hw
*hw
)
127 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
130 regmap_read(dclk
->regmap
, SUN4I_TCON0_IO_POL_REG
, &val
);
138 static int sun4i_dclk_set_phase(struct clk_hw
*hw
, int degrees
)
140 struct sun4i_dclk
*dclk
= hw_to_dclk(hw
);
141 u32 val
= degrees
/ 120;
145 regmap_update_bits(dclk
->regmap
, SUN4I_TCON0_IO_POL_REG
,
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
,
157 .recalc_rate
= sun4i_dclk_recalc_rate
,
158 .round_rate
= sun4i_dclk_round_rate
,
159 .set_rate
= sun4i_dclk_set_rate
,
161 .get_phase
= sun4i_dclk_get_phase
,
162 .set_phase
= sun4i_dclk_set_phase
,
165 int sun4i_dclk_create(struct device
*dev
, struct sun4i_tcon
*tcon
)
167 const char *clk_name
, *parent_name
;
168 struct clk_init_data init
;
169 struct sun4i_dclk
*dclk
;
172 parent_name
= __clk_get_name(tcon
->sclk0
);
173 ret
= of_property_read_string_index(dev
->of_node
,
174 "clock-output-names", 0,
179 dclk
= devm_kzalloc(dev
, sizeof(*dclk
), GFP_KERNEL
);
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
;
190 dclk
->regmap
= tcon
->regs
;
191 dclk
->hw
.init
= &init
;
193 tcon
->dclk
= clk_register(dev
, &dclk
->hw
);
194 if (IS_ERR(tcon
->dclk
))
195 return PTR_ERR(tcon
->dclk
);
199 EXPORT_SYMBOL(sun4i_dclk_create
);
201 int sun4i_dclk_free(struct sun4i_tcon
*tcon
)
203 clk_unregister(tcon
->dclk
);
206 EXPORT_SYMBOL(sun4i_dclk_free
);