GNU Linux-libre 4.14.295-gnu1
[releases.git] / drivers / gpu / drm / sun4i / sun4i_hdmi_tmds_clk.c
1 /*
2  * Copyright (C) 2016 Free Electrons
3  * Copyright (C) 2016 NextThing Co
4  *
5  * Maxime Ripard <maxime.ripard@free-electrons.com>
6  *
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.
11  */
12
13 #include <linux/clk-provider.h>
14
15 #include "sun4i_tcon.h"
16 #include "sun4i_hdmi.h"
17
18 struct sun4i_tmds {
19         struct clk_hw           hw;
20         struct sun4i_hdmi       *hdmi;
21 };
22
23 static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
24 {
25         return container_of(hw, struct sun4i_tmds, hw);
26 }
27
28
29 static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
30                                              unsigned long parent_rate,
31                                              u8 *div,
32                                              bool *half)
33 {
34         unsigned long best_rate = 0;
35         u8 best_m = 0, m;
36         bool is_double;
37
38         for (m = 1; m < 16; m++) {
39                 u8 d;
40
41                 for (d = 1; d < 3; d++) {
42                         unsigned long tmp_rate;
43
44                         tmp_rate = parent_rate / m / d;
45
46                         if (tmp_rate > rate)
47                                 continue;
48
49                         if (!best_rate ||
50                             (rate - tmp_rate) < (rate - best_rate)) {
51                                 best_rate = tmp_rate;
52                                 best_m = m;
53                                 is_double = (d == 2) ? true : false;
54                         }
55                 }
56         }
57
58         if (div && half) {
59                 *div = best_m;
60                 *half = is_double;
61         }
62
63         return best_rate;
64 }
65
66
67 static int sun4i_tmds_determine_rate(struct clk_hw *hw,
68                                      struct clk_rate_request *req)
69 {
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;
74         int i, j;
75
76         /*
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.
80          */
81
82         parent = clk_hw_get_parent_by_index(hw, 0);
83         if (!parent)
84                 return -EINVAL;
85
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;
90
91                         rounded = clk_hw_round_rate(parent, ideal);
92
93                         if (rounded == ideal) {
94                                 best_parent = rounded;
95                                 best_half = i;
96                                 best_div = j;
97                                 goto out;
98                         }
99
100                         if (abs(rate - rounded / i) <
101                             abs(rate - best_parent / best_div)) {
102                                 best_parent = rounded;
103                                 best_div = i;
104                         }
105                 }
106         }
107
108 out:
109         req->rate = best_parent / best_half / best_div;
110         req->best_parent_rate = best_parent;
111         req->best_parent_hw = parent;
112
113         return 0;
114 }
115
116 static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
117                                             unsigned long parent_rate)
118 {
119         struct sun4i_tmds *tmds = hw_to_tmds(hw);
120         u32 reg;
121
122         reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
123         if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
124                 parent_rate /= 2;
125
126         reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
127         reg = (reg >> 4) & 0xf;
128         if (!reg)
129                 reg = 1;
130
131         return parent_rate / reg;
132 }
133
134 static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
135                                unsigned long parent_rate)
136 {
137         struct sun4i_tmds *tmds = hw_to_tmds(hw);
138         bool half;
139         u32 reg;
140         u8 div;
141
142         sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
143
144         reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
145         reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
146         if (half)
147                 reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
148         writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
149
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);
154
155         return 0;
156 }
157
158 static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
159 {
160         struct sun4i_tmds *tmds = hw_to_tmds(hw);
161         u32 reg;
162
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);
166 }
167
168 static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
169 {
170         struct sun4i_tmds *tmds = hw_to_tmds(hw);
171         u32 reg;
172
173         if (index > 1)
174                 return -EINVAL;
175
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);
180
181         return 0;
182 }
183
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,
188
189         .get_parent     = sun4i_tmds_get_parent,
190         .set_parent     = sun4i_tmds_set_parent,
191 };
192
193 int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
194 {
195         struct clk_init_data init;
196         struct sun4i_tmds *tmds;
197         const char *parents[2];
198
199         parents[0] = __clk_get_name(hdmi->pll0_clk);
200         if (!parents[0])
201                 return -ENODEV;
202
203         parents[1] = __clk_get_name(hdmi->pll1_clk);
204         if (!parents[1])
205                 return -ENODEV;
206
207         tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
208         if (!tmds)
209                 return -ENOMEM;
210
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;
216
217         tmds->hdmi = hdmi;
218         tmds->hw.init = &init;
219
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);
223
224         return 0;
225 }