]> Git Repo - linux.git/blob - drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
Merge tag 'drm-for-v4.17' of git://people.freedesktop.org/~airlied/linux
[linux.git] / drivers / gpu / drm / sun4i / sun8i_hdmi_phy_clk.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2018 Jernej Skrabec <[email protected]>
4  */
5
6 #include <linux/clk-provider.h>
7
8 #include "sun8i_dw_hdmi.h"
9
10 struct sun8i_phy_clk {
11         struct clk_hw           hw;
12         struct sun8i_hdmi_phy   *phy;
13 };
14
15 static inline struct sun8i_phy_clk *hw_to_phy_clk(struct clk_hw *hw)
16 {
17         return container_of(hw, struct sun8i_phy_clk, hw);
18 }
19
20 static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
21                                         struct clk_rate_request *req)
22 {
23         unsigned long rate = req->rate;
24         unsigned long best_rate = 0;
25         struct clk_hw *parent;
26         int best_div = 1;
27         int i;
28
29         parent = clk_hw_get_parent(hw);
30
31         for (i = 1; i <= 16; i++) {
32                 unsigned long ideal = rate * i;
33                 unsigned long rounded;
34
35                 rounded = clk_hw_round_rate(parent, ideal);
36
37                 if (rounded == ideal) {
38                         best_rate = rounded;
39                         best_div = i;
40                         break;
41                 }
42
43                 if (!best_rate ||
44                     abs(rate - rounded / i) <
45                     abs(rate - best_rate / best_div)) {
46                         best_rate = rounded;
47                         best_div = i;
48                 }
49         }
50
51         req->rate = best_rate / best_div;
52         req->best_parent_rate = best_rate;
53         req->best_parent_hw = parent;
54
55         return 0;
56 }
57
58 static unsigned long sun8i_phy_clk_recalc_rate(struct clk_hw *hw,
59                                                unsigned long parent_rate)
60 {
61         struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
62         u32 reg;
63
64         regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, &reg);
65         reg = ((reg >> SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT) &
66                 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK) + 1;
67
68         return parent_rate / reg;
69 }
70
71 static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
72                                   unsigned long parent_rate)
73 {
74         struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
75         unsigned long best_rate = 0;
76         u8 best_m = 0, m;
77
78         for (m = 1; m <= 16; m++) {
79                 unsigned long tmp_rate = parent_rate / m;
80
81                 if (tmp_rate > rate)
82                         continue;
83
84                 if (!best_rate ||
85                     (rate - tmp_rate) < (rate - best_rate)) {
86                         best_rate = tmp_rate;
87                         best_m = m;
88                 }
89         }
90
91         regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
92                            SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
93                            SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(best_m));
94
95         return 0;
96 }
97
98 static const struct clk_ops sun8i_phy_clk_ops = {
99         .determine_rate = sun8i_phy_clk_determine_rate,
100         .recalc_rate    = sun8i_phy_clk_recalc_rate,
101         .set_rate       = sun8i_phy_clk_set_rate,
102 };
103
104 int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
105 {
106         struct clk_init_data init;
107         struct sun8i_phy_clk *priv;
108         const char *parents[1];
109
110         parents[0] = __clk_get_name(phy->clk_pll0);
111         if (!parents[0])
112                 return -ENODEV;
113
114         priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
115         if (!priv)
116                 return -ENOMEM;
117
118         init.name = "hdmi-phy-clk";
119         init.ops = &sun8i_phy_clk_ops;
120         init.parent_names = parents;
121         init.num_parents = 1;
122         init.flags = CLK_SET_RATE_PARENT;
123
124         priv->phy = phy;
125         priv->hw.init = &init;
126
127         phy->clk_phy = devm_clk_register(dev, &priv->hw);
128         if (IS_ERR(phy->clk_phy))
129                 return PTR_ERR(phy->clk_phy);
130
131         return 0;
132 }
This page took 0.040693 seconds and 4 git commands to generate.