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>
12 #include "sun4i_hdmi.h"
16 struct sun4i_hdmi
*hdmi
;
21 static inline struct sun4i_tmds
*hw_to_tmds(struct clk_hw
*hw
)
23 return container_of(hw
, struct sun4i_tmds
, hw
);
27 static unsigned long sun4i_tmds_calc_divider(unsigned long rate
,
28 unsigned long parent_rate
,
33 unsigned long best_rate
= 0;
35 bool is_double
= false;
37 for (m
= div_offset
?: 1; m
< (16 + div_offset
); m
++) {
40 for (d
= 1; d
< 3; d
++) {
41 unsigned long tmp_rate
;
43 tmp_rate
= parent_rate
/ m
/ d
;
49 (rate
- tmp_rate
) < (rate
- best_rate
)) {
52 is_double
= (d
== 2) ? true : false;
66 static int sun4i_tmds_determine_rate(struct clk_hw
*hw
,
67 struct clk_rate_request
*req
)
69 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
70 struct clk_hw
*parent
= NULL
;
71 unsigned long best_parent
= 0;
72 unsigned long rate
= req
->rate
;
73 int best_div
= 1, best_half
= 1;
77 * We only consider PLL3, since the TCON is very likely to be
78 * clocked from it, and to have the same rate than our HDMI
79 * clock, so we should not need to do anything.
82 for (p
= 0; p
< clk_hw_get_num_parents(hw
); p
++) {
83 parent
= clk_hw_get_parent_by_index(hw
, p
);
87 for (i
= 1; i
< 3; i
++) {
88 for (j
= tmds
->div_offset
?: 1;
89 j
< (16 + tmds
->div_offset
); j
++) {
90 unsigned long ideal
= rate
* i
* j
;
91 unsigned long rounded
;
93 rounded
= clk_hw_round_rate(parent
, ideal
);
95 if (rounded
== ideal
) {
96 best_parent
= rounded
;
103 abs(rate
- rounded
/ i
/ j
) <
104 abs(rate
- best_parent
/ best_half
/
106 best_parent
= rounded
;
118 req
->rate
= best_parent
/ best_half
/ best_div
;
119 req
->best_parent_rate
= best_parent
;
120 req
->best_parent_hw
= parent
;
125 static unsigned long sun4i_tmds_recalc_rate(struct clk_hw
*hw
,
126 unsigned long parent_rate
)
128 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
131 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
132 if (reg
& SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
)
135 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
136 reg
= ((reg
>> 4) & 0xf) + tmds
->div_offset
;
140 return parent_rate
/ reg
;
143 static int sun4i_tmds_set_rate(struct clk_hw
*hw
, unsigned long rate
,
144 unsigned long parent_rate
)
146 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
151 sun4i_tmds_calc_divider(rate
, parent_rate
, tmds
->div_offset
,
154 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
155 reg
&= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
;
157 reg
|= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
;
158 writel(reg
, tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
160 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
161 reg
&= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK
;
162 writel(reg
| SUN4I_HDMI_PLL_CTRL_DIV(div
- tmds
->div_offset
),
163 tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
168 static u8
sun4i_tmds_get_parent(struct clk_hw
*hw
)
170 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
173 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
174 return ((reg
& SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK
) >>
175 SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT
);
178 static int sun4i_tmds_set_parent(struct clk_hw
*hw
, u8 index
)
180 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
186 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
187 reg
&= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK
;
188 writel(reg
| SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index
),
189 tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
194 static const struct clk_ops sun4i_tmds_ops
= {
195 .determine_rate
= sun4i_tmds_determine_rate
,
196 .recalc_rate
= sun4i_tmds_recalc_rate
,
197 .set_rate
= sun4i_tmds_set_rate
,
199 .get_parent
= sun4i_tmds_get_parent
,
200 .set_parent
= sun4i_tmds_set_parent
,
203 int sun4i_tmds_create(struct sun4i_hdmi
*hdmi
)
205 struct clk_init_data init
;
206 struct sun4i_tmds
*tmds
;
207 const char *parents
[2];
209 parents
[0] = __clk_get_name(hdmi
->pll0_clk
);
213 parents
[1] = __clk_get_name(hdmi
->pll1_clk
);
217 tmds
= devm_kzalloc(hdmi
->dev
, sizeof(*tmds
), GFP_KERNEL
);
221 init
.name
= "hdmi-tmds";
222 init
.ops
= &sun4i_tmds_ops
;
223 init
.parent_names
= parents
;
224 init
.num_parents
= 2;
225 init
.flags
= CLK_SET_RATE_PARENT
;
228 tmds
->hw
.init
= &init
;
229 tmds
->div_offset
= hdmi
->variant
->tmds_clk_div_offset
;
231 hdmi
->tmds_clk
= devm_clk_register(hdmi
->dev
, &tmds
->hw
);
232 if (IS_ERR(hdmi
->tmds_clk
))
233 return PTR_ERR(hdmi
->tmds_clk
);