1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
6 #include <linux/clk-provider.h>
8 #include "sun8i_dw_hdmi.h"
10 struct sun8i_phy_clk
{
12 struct sun8i_hdmi_phy
*phy
;
15 static inline struct sun8i_phy_clk
*hw_to_phy_clk(struct clk_hw
*hw
)
17 return container_of(hw
, struct sun8i_phy_clk
, hw
);
20 static int sun8i_phy_clk_determine_rate(struct clk_hw
*hw
,
21 struct clk_rate_request
*req
)
23 unsigned long rate
= req
->rate
;
24 unsigned long best_rate
= 0;
25 struct clk_hw
*best_parent
= NULL
;
26 struct clk_hw
*parent
;
30 for (p
= 0; p
< clk_hw_get_num_parents(hw
); p
++) {
31 parent
= clk_hw_get_parent_by_index(hw
, p
);
35 for (i
= 1; i
<= 16; i
++) {
36 unsigned long ideal
= rate
* i
;
37 unsigned long rounded
;
39 rounded
= clk_hw_round_rate(parent
, ideal
);
41 if (rounded
== ideal
) {
49 abs(rate
- rounded
/ i
) <
50 abs(rate
- best_rate
/ best_div
)) {
57 if (best_rate
/ best_div
== rate
)
61 req
->rate
= best_rate
/ best_div
;
62 req
->best_parent_rate
= best_rate
;
63 req
->best_parent_hw
= best_parent
;
68 static unsigned long sun8i_phy_clk_recalc_rate(struct clk_hw
*hw
,
69 unsigned long parent_rate
)
71 struct sun8i_phy_clk
*priv
= hw_to_phy_clk(hw
);
74 regmap_read(priv
->phy
->regs
, SUN8I_HDMI_PHY_PLL_CFG2_REG
, ®
);
75 reg
= ((reg
>> SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT
) &
76 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK
) + 1;
78 return parent_rate
/ reg
;
81 static int sun8i_phy_clk_set_rate(struct clk_hw
*hw
, unsigned long rate
,
82 unsigned long parent_rate
)
84 struct sun8i_phy_clk
*priv
= hw_to_phy_clk(hw
);
85 unsigned long best_rate
= 0;
88 for (m
= 1; m
<= 16; m
++) {
89 unsigned long tmp_rate
= parent_rate
/ m
;
95 (rate
- tmp_rate
) < (rate
- best_rate
)) {
101 regmap_update_bits(priv
->phy
->regs
, SUN8I_HDMI_PHY_PLL_CFG2_REG
,
102 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK
,
103 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(best_m
));
108 static u8
sun8i_phy_clk_get_parent(struct clk_hw
*hw
)
110 struct sun8i_phy_clk
*priv
= hw_to_phy_clk(hw
);
113 regmap_read(priv
->phy
->regs
, SUN8I_HDMI_PHY_PLL_CFG1_REG
, ®
);
114 reg
= (reg
& SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK
) >>
115 SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT
;
120 static int sun8i_phy_clk_set_parent(struct clk_hw
*hw
, u8 index
)
122 struct sun8i_phy_clk
*priv
= hw_to_phy_clk(hw
);
127 regmap_update_bits(priv
->phy
->regs
, SUN8I_HDMI_PHY_PLL_CFG1_REG
,
128 SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK
,
129 index
<< SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT
);
134 static const struct clk_ops sun8i_phy_clk_ops
= {
135 .determine_rate
= sun8i_phy_clk_determine_rate
,
136 .recalc_rate
= sun8i_phy_clk_recalc_rate
,
137 .set_rate
= sun8i_phy_clk_set_rate
,
139 .get_parent
= sun8i_phy_clk_get_parent
,
140 .set_parent
= sun8i_phy_clk_set_parent
,
143 int sun8i_phy_clk_create(struct sun8i_hdmi_phy
*phy
, struct device
*dev
,
146 struct clk_init_data init
;
147 struct sun8i_phy_clk
*priv
;
148 const char *parents
[2];
150 parents
[0] = __clk_get_name(phy
->clk_pll0
);
155 parents
[1] = __clk_get_name(phy
->clk_pll1
);
160 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
164 init
.name
= "hdmi-phy-clk";
165 init
.ops
= &sun8i_phy_clk_ops
;
166 init
.parent_names
= parents
;
167 init
.num_parents
= second_parent
? 2 : 1;
168 init
.flags
= CLK_SET_RATE_PARENT
;
171 priv
->hw
.init
= &init
;
173 phy
->clk_phy
= devm_clk_register(dev
, &priv
->hw
);
174 if (IS_ERR(phy
->clk_phy
))
175 return PTR_ERR(phy
->clk_phy
);