2 * Copyright (C) 2016 Free Electrons
3 * Copyright (C) 2016 NextThing Co
5 * Maxime Ripard <maxime.ripard@free-electrons.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (at your option) any later version.
13 #include <linux/clk-provider.h>
15 #include "sun4i_tcon.h"
16 #include "sun4i_hdmi.h"
20 struct sun4i_hdmi
*hdmi
;
23 static inline struct sun4i_tmds
*hw_to_tmds(struct clk_hw
*hw
)
25 return container_of(hw
, struct sun4i_tmds
, hw
);
29 static unsigned long sun4i_tmds_calc_divider(unsigned long rate
,
30 unsigned long parent_rate
,
34 unsigned long best_rate
= 0;
38 for (m
= 1; m
< 16; m
++) {
41 for (d
= 1; d
< 3; d
++) {
42 unsigned long tmp_rate
;
44 tmp_rate
= parent_rate
/ m
/ d
;
50 (rate
- tmp_rate
) < (rate
- best_rate
)) {
67 static int sun4i_tmds_determine_rate(struct clk_hw
*hw
,
68 struct clk_rate_request
*req
)
70 struct clk_hw
*parent
;
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 parent
= clk_hw_get_parent_by_index(hw
, 0);
86 for (i
= 1; i
< 3; i
++) {
87 for (j
= 1; j
< 16; j
++) {
88 unsigned long ideal
= rate
* i
* j
;
89 unsigned long rounded
;
91 rounded
= clk_hw_round_rate(parent
, ideal
);
93 if (rounded
== ideal
) {
94 best_parent
= rounded
;
100 if (abs(rate
- rounded
/ i
) <
101 abs(rate
- best_parent
/ best_div
)) {
102 best_parent
= rounded
;
109 req
->rate
= best_parent
/ best_half
/ best_div
;
110 req
->best_parent_rate
= best_parent
;
111 req
->best_parent_hw
= parent
;
116 static unsigned long sun4i_tmds_recalc_rate(struct clk_hw
*hw
,
117 unsigned long parent_rate
)
119 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
122 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
123 if (reg
& SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
)
126 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
127 reg
= (reg
>> 4) & 0xf;
131 return parent_rate
/ reg
;
134 static int sun4i_tmds_set_rate(struct clk_hw
*hw
, unsigned long rate
,
135 unsigned long parent_rate
)
137 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
142 sun4i_tmds_calc_divider(rate
, parent_rate
, &div
, &half
);
144 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
145 reg
&= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
;
147 reg
|= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK
;
148 writel(reg
, tmds
->hdmi
->base
+ SUN4I_HDMI_PAD_CTRL1_REG
);
150 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
151 reg
&= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK
;
152 writel(reg
| SUN4I_HDMI_PLL_CTRL_DIV(div
),
153 tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_CTRL_REG
);
158 static u8
sun4i_tmds_get_parent(struct clk_hw
*hw
)
160 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
163 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
164 return ((reg
& SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK
) >>
165 SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT
);
168 static int sun4i_tmds_set_parent(struct clk_hw
*hw
, u8 index
)
170 struct sun4i_tmds
*tmds
= hw_to_tmds(hw
);
176 reg
= readl(tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
177 reg
&= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK
;
178 writel(reg
| SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index
),
179 tmds
->hdmi
->base
+ SUN4I_HDMI_PLL_DBG0_REG
);
184 static const struct clk_ops sun4i_tmds_ops
= {
185 .determine_rate
= sun4i_tmds_determine_rate
,
186 .recalc_rate
= sun4i_tmds_recalc_rate
,
187 .set_rate
= sun4i_tmds_set_rate
,
189 .get_parent
= sun4i_tmds_get_parent
,
190 .set_parent
= sun4i_tmds_set_parent
,
193 int sun4i_tmds_create(struct sun4i_hdmi
*hdmi
)
195 struct clk_init_data init
;
196 struct sun4i_tmds
*tmds
;
197 const char *parents
[2];
199 parents
[0] = __clk_get_name(hdmi
->pll0_clk
);
203 parents
[1] = __clk_get_name(hdmi
->pll1_clk
);
207 tmds
= devm_kzalloc(hdmi
->dev
, sizeof(*tmds
), GFP_KERNEL
);
211 init
.name
= "hdmi-tmds";
212 init
.ops
= &sun4i_tmds_ops
;
213 init
.parent_names
= parents
;
214 init
.num_parents
= 2;
215 init
.flags
= CLK_SET_RATE_PARENT
;
218 tmds
->hw
.init
= &init
;
220 hdmi
->tmds_clk
= devm_clk_register(hdmi
->dev
, &tmds
->hw
);
221 if (IS_ERR(hdmi
->tmds_clk
))
222 return PTR_ERR(hdmi
->tmds_clk
);