1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> |
4 | */ |
5 | |
6 | #include <linux/delay.h> |
7 | #include <linux/of.h> |
8 | #include <linux/of_platform.h> |
9 | #include <linux/platform_device.h> |
10 | |
11 | #include "sun8i_dw_hdmi.h" |
12 | |
13 | /* |
14 | * Address can be actually any value. Here is set to same value as |
15 | * it is set in BSP driver. |
16 | */ |
17 | #define I2C_ADDR 0x69 |
18 | |
19 | static const struct dw_hdmi_mpll_config sun50i_h6_mpll_cfg[] = { |
20 | { |
21 | 30666000, { |
22 | { 0x00b3, 0x0000 }, |
23 | { 0x2153, 0x0000 }, |
24 | { 0x40f3, 0x0000 }, |
25 | }, |
26 | }, { |
27 | 36800000, { |
28 | { 0x00b3, 0x0000 }, |
29 | { 0x2153, 0x0000 }, |
30 | { 0x40a2, 0x0001 }, |
31 | }, |
32 | }, { |
33 | 46000000, { |
34 | { 0x00b3, 0x0000 }, |
35 | { 0x2142, 0x0001 }, |
36 | { 0x40a2, 0x0001 }, |
37 | }, |
38 | }, { |
39 | 61333000, { |
40 | { 0x0072, 0x0001 }, |
41 | { 0x2142, 0x0001 }, |
42 | { 0x40a2, 0x0001 }, |
43 | }, |
44 | }, { |
45 | 73600000, { |
46 | { 0x0072, 0x0001 }, |
47 | { 0x2142, 0x0001 }, |
48 | { 0x4061, 0x0002 }, |
49 | }, |
50 | }, { |
51 | 92000000, { |
52 | { 0x0072, 0x0001 }, |
53 | { 0x2145, 0x0002 }, |
54 | { 0x4061, 0x0002 }, |
55 | }, |
56 | }, { |
57 | 122666000, { |
58 | { 0x0051, 0x0002 }, |
59 | { 0x2145, 0x0002 }, |
60 | { 0x4061, 0x0002 }, |
61 | }, |
62 | }, { |
63 | 147200000, { |
64 | { 0x0051, 0x0002 }, |
65 | { 0x2145, 0x0002 }, |
66 | { 0x4064, 0x0003 }, |
67 | }, |
68 | }, { |
69 | 184000000, { |
70 | { 0x0051, 0x0002 }, |
71 | { 0x214c, 0x0003 }, |
72 | { 0x4064, 0x0003 }, |
73 | }, |
74 | }, { |
75 | 226666000, { |
76 | { 0x0040, 0x0003 }, |
77 | { 0x214c, 0x0003 }, |
78 | { 0x4064, 0x0003 }, |
79 | }, |
80 | }, { |
81 | 272000000, { |
82 | { 0x0040, 0x0003 }, |
83 | { 0x214c, 0x0003 }, |
84 | { 0x5a64, 0x0003 }, |
85 | }, |
86 | }, { |
87 | 340000000, { |
88 | { 0x0040, 0x0003 }, |
89 | { 0x3b4c, 0x0003 }, |
90 | { 0x5a64, 0x0003 }, |
91 | }, |
92 | }, { |
93 | 594000000, { |
94 | { 0x1a40, 0x0003 }, |
95 | { 0x3b4c, 0x0003 }, |
96 | { 0x5a64, 0x0003 }, |
97 | }, |
98 | }, { |
99 | ~0UL, { |
100 | { 0x0000, 0x0000 }, |
101 | { 0x0000, 0x0000 }, |
102 | { 0x0000, 0x0000 }, |
103 | }, |
104 | } |
105 | }; |
106 | |
107 | static const struct dw_hdmi_curr_ctrl sun50i_h6_cur_ctr[] = { |
108 | /* pixelclk bpp8 bpp10 bpp12 */ |
109 | { 27000000, { 0x0012, 0x0000, 0x0000 }, }, |
110 | { 74250000, { 0x0013, 0x001a, 0x001b }, }, |
111 | { 148500000, { 0x0019, 0x0033, 0x0034 }, }, |
112 | { 297000000, { 0x0019, 0x001b, 0x001b }, }, |
113 | { 594000000, { 0x0010, 0x001b, 0x001b }, }, |
114 | { ~0UL, { 0x0000, 0x0000, 0x0000 }, } |
115 | }; |
116 | |
117 | static const struct dw_hdmi_phy_config sun50i_h6_phy_config[] = { |
118 | /*pixelclk symbol term vlev*/ |
119 | { 27000000, 0x8009, 0x0007, 0x02b0 }, |
120 | { 74250000, 0x8009, 0x0006, 0x022d }, |
121 | { 148500000, 0x8029, 0x0006, 0x0270 }, |
122 | { 297000000, 0x8039, 0x0005, 0x01ab }, |
123 | { 594000000, 0x8029, 0x0000, 0x008a }, |
124 | { ~0UL, 0x0000, 0x0000, 0x0000} |
125 | }; |
126 | |
127 | static void sun8i_hdmi_phy_set_polarity(struct sun8i_hdmi_phy *phy, |
128 | const struct drm_display_mode *mode) |
129 | { |
130 | u32 val = 0; |
131 | |
132 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) |
133 | val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC; |
134 | |
135 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) |
136 | val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC; |
137 | |
138 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, |
139 | SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val); |
140 | }; |
141 | |
142 | static int sun8i_a83t_hdmi_phy_config(struct dw_hdmi *hdmi, void *data, |
143 | const struct drm_display_info *display, |
144 | const struct drm_display_mode *mode) |
145 | { |
146 | unsigned int clk_rate = mode->crtc_clock * 1000; |
147 | struct sun8i_hdmi_phy *phy = data; |
148 | |
149 | sun8i_hdmi_phy_set_polarity(phy, mode); |
150 | |
151 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, |
152 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, |
153 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN); |
154 | |
155 | /* power down */ |
156 | dw_hdmi_phy_gen2_txpwron(hdmi, enable: 0); |
157 | dw_hdmi_phy_gen2_pddq(hdmi, enable: 1); |
158 | |
159 | dw_hdmi_phy_gen2_reset(hdmi); |
160 | |
161 | dw_hdmi_phy_gen2_pddq(hdmi, enable: 0); |
162 | |
163 | dw_hdmi_phy_i2c_set_addr(hdmi, I2C_ADDR); |
164 | |
165 | /* |
166 | * Values are taken from BSP HDMI driver. Although AW didn't |
167 | * release any documentation, explanation of this values can |
168 | * be found in i.MX 6Dual/6Quad Reference Manual. |
169 | */ |
170 | if (clk_rate <= 27000000) { |
171 | dw_hdmi_phy_i2c_write(hdmi, data: 0x01e0, addr: 0x06); |
172 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x15); |
173 | dw_hdmi_phy_i2c_write(hdmi, data: 0x08da, addr: 0x10); |
174 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0007, addr: 0x19); |
175 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0318, addr: 0x0e); |
176 | dw_hdmi_phy_i2c_write(hdmi, data: 0x8009, addr: 0x09); |
177 | } else if (clk_rate <= 74250000) { |
178 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0540, addr: 0x06); |
179 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0005, addr: 0x15); |
180 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x10); |
181 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0007, addr: 0x19); |
182 | dw_hdmi_phy_i2c_write(hdmi, data: 0x02b5, addr: 0x0e); |
183 | dw_hdmi_phy_i2c_write(hdmi, data: 0x8009, addr: 0x09); |
184 | } else if (clk_rate <= 148500000) { |
185 | dw_hdmi_phy_i2c_write(hdmi, data: 0x04a0, addr: 0x06); |
186 | dw_hdmi_phy_i2c_write(hdmi, data: 0x000a, addr: 0x15); |
187 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x10); |
188 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0002, addr: 0x19); |
189 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0021, addr: 0x0e); |
190 | dw_hdmi_phy_i2c_write(hdmi, data: 0x8029, addr: 0x09); |
191 | } else { |
192 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x06); |
193 | dw_hdmi_phy_i2c_write(hdmi, data: 0x000f, addr: 0x15); |
194 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x10); |
195 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0002, addr: 0x19); |
196 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x0e); |
197 | dw_hdmi_phy_i2c_write(hdmi, data: 0x802b, addr: 0x09); |
198 | } |
199 | |
200 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x1e); |
201 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x13); |
202 | dw_hdmi_phy_i2c_write(hdmi, data: 0x0000, addr: 0x17); |
203 | |
204 | dw_hdmi_phy_gen2_txpwron(hdmi, enable: 1); |
205 | |
206 | return 0; |
207 | } |
208 | |
209 | static void sun8i_a83t_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) |
210 | { |
211 | struct sun8i_hdmi_phy *phy = data; |
212 | |
213 | dw_hdmi_phy_gen2_txpwron(hdmi, enable: 0); |
214 | dw_hdmi_phy_gen2_pddq(hdmi, enable: 1); |
215 | |
216 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, |
217 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, val: 0); |
218 | } |
219 | |
220 | static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = { |
221 | .init = sun8i_a83t_hdmi_phy_config, |
222 | .disable = sun8i_a83t_hdmi_phy_disable, |
223 | .read_hpd = dw_hdmi_phy_read_hpd, |
224 | .update_hpd = dw_hdmi_phy_update_hpd, |
225 | .setup_hpd = dw_hdmi_phy_setup_hpd, |
226 | }; |
227 | |
228 | static int sun8i_h3_hdmi_phy_config(struct dw_hdmi *hdmi, void *data, |
229 | const struct drm_display_info *display, |
230 | const struct drm_display_mode *mode) |
231 | { |
232 | unsigned int clk_rate = mode->crtc_clock * 1000; |
233 | struct sun8i_hdmi_phy *phy = data; |
234 | u32 pll_cfg1_init; |
235 | u32 pll_cfg2_init; |
236 | u32 ana_cfg1_end; |
237 | u32 ana_cfg2_init; |
238 | u32 ana_cfg3_init; |
239 | u32 b_offset = 0; |
240 | u32 val; |
241 | |
242 | if (phy->variant->has_phy_clk) |
243 | clk_set_rate(clk: phy->clk_phy, rate: clk_rate); |
244 | |
245 | sun8i_hdmi_phy_set_polarity(phy, mode); |
246 | |
247 | /* bandwidth / frequency independent settings */ |
248 | |
249 | pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN | |
250 | SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN | |
251 | SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(7) | |
252 | SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(1) | |
253 | SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN | |
254 | SUN8I_HDMI_PHY_PLL_CFG1_CS | |
255 | SUN8I_HDMI_PHY_PLL_CFG1_CP_S(2) | |
256 | SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63) | |
257 | SUN8I_HDMI_PHY_PLL_CFG1_BWS; |
258 | |
259 | pll_cfg2_init = SUN8I_HDMI_PHY_PLL_CFG2_SV_H | |
260 | SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN | |
261 | SUN8I_HDMI_PHY_PLL_CFG2_SDIV2; |
262 | |
263 | ana_cfg1_end = SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(1) | |
264 | SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT | |
265 | SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT | |
266 | SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT | |
267 | SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT | |
268 | SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL | |
269 | SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG | |
270 | SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS | |
271 | SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN | |
272 | SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK | |
273 | SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL | |
274 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK | |
275 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | |
276 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | |
277 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | |
278 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 | |
279 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | |
280 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | |
281 | SUN8I_HDMI_PHY_ANA_CFG1_CKEN | |
282 | SUN8I_HDMI_PHY_ANA_CFG1_LDOEN | |
283 | SUN8I_HDMI_PHY_ANA_CFG1_ENVBS | |
284 | SUN8I_HDMI_PHY_ANA_CFG1_ENBI; |
285 | |
286 | ana_cfg2_init = SUN8I_HDMI_PHY_ANA_CFG2_M_EN | |
287 | SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK | |
288 | SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN | |
289 | SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(1) | |
290 | SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(1); |
291 | |
292 | ana_cfg3_init = SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(0x3e0) | |
293 | SUN8I_HDMI_PHY_ANA_CFG3_SDAEN | |
294 | SUN8I_HDMI_PHY_ANA_CFG3_SCLEN; |
295 | |
296 | /* bandwidth / frequency dependent settings */ |
297 | if (clk_rate <= 27000000) { |
298 | pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | |
299 | SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); |
300 | pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | |
301 | SUN8I_HDMI_PHY_PLL_CFG2_S(4); |
302 | ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW; |
303 | ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) | |
304 | SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal); |
305 | ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(3) | |
306 | SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(5); |
307 | } else if (clk_rate <= 74250000) { |
308 | pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | |
309 | SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); |
310 | pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | |
311 | SUN8I_HDMI_PHY_PLL_CFG2_S(5); |
312 | ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW; |
313 | ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) | |
314 | SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal); |
315 | ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(5) | |
316 | SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(7); |
317 | } else if (clk_rate <= 148500000) { |
318 | pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | |
319 | SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); |
320 | pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | |
321 | SUN8I_HDMI_PHY_PLL_CFG2_S(6); |
322 | ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK | |
323 | SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW | |
324 | SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(2); |
325 | ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(7) | |
326 | SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(9); |
327 | } else { |
328 | b_offset = 2; |
329 | pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63); |
330 | pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(6) | |
331 | SUN8I_HDMI_PHY_PLL_CFG2_S(7); |
332 | ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK | |
333 | SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW | |
334 | SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4); |
335 | ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(9) | |
336 | SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(13) | |
337 | SUN8I_HDMI_PHY_ANA_CFG3_REG_EMP(3); |
338 | } |
339 | |
340 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
341 | SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, val: 0); |
342 | |
343 | /* |
344 | * NOTE: We have to be careful not to overwrite PHY parent |
345 | * clock selection bit and clock divider. |
346 | */ |
347 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, |
348 | mask: (u32)~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, |
349 | val: pll_cfg1_init); |
350 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, |
351 | mask: (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK, |
352 | val: pll_cfg2_init); |
353 | usleep_range(min: 10000, max: 15000); |
354 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG3_REG, |
355 | SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2); |
356 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, |
357 | SUN8I_HDMI_PHY_PLL_CFG1_PLLEN, |
358 | SUN8I_HDMI_PHY_PLL_CFG1_PLLEN); |
359 | msleep(msecs: 100); |
360 | |
361 | /* get B value */ |
362 | regmap_read(map: phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, val: &val); |
363 | val = (val & SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK) >> |
364 | SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT; |
365 | val = min(val + b_offset, (u32)0x3f); |
366 | |
367 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, |
368 | SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 | |
369 | SUN8I_HDMI_PHY_PLL_CFG1_REG_OD, |
370 | SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 | |
371 | SUN8I_HDMI_PHY_PLL_CFG1_REG_OD); |
372 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, |
373 | SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK, |
374 | val: val << SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT); |
375 | msleep(msecs: 100); |
376 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, val: ana_cfg1_end); |
377 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG2_REG, val: ana_cfg2_init); |
378 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, val: ana_cfg3_init); |
379 | |
380 | return 0; |
381 | } |
382 | |
383 | static void sun8i_h3_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) |
384 | { |
385 | struct sun8i_hdmi_phy *phy = data; |
386 | |
387 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
388 | SUN8I_HDMI_PHY_ANA_CFG1_LDOEN | |
389 | SUN8I_HDMI_PHY_ANA_CFG1_ENVBS | |
390 | SUN8I_HDMI_PHY_ANA_CFG1_ENBI); |
391 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, val: 0); |
392 | } |
393 | |
394 | static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = { |
395 | .init = sun8i_h3_hdmi_phy_config, |
396 | .disable = sun8i_h3_hdmi_phy_disable, |
397 | .read_hpd = dw_hdmi_phy_read_hpd, |
398 | .update_hpd = dw_hdmi_phy_update_hpd, |
399 | .setup_hpd = dw_hdmi_phy_setup_hpd, |
400 | }; |
401 | |
402 | static void sun8i_hdmi_phy_unlock(struct sun8i_hdmi_phy *phy) |
403 | { |
404 | /* enable read access to HDMI controller */ |
405 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_READ_EN_REG, |
406 | SUN8I_HDMI_PHY_READ_EN_MAGIC); |
407 | |
408 | /* unscramble register offsets */ |
409 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_UNSCRAMBLE_REG, |
410 | SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC); |
411 | } |
412 | |
413 | static void sun50i_hdmi_phy_init_h6(struct sun8i_hdmi_phy *phy) |
414 | { |
415 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, |
416 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, |
417 | SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN); |
418 | |
419 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, |
420 | mask: 0xffff0000, val: 0x80c00000); |
421 | } |
422 | |
423 | static void sun8i_hdmi_phy_init_a83t(struct sun8i_hdmi_phy *phy) |
424 | { |
425 | sun8i_hdmi_phy_unlock(phy); |
426 | |
427 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, |
428 | SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK, |
429 | SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK); |
430 | |
431 | /* |
432 | * Set PHY I2C address. It must match to the address set by |
433 | * dw_hdmi_phy_set_slave_addr(). |
434 | */ |
435 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, |
436 | SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK, |
437 | SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR)); |
438 | } |
439 | |
440 | static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy) |
441 | { |
442 | unsigned int val; |
443 | |
444 | sun8i_hdmi_phy_unlock(phy); |
445 | |
446 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, val: 0); |
447 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
448 | SUN8I_HDMI_PHY_ANA_CFG1_ENBI, |
449 | SUN8I_HDMI_PHY_ANA_CFG1_ENBI); |
450 | udelay(5); |
451 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
452 | SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN, |
453 | SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN); |
454 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
455 | SUN8I_HDMI_PHY_ANA_CFG1_ENVBS, |
456 | SUN8I_HDMI_PHY_ANA_CFG1_ENVBS); |
457 | usleep_range(min: 10, max: 20); |
458 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
459 | SUN8I_HDMI_PHY_ANA_CFG1_LDOEN, |
460 | SUN8I_HDMI_PHY_ANA_CFG1_LDOEN); |
461 | udelay(5); |
462 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
463 | SUN8I_HDMI_PHY_ANA_CFG1_CKEN, |
464 | SUN8I_HDMI_PHY_ANA_CFG1_CKEN); |
465 | usleep_range(min: 40, max: 100); |
466 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
467 | SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL, |
468 | SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL); |
469 | usleep_range(min: 100, max: 200); |
470 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
471 | SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG, |
472 | SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG); |
473 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
474 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | |
475 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | |
476 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2, |
477 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | |
478 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | |
479 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2); |
480 | |
481 | /* wait for calibration to finish */ |
482 | regmap_read_poll_timeout(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, val, |
483 | (val & SUN8I_HDMI_PHY_ANA_STS_RCALEND2D), |
484 | 100, 2000); |
485 | |
486 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
487 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK, |
488 | SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK); |
489 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, |
490 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | |
491 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | |
492 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | |
493 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK, |
494 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | |
495 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | |
496 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | |
497 | SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK); |
498 | |
499 | /* enable DDC communication */ |
500 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, |
501 | SUN8I_HDMI_PHY_ANA_CFG3_SCLEN | |
502 | SUN8I_HDMI_PHY_ANA_CFG3_SDAEN, |
503 | SUN8I_HDMI_PHY_ANA_CFG3_SCLEN | |
504 | SUN8I_HDMI_PHY_ANA_CFG3_SDAEN); |
505 | |
506 | /* reset PHY PLL clock parent */ |
507 | regmap_update_bits(map: phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, |
508 | SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, val: 0); |
509 | |
510 | /* set HW control of CEC pins */ |
511 | regmap_write(map: phy->regs, SUN8I_HDMI_PHY_CEC_REG, val: 0); |
512 | |
513 | /* read calibration data */ |
514 | regmap_read(map: phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, val: &val); |
515 | phy->rcal = (val & SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK) >> 2; |
516 | } |
517 | |
518 | int sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy) |
519 | { |
520 | int ret; |
521 | |
522 | ret = reset_control_deassert(rstc: phy->rst_phy); |
523 | if (ret) { |
524 | dev_err(phy->dev, "Cannot deassert phy reset control: %d\n" , ret); |
525 | return ret; |
526 | } |
527 | |
528 | ret = clk_prepare_enable(clk: phy->clk_bus); |
529 | if (ret) { |
530 | dev_err(phy->dev, "Cannot enable bus clock: %d\n" , ret); |
531 | goto err_assert_rst_phy; |
532 | } |
533 | |
534 | ret = clk_prepare_enable(clk: phy->clk_mod); |
535 | if (ret) { |
536 | dev_err(phy->dev, "Cannot enable mod clock: %d\n" , ret); |
537 | goto err_disable_clk_bus; |
538 | } |
539 | |
540 | if (phy->variant->has_phy_clk) { |
541 | ret = sun8i_phy_clk_create(phy, dev: phy->dev, |
542 | second_parent: phy->variant->has_second_pll); |
543 | if (ret) { |
544 | dev_err(phy->dev, "Couldn't create the PHY clock\n" ); |
545 | goto err_disable_clk_mod; |
546 | } |
547 | |
548 | clk_prepare_enable(clk: phy->clk_phy); |
549 | } |
550 | |
551 | phy->variant->phy_init(phy); |
552 | |
553 | return 0; |
554 | |
555 | err_disable_clk_mod: |
556 | clk_disable_unprepare(clk: phy->clk_mod); |
557 | err_disable_clk_bus: |
558 | clk_disable_unprepare(clk: phy->clk_bus); |
559 | err_assert_rst_phy: |
560 | reset_control_assert(rstc: phy->rst_phy); |
561 | |
562 | return ret; |
563 | } |
564 | |
565 | void sun8i_hdmi_phy_deinit(struct sun8i_hdmi_phy *phy) |
566 | { |
567 | clk_disable_unprepare(clk: phy->clk_mod); |
568 | clk_disable_unprepare(clk: phy->clk_bus); |
569 | clk_disable_unprepare(clk: phy->clk_phy); |
570 | |
571 | reset_control_assert(rstc: phy->rst_phy); |
572 | } |
573 | |
574 | void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy, |
575 | struct dw_hdmi_plat_data *plat_data) |
576 | { |
577 | const struct sun8i_hdmi_phy_variant *variant = phy->variant; |
578 | |
579 | if (variant->phy_ops) { |
580 | plat_data->phy_ops = variant->phy_ops; |
581 | plat_data->phy_name = "sun8i_dw_hdmi_phy" ; |
582 | plat_data->phy_data = phy; |
583 | } else { |
584 | plat_data->mpll_cfg = variant->mpll_cfg; |
585 | plat_data->cur_ctr = variant->cur_ctr; |
586 | plat_data->phy_config = variant->phy_cfg; |
587 | } |
588 | } |
589 | |
590 | static const struct regmap_config sun8i_hdmi_phy_regmap_config = { |
591 | .reg_bits = 32, |
592 | .val_bits = 32, |
593 | .reg_stride = 4, |
594 | .max_register = SUN8I_HDMI_PHY_CEC_REG, |
595 | .name = "phy" |
596 | }; |
597 | |
598 | static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = { |
599 | .phy_ops = &sun8i_a83t_hdmi_phy_ops, |
600 | .phy_init = &sun8i_hdmi_phy_init_a83t, |
601 | }; |
602 | |
603 | static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = { |
604 | .has_phy_clk = true, |
605 | .phy_ops = &sun8i_h3_hdmi_phy_ops, |
606 | .phy_init = &sun8i_hdmi_phy_init_h3, |
607 | }; |
608 | |
609 | static const struct sun8i_hdmi_phy_variant sun8i_r40_hdmi_phy = { |
610 | .has_phy_clk = true, |
611 | .has_second_pll = true, |
612 | .phy_ops = &sun8i_h3_hdmi_phy_ops, |
613 | .phy_init = &sun8i_hdmi_phy_init_h3, |
614 | }; |
615 | |
616 | static const struct sun8i_hdmi_phy_variant sun50i_a64_hdmi_phy = { |
617 | .has_phy_clk = true, |
618 | .phy_ops = &sun8i_h3_hdmi_phy_ops, |
619 | .phy_init = &sun8i_hdmi_phy_init_h3, |
620 | }; |
621 | |
622 | static const struct sun8i_hdmi_phy_variant sun50i_h6_hdmi_phy = { |
623 | .cur_ctr = sun50i_h6_cur_ctr, |
624 | .mpll_cfg = sun50i_h6_mpll_cfg, |
625 | .phy_cfg = sun50i_h6_phy_config, |
626 | .phy_init = &sun50i_hdmi_phy_init_h6, |
627 | }; |
628 | |
629 | static const struct of_device_id sun8i_hdmi_phy_of_table[] = { |
630 | { |
631 | .compatible = "allwinner,sun8i-a83t-hdmi-phy" , |
632 | .data = &sun8i_a83t_hdmi_phy, |
633 | }, |
634 | { |
635 | .compatible = "allwinner,sun8i-h3-hdmi-phy" , |
636 | .data = &sun8i_h3_hdmi_phy, |
637 | }, |
638 | { |
639 | .compatible = "allwinner,sun8i-r40-hdmi-phy" , |
640 | .data = &sun8i_r40_hdmi_phy, |
641 | }, |
642 | { |
643 | .compatible = "allwinner,sun50i-a64-hdmi-phy" , |
644 | .data = &sun50i_a64_hdmi_phy, |
645 | }, |
646 | { |
647 | .compatible = "allwinner,sun50i-h6-hdmi-phy" , |
648 | .data = &sun50i_h6_hdmi_phy, |
649 | }, |
650 | { /* sentinel */ } |
651 | }; |
652 | |
653 | int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node) |
654 | { |
655 | struct platform_device *pdev = of_find_device_by_node(np: node); |
656 | struct sun8i_hdmi_phy *phy; |
657 | |
658 | if (!pdev) |
659 | return -EPROBE_DEFER; |
660 | |
661 | phy = platform_get_drvdata(pdev); |
662 | if (!phy) { |
663 | put_device(dev: &pdev->dev); |
664 | return -EPROBE_DEFER; |
665 | } |
666 | |
667 | hdmi->phy = phy; |
668 | |
669 | put_device(dev: &pdev->dev); |
670 | |
671 | return 0; |
672 | } |
673 | |
674 | static int sun8i_hdmi_phy_probe(struct platform_device *pdev) |
675 | { |
676 | struct device *dev = &pdev->dev; |
677 | struct sun8i_hdmi_phy *phy; |
678 | void __iomem *regs; |
679 | |
680 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
681 | if (!phy) |
682 | return -ENOMEM; |
683 | |
684 | phy->variant = of_device_get_match_data(dev); |
685 | phy->dev = dev; |
686 | |
687 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
688 | if (IS_ERR(ptr: regs)) |
689 | return dev_err_probe(dev, err: PTR_ERR(ptr: regs), |
690 | fmt: "Couldn't map the HDMI PHY registers\n" ); |
691 | |
692 | phy->regs = devm_regmap_init_mmio(dev, regs, |
693 | &sun8i_hdmi_phy_regmap_config); |
694 | if (IS_ERR(ptr: phy->regs)) |
695 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->regs), |
696 | fmt: "Couldn't create the HDMI PHY regmap\n" ); |
697 | |
698 | phy->clk_bus = devm_clk_get(dev, id: "bus" ); |
699 | if (IS_ERR(ptr: phy->clk_bus)) |
700 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->clk_bus), |
701 | fmt: "Could not get bus clock\n" ); |
702 | |
703 | phy->clk_mod = devm_clk_get(dev, id: "mod" ); |
704 | if (IS_ERR(ptr: phy->clk_mod)) |
705 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->clk_mod), |
706 | fmt: "Could not get mod clock\n" ); |
707 | |
708 | if (phy->variant->has_phy_clk) { |
709 | phy->clk_pll0 = devm_clk_get(dev, id: "pll-0" ); |
710 | if (IS_ERR(ptr: phy->clk_pll0)) |
711 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->clk_pll0), |
712 | fmt: "Could not get pll-0 clock\n" ); |
713 | |
714 | if (phy->variant->has_second_pll) { |
715 | phy->clk_pll1 = devm_clk_get(dev, id: "pll-1" ); |
716 | if (IS_ERR(ptr: phy->clk_pll1)) |
717 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->clk_pll1), |
718 | fmt: "Could not get pll-1 clock\n" ); |
719 | } |
720 | } |
721 | |
722 | phy->rst_phy = devm_reset_control_get_shared(dev, id: "phy" ); |
723 | if (IS_ERR(ptr: phy->rst_phy)) |
724 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy->rst_phy), |
725 | fmt: "Could not get phy reset control\n" ); |
726 | |
727 | platform_set_drvdata(pdev, data: phy); |
728 | |
729 | return 0; |
730 | } |
731 | |
732 | struct platform_driver sun8i_hdmi_phy_driver = { |
733 | .probe = sun8i_hdmi_phy_probe, |
734 | .driver = { |
735 | .name = "sun8i-hdmi-phy" , |
736 | .of_match_table = sun8i_hdmi_phy_of_table, |
737 | }, |
738 | }; |
739 | |