aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJernej Skrabec <jernej.skrabec@siol.net>2018-03-01 16:34:38 -0500
committerMaxime Ripard <maxime.ripard@bootlin.com>2018-03-02 02:45:49 -0500
commit4f86e81748fe230b9cf82a7fd69884c7a08ba9d1 (patch)
treea24d2d29845088099f740cdb1bb4a92ce3f3718b
parent6876b160b70eeab5c7e0c3888f4907502a1df4a7 (diff)
drm/sun4i: Add support for H3 HDMI PHY variant
While A83T HDMI PHY seems to be just customized Synopsys HDMI PHY, H3 HDMI PHY is completely custom PHY. However, they still have many things in common like clock and reset setup, setting sync polarity and more. Add support for H3 HDMI PHY variant. While documentation exists for this PHY variant, it doesn't go in great details. Because of that, almost all settings are copied from BSP linux 4.4. Interestingly, those settings are slightly different to those found in a older BSP with Linux 3.4. For now, no user visible difference was found between them. Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net> Signed-off-by: Maxime Ripard <maxime.ripard@bootlin.com> Link: https://patchwork.freedesktop.org/patch/msgid/20180301213442.16677-13-jernej.skrabec@siol.net
-rw-r--r--drivers/gpu/drm/sun4i/Makefile1
-rw-r--r--drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h6
-rw-r--r--drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c264
-rw-r--r--drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c132
4 files changed, 400 insertions, 3 deletions
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 1610e748119b..330843ce4280 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -12,6 +12,7 @@ sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
12 12
13sun8i-drm-hdmi-y += sun8i_dw_hdmi.o 13sun8i-drm-hdmi-y += sun8i_dw_hdmi.o
14sun8i-drm-hdmi-y += sun8i_hdmi_phy.o 14sun8i-drm-hdmi-y += sun8i_hdmi_phy.o
15sun8i-drm-hdmi-y += sun8i_hdmi_phy_clk.o
15 16
16sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ 17sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \
17 sun8i_vi_layer.o sun8i_ui_scaler.o \ 18 sun8i_vi_layer.o sun8i_ui_scaler.o \
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
index 49161326ea5a..79154f0f674a 100644
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
@@ -146,6 +146,7 @@
146struct sun8i_hdmi_phy; 146struct sun8i_hdmi_phy;
147 147
148struct sun8i_hdmi_phy_variant { 148struct sun8i_hdmi_phy_variant {
149 bool has_phy_clk;
149 void (*phy_init)(struct sun8i_hdmi_phy *phy); 150 void (*phy_init)(struct sun8i_hdmi_phy *phy);
150 void (*phy_disable)(struct dw_hdmi *hdmi, 151 void (*phy_disable)(struct dw_hdmi *hdmi,
151 struct sun8i_hdmi_phy *phy); 152 struct sun8i_hdmi_phy *phy);
@@ -157,6 +158,9 @@ struct sun8i_hdmi_phy_variant {
157struct sun8i_hdmi_phy { 158struct sun8i_hdmi_phy {
158 struct clk *clk_bus; 159 struct clk *clk_bus;
159 struct clk *clk_mod; 160 struct clk *clk_mod;
161 struct clk *clk_phy;
162 struct clk *clk_pll0;
163 unsigned int rcal;
160 struct regmap *regs; 164 struct regmap *regs;
161 struct reset_control *rst_phy; 165 struct reset_control *rst_phy;
162 struct sun8i_hdmi_phy_variant *variant; 166 struct sun8i_hdmi_phy_variant *variant;
@@ -184,4 +188,6 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
184void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy); 188void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
185const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void); 189const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
186 190
191int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
192
187#endif /* _SUN8I_DW_HDMI_H_ */ 193#endif /* _SUN8I_DW_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
index 16889bc0c62d..5a52fc489a9d 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
@@ -3,6 +3,7 @@
3 * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> 3 * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
4 */ 4 */
5 5
6#include <linux/delay.h>
6#include <linux/of_address.h> 7#include <linux/of_address.h>
7 8
8#include "sun8i_dw_hdmi.h" 9#include "sun8i_dw_hdmi.h"
@@ -73,7 +74,148 @@ static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
73 dw_hdmi_phy_gen2_txpwron(hdmi, 1); 74 dw_hdmi_phy_gen2_txpwron(hdmi, 1);
74 75
75 return 0; 76 return 0;
76}; 77}
78
79static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
80 struct sun8i_hdmi_phy *phy,
81 unsigned int clk_rate)
82{
83 u32 pll_cfg1_init;
84 u32 pll_cfg2_init;
85 u32 ana_cfg1_end;
86 u32 ana_cfg2_init;
87 u32 ana_cfg3_init;
88 u32 b_offset = 0;
89 u32 val;
90
91 /* bandwidth / frequency independent settings */
92
93 pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN |
94 SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN |
95 SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(7) |
96 SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(1) |
97 SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN |
98 SUN8I_HDMI_PHY_PLL_CFG1_CS |
99 SUN8I_HDMI_PHY_PLL_CFG1_CP_S(2) |
100 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63) |
101 SUN8I_HDMI_PHY_PLL_CFG1_BWS;
102
103 pll_cfg2_init = SUN8I_HDMI_PHY_PLL_CFG2_SV_H |
104 SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN |
105 SUN8I_HDMI_PHY_PLL_CFG2_SDIV2;
106
107 ana_cfg1_end = SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(1) |
108 SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT |
109 SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT |
110 SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT |
111 SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT |
112 SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL |
113 SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG |
114 SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS |
115 SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN |
116 SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK |
117 SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL |
118 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK |
119 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
120 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
121 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
122 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 |
123 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
124 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
125 SUN8I_HDMI_PHY_ANA_CFG1_CKEN |
126 SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
127 SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
128 SUN8I_HDMI_PHY_ANA_CFG1_ENBI;
129
130 ana_cfg2_init = SUN8I_HDMI_PHY_ANA_CFG2_M_EN |
131 SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK |
132 SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN |
133 SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(1) |
134 SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(1);
135
136 ana_cfg3_init = SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(0x3e0) |
137 SUN8I_HDMI_PHY_ANA_CFG3_SDAEN |
138 SUN8I_HDMI_PHY_ANA_CFG3_SCLEN;
139
140 /* bandwidth / frequency dependent settings */
141 if (clk_rate <= 27000000) {
142 pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
143 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
144 pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
145 SUN8I_HDMI_PHY_PLL_CFG2_S(4);
146 ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
147 ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
148 SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal);
149 ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(3) |
150 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(5);
151 } else if (clk_rate <= 74250000) {
152 pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
153 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
154 pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
155 SUN8I_HDMI_PHY_PLL_CFG2_S(5);
156 ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
157 ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
158 SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal);
159 ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(5) |
160 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(7);
161 } else if (clk_rate <= 148500000) {
162 pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
163 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
164 pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
165 SUN8I_HDMI_PHY_PLL_CFG2_S(6);
166 ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
167 SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
168 SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(2);
169 ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(7) |
170 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(9);
171 } else {
172 b_offset = 2;
173 pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63);
174 pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(6) |
175 SUN8I_HDMI_PHY_PLL_CFG2_S(7);
176 ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
177 SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
178 SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4);
179 ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(9) |
180 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(13);
181 }
182
183 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
184 SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
185
186 regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
187 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
188 (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
189 pll_cfg2_init);
190 usleep_range(10000, 15000);
191 regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG3_REG,
192 SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2);
193 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
194 SUN8I_HDMI_PHY_PLL_CFG1_PLLEN,
195 SUN8I_HDMI_PHY_PLL_CFG1_PLLEN);
196 msleep(100);
197
198 /* get B value */
199 regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val);
200 val = (val & SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK) >>
201 SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT;
202 val = min(val + b_offset, (u32)0x3f);
203
204 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
205 SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 |
206 SUN8I_HDMI_PHY_PLL_CFG1_REG_OD,
207 SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 |
208 SUN8I_HDMI_PHY_PLL_CFG1_REG_OD);
209 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
210 SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK,
211 val << SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT);
212 msleep(100);
213 regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, ana_cfg1_end);
214 regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG2_REG, ana_cfg2_init);
215 regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, ana_cfg3_init);
216
217 return 0;
218}
77 219
78static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data, 220static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
79 struct drm_display_mode *mode) 221 struct drm_display_mode *mode)
@@ -90,6 +232,9 @@ static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
90 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, 232 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
91 SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val); 233 SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
92 234
235 if (phy->variant->has_phy_clk)
236 clk_set_rate(phy->clk_phy, mode->crtc_clock * 1000);
237
93 return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000); 238 return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000);
94}; 239};
95 240
@@ -103,6 +248,16 @@ static void sun8i_hdmi_phy_disable_a83t(struct dw_hdmi *hdmi,
103 SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0); 248 SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
104} 249}
105 250
251static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi,
252 struct sun8i_hdmi_phy *phy)
253{
254 regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
255 SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
256 SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
257 SUN8I_HDMI_PHY_ANA_CFG1_ENBI);
258 regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
259}
260
106static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) 261static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
107{ 262{
108 struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data; 263 struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
@@ -133,6 +288,78 @@ static void sun8i_hdmi_phy_init_a83t(struct sun8i_hdmi_phy *phy)
133 SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR)); 288 SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR));
134} 289}
135 290
291static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
292{
293 unsigned int val;
294
295 regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, 0);
296 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
297 SUN8I_HDMI_PHY_ANA_CFG1_ENBI,
298 SUN8I_HDMI_PHY_ANA_CFG1_ENBI);
299 udelay(5);
300 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
301 SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN,
302 SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN);
303 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
304 SUN8I_HDMI_PHY_ANA_CFG1_ENVBS,
305 SUN8I_HDMI_PHY_ANA_CFG1_ENVBS);
306 usleep_range(10, 20);
307 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
308 SUN8I_HDMI_PHY_ANA_CFG1_LDOEN,
309 SUN8I_HDMI_PHY_ANA_CFG1_LDOEN);
310 udelay(5);
311 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
312 SUN8I_HDMI_PHY_ANA_CFG1_CKEN,
313 SUN8I_HDMI_PHY_ANA_CFG1_CKEN);
314 usleep_range(40, 100);
315 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
316 SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL,
317 SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL);
318 usleep_range(100, 200);
319 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
320 SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG,
321 SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG);
322 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
323 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
324 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
325 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2,
326 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
327 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
328 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2);
329
330 /* wait for calibration to finish */
331 regmap_read_poll_timeout(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, val,
332 (val & SUN8I_HDMI_PHY_ANA_STS_RCALEND2D),
333 100, 2000);
334
335 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
336 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK,
337 SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK);
338 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
339 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
340 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
341 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
342 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK,
343 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
344 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
345 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
346 SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK);
347
348 /* enable DDC communication */
349 regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG,
350 SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
351 SUN8I_HDMI_PHY_ANA_CFG3_SDAEN,
352 SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
353 SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
354
355 /* set HW control of CEC pins */
356 regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0);
357
358 /* read calibration data */
359 regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val);
360 phy->rcal = (val & SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK) >> 2;
361}
362
136void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy) 363void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy)
137{ 364{
138 /* enable read access to HDMI controller */ 365 /* enable read access to HDMI controller */
@@ -155,7 +382,7 @@ static struct regmap_config sun8i_hdmi_phy_regmap_config = {
155 .reg_bits = 32, 382 .reg_bits = 32,
156 .val_bits = 32, 383 .val_bits = 32,
157 .reg_stride = 4, 384 .reg_stride = 4,
158 .max_register = SUN8I_HDMI_PHY_UNSCRAMBLE_REG, 385 .max_register = SUN8I_HDMI_PHY_CEC_REG,
159 .name = "phy" 386 .name = "phy"
160}; 387};
161 388
@@ -165,11 +392,22 @@ static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
165 .phy_config = &sun8i_hdmi_phy_config_a83t, 392 .phy_config = &sun8i_hdmi_phy_config_a83t,
166}; 393};
167 394
395static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
396 .has_phy_clk = true,
397 .phy_init = &sun8i_hdmi_phy_init_h3,
398 .phy_disable = &sun8i_hdmi_phy_disable_h3,
399 .phy_config = &sun8i_hdmi_phy_config_h3,
400};
401
168static const struct of_device_id sun8i_hdmi_phy_of_table[] = { 402static const struct of_device_id sun8i_hdmi_phy_of_table[] = {
169 { 403 {
170 .compatible = "allwinner,sun8i-a83t-hdmi-phy", 404 .compatible = "allwinner,sun8i-a83t-hdmi-phy",
171 .data = &sun8i_a83t_hdmi_phy, 405 .data = &sun8i_a83t_hdmi_phy,
172 }, 406 },
407 {
408 .compatible = "allwinner,sun8i-h3-hdmi-phy",
409 .data = &sun8i_h3_hdmi_phy,
410 },
173 { /* sentinel */ } 411 { /* sentinel */ }
174}; 412};
175 413
@@ -226,11 +464,26 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
226 goto err_put_clk_bus; 464 goto err_put_clk_bus;
227 } 465 }
228 466
467 if (phy->variant->has_phy_clk) {
468 phy->clk_pll0 = of_clk_get_by_name(node, "pll-0");
469 if (IS_ERR(phy->clk_pll0)) {
470 dev_err(dev, "Could not get pll-0 clock\n");
471 ret = PTR_ERR(phy->clk_pll0);
472 goto err_put_clk_mod;
473 }
474
475 ret = sun8i_phy_clk_create(phy, dev);
476 if (ret) {
477 dev_err(dev, "Couldn't create the PHY clock\n");
478 goto err_put_clk_pll0;
479 }
480 }
481
229 phy->rst_phy = of_reset_control_get_shared(node, "phy"); 482 phy->rst_phy = of_reset_control_get_shared(node, "phy");
230 if (IS_ERR(phy->rst_phy)) { 483 if (IS_ERR(phy->rst_phy)) {
231 dev_err(dev, "Could not get phy reset control\n"); 484 dev_err(dev, "Could not get phy reset control\n");
232 ret = PTR_ERR(phy->rst_phy); 485 ret = PTR_ERR(phy->rst_phy);
233 goto err_put_clk_mod; 486 goto err_put_clk_pll0;
234 } 487 }
235 488
236 ret = reset_control_deassert(phy->rst_phy); 489 ret = reset_control_deassert(phy->rst_phy);
@@ -261,6 +514,9 @@ err_deassert_rst_phy:
261 reset_control_assert(phy->rst_phy); 514 reset_control_assert(phy->rst_phy);
262err_put_rst_phy: 515err_put_rst_phy:
263 reset_control_put(phy->rst_phy); 516 reset_control_put(phy->rst_phy);
517err_put_clk_pll0:
518 if (phy->variant->has_phy_clk)
519 clk_put(phy->clk_pll0);
264err_put_clk_mod: 520err_put_clk_mod:
265 clk_put(phy->clk_mod); 521 clk_put(phy->clk_mod);
266err_put_clk_bus: 522err_put_clk_bus:
@@ -280,6 +536,8 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
280 536
281 reset_control_put(phy->rst_phy); 537 reset_control_put(phy->rst_phy);
282 538
539 if (phy->variant->has_phy_clk)
540 clk_put(phy->clk_pll0);
283 clk_put(phy->clk_mod); 541 clk_put(phy->clk_mod);
284 clk_put(phy->clk_bus); 542 clk_put(phy->clk_bus);
285} 543}
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
new file mode 100644
index 000000000000..faea449812f8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
@@ -0,0 +1,132 @@
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
4 */
5
6#include <linux/clk-provider.h>
7
8#include "sun8i_dw_hdmi.h"
9
10struct sun8i_phy_clk {
11 struct clk_hw hw;
12 struct sun8i_hdmi_phy *phy;
13};
14
15static 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
20static 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
58static 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
71static 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
98static 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
104int 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}