1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * Copyright (c) 2017, 2020, The Linux Foundation. All rights reserved. |
4 | * Copyright (c) 2021, Linaro Ltd. |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/err.h> |
11 | #include <linux/io.h> |
12 | #include <linux/iopoll.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/phy/phy.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/regulator/consumer.h> |
19 | #include <linux/reset.h> |
20 | #include <linux/slab.h> |
21 | |
22 | #include <dt-bindings/phy/phy.h> |
23 | |
24 | #include "phy-qcom-qmp-dp-phy.h" |
25 | #include "phy-qcom-qmp-qserdes-com-v4.h" |
26 | |
27 | /* EDP_PHY registers */ |
28 | #define DP_PHY_CFG 0x0010 |
29 | #define DP_PHY_CFG_1 0x0014 |
30 | #define DP_PHY_PD_CTL 0x001c |
31 | #define DP_PHY_MODE 0x0020 |
32 | |
33 | #define DP_PHY_AUX_CFG0 0x0024 |
34 | #define DP_PHY_AUX_CFG1 0x0028 |
35 | #define DP_PHY_AUX_CFG2 0x002C |
36 | #define DP_PHY_AUX_CFG3 0x0030 |
37 | #define DP_PHY_AUX_CFG4 0x0034 |
38 | #define DP_PHY_AUX_CFG5 0x0038 |
39 | #define DP_PHY_AUX_CFG6 0x003C |
40 | #define DP_PHY_AUX_CFG7 0x0040 |
41 | #define DP_PHY_AUX_CFG8 0x0044 |
42 | #define DP_PHY_AUX_CFG9 0x0048 |
43 | |
44 | #define DP_PHY_AUX_INTERRUPT_MASK 0x0058 |
45 | |
46 | #define DP_PHY_VCO_DIV 0x0074 |
47 | #define DP_PHY_TX0_TX1_LANE_CTL 0x007c |
48 | #define DP_PHY_TX2_TX3_LANE_CTL 0x00a0 |
49 | |
50 | #define DP_PHY_STATUS 0x00e0 |
51 | |
52 | /* LANE_TXn registers */ |
53 | #define TXn_CLKBUF_ENABLE 0x0000 |
54 | #define TXn_TX_EMP_POST1_LVL 0x0004 |
55 | |
56 | #define TXn_TX_DRV_LVL 0x0014 |
57 | #define TXn_TX_DRV_LVL_OFFSET 0x0018 |
58 | #define TXn_RESET_TSYNC_EN 0x001c |
59 | #define TXn_LDO_CONFIG 0x0084 |
60 | #define TXn_TX_BAND 0x0028 |
61 | |
62 | #define TXn_RES_CODE_LANE_OFFSET_TX0 0x0044 |
63 | #define TXn_RES_CODE_LANE_OFFSET_TX1 0x0048 |
64 | |
65 | #define TXn_TRANSCEIVER_BIAS_EN 0x0054 |
66 | #define TXn_HIGHZ_DRVR_EN 0x0058 |
67 | #define TXn_TX_POL_INV 0x005c |
68 | #define TXn_LANE_MODE_1 0x0064 |
69 | |
70 | #define TXn_TRAN_DRVR_EMP_EN 0x0078 |
71 | |
72 | struct qcom_edp_cfg { |
73 | bool is_dp; |
74 | |
75 | /* DP PHY swing and pre_emphasis tables */ |
76 | const u8 (*swing_hbr_rbr)[4][4]; |
77 | const u8 (*swing_hbr3_hbr2)[4][4]; |
78 | const u8 (*pre_emphasis_hbr_rbr)[4][4]; |
79 | const u8 (*pre_emphasis_hbr3_hbr2)[4][4]; |
80 | }; |
81 | |
82 | struct qcom_edp { |
83 | struct device *dev; |
84 | const struct qcom_edp_cfg *cfg; |
85 | |
86 | struct phy *phy; |
87 | |
88 | void __iomem *edp; |
89 | void __iomem *tx0; |
90 | void __iomem *tx1; |
91 | void __iomem *pll; |
92 | |
93 | struct clk_hw dp_link_hw; |
94 | struct clk_hw dp_pixel_hw; |
95 | |
96 | struct phy_configure_opts_dp dp_opts; |
97 | |
98 | struct clk_bulk_data clks[2]; |
99 | struct regulator_bulk_data supplies[2]; |
100 | }; |
101 | |
102 | static const u8 dp_swing_hbr_rbr[4][4] = { |
103 | { 0x08, 0x0f, 0x16, 0x1f }, |
104 | { 0x11, 0x1e, 0x1f, 0xff }, |
105 | { 0x16, 0x1f, 0xff, 0xff }, |
106 | { 0x1f, 0xff, 0xff, 0xff } |
107 | }; |
108 | |
109 | static const u8 dp_pre_emp_hbr_rbr[4][4] = { |
110 | { 0x00, 0x0d, 0x14, 0x1a }, |
111 | { 0x00, 0x0e, 0x15, 0xff }, |
112 | { 0x00, 0x0e, 0xff, 0xff }, |
113 | { 0x03, 0xff, 0xff, 0xff } |
114 | }; |
115 | |
116 | static const u8 dp_swing_hbr2_hbr3[4][4] = { |
117 | { 0x02, 0x12, 0x16, 0x1a }, |
118 | { 0x09, 0x19, 0x1f, 0xff }, |
119 | { 0x10, 0x1f, 0xff, 0xff }, |
120 | { 0x1f, 0xff, 0xff, 0xff } |
121 | }; |
122 | |
123 | static const u8 dp_pre_emp_hbr2_hbr3[4][4] = { |
124 | { 0x00, 0x0c, 0x15, 0x1b }, |
125 | { 0x02, 0x0e, 0x16, 0xff }, |
126 | { 0x02, 0x11, 0xff, 0xff }, |
127 | { 0x04, 0xff, 0xff, 0xff } |
128 | }; |
129 | |
130 | static const struct qcom_edp_cfg dp_phy_cfg = { |
131 | .is_dp = true, |
132 | .swing_hbr_rbr = &dp_swing_hbr_rbr, |
133 | .swing_hbr3_hbr2 = &dp_swing_hbr2_hbr3, |
134 | .pre_emphasis_hbr_rbr = &dp_pre_emp_hbr_rbr, |
135 | .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3, |
136 | }; |
137 | |
138 | static const u8 edp_swing_hbr_rbr[4][4] = { |
139 | { 0x07, 0x0f, 0x16, 0x1f }, |
140 | { 0x0d, 0x16, 0x1e, 0xff }, |
141 | { 0x11, 0x1b, 0xff, 0xff }, |
142 | { 0x16, 0xff, 0xff, 0xff } |
143 | }; |
144 | |
145 | static const u8 edp_pre_emp_hbr_rbr[4][4] = { |
146 | { 0x05, 0x12, 0x17, 0x1d }, |
147 | { 0x05, 0x11, 0x18, 0xff }, |
148 | { 0x06, 0x11, 0xff, 0xff }, |
149 | { 0x00, 0xff, 0xff, 0xff } |
150 | }; |
151 | |
152 | static const u8 edp_swing_hbr2_hbr3[4][4] = { |
153 | { 0x0b, 0x11, 0x17, 0x1c }, |
154 | { 0x10, 0x19, 0x1f, 0xff }, |
155 | { 0x19, 0x1f, 0xff, 0xff }, |
156 | { 0x1f, 0xff, 0xff, 0xff } |
157 | }; |
158 | |
159 | static const u8 edp_pre_emp_hbr2_hbr3[4][4] = { |
160 | { 0x08, 0x11, 0x17, 0x1b }, |
161 | { 0x00, 0x0c, 0x13, 0xff }, |
162 | { 0x05, 0x10, 0xff, 0xff }, |
163 | { 0x00, 0xff, 0xff, 0xff } |
164 | }; |
165 | |
166 | static const struct qcom_edp_cfg edp_phy_cfg = { |
167 | .is_dp = false, |
168 | .swing_hbr_rbr = &edp_swing_hbr_rbr, |
169 | .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3, |
170 | .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr, |
171 | .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3, |
172 | }; |
173 | |
174 | static int qcom_edp_phy_init(struct phy *phy) |
175 | { |
176 | struct qcom_edp *edp = phy_get_drvdata(phy); |
177 | const struct qcom_edp_cfg *cfg = edp->cfg; |
178 | int ret; |
179 | u8 cfg8; |
180 | |
181 | ret = regulator_bulk_enable(ARRAY_SIZE(edp->supplies), consumers: edp->supplies); |
182 | if (ret) |
183 | return ret; |
184 | |
185 | ret = clk_bulk_prepare_enable(ARRAY_SIZE(edp->clks), clks: edp->clks); |
186 | if (ret) |
187 | goto out_disable_supplies; |
188 | |
189 | writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | |
190 | DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, |
191 | addr: edp->edp + DP_PHY_PD_CTL); |
192 | |
193 | /* Turn on BIAS current for PHY/PLL */ |
194 | writel(val: 0x17, addr: edp->pll + QSERDES_V4_COM_BIAS_EN_CLKBUFLR_EN); |
195 | |
196 | writel(DP_PHY_PD_CTL_PSR_PWRDN, addr: edp->edp + DP_PHY_PD_CTL); |
197 | msleep(msecs: 20); |
198 | |
199 | writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | |
200 | DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | |
201 | DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, |
202 | addr: edp->edp + DP_PHY_PD_CTL); |
203 | |
204 | if (cfg && cfg->is_dp) |
205 | cfg8 = 0xb7; |
206 | else |
207 | cfg8 = 0x37; |
208 | |
209 | writel(val: 0xfc, addr: edp->edp + DP_PHY_MODE); |
210 | |
211 | writel(val: 0x00, addr: edp->edp + DP_PHY_AUX_CFG0); |
212 | writel(val: 0x13, addr: edp->edp + DP_PHY_AUX_CFG1); |
213 | writel(val: 0x24, addr: edp->edp + DP_PHY_AUX_CFG2); |
214 | writel(val: 0x00, addr: edp->edp + DP_PHY_AUX_CFG3); |
215 | writel(val: 0x0a, addr: edp->edp + DP_PHY_AUX_CFG4); |
216 | writel(val: 0x26, addr: edp->edp + DP_PHY_AUX_CFG5); |
217 | writel(val: 0x0a, addr: edp->edp + DP_PHY_AUX_CFG6); |
218 | writel(val: 0x03, addr: edp->edp + DP_PHY_AUX_CFG7); |
219 | writel(val: cfg8, addr: edp->edp + DP_PHY_AUX_CFG8); |
220 | writel(val: 0x03, addr: edp->edp + DP_PHY_AUX_CFG9); |
221 | |
222 | writel(PHY_AUX_STOP_ERR_MASK | PHY_AUX_DEC_ERR_MASK | |
223 | PHY_AUX_SYNC_ERR_MASK | PHY_AUX_ALIGN_ERR_MASK | |
224 | PHY_AUX_REQ_ERR_MASK, addr: edp->edp + DP_PHY_AUX_INTERRUPT_MASK); |
225 | |
226 | msleep(msecs: 20); |
227 | |
228 | return 0; |
229 | |
230 | out_disable_supplies: |
231 | regulator_bulk_disable(ARRAY_SIZE(edp->supplies), consumers: edp->supplies); |
232 | |
233 | return ret; |
234 | } |
235 | |
236 | static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configure_opts_dp *dp_opts) |
237 | { |
238 | const struct qcom_edp_cfg *cfg = edp->cfg; |
239 | unsigned int v_level = 0; |
240 | unsigned int p_level = 0; |
241 | u8 ldo_config; |
242 | u8 swing; |
243 | u8 emph; |
244 | int i; |
245 | |
246 | if (!cfg) |
247 | return 0; |
248 | |
249 | for (i = 0; i < dp_opts->lanes; i++) { |
250 | v_level = max(v_level, dp_opts->voltage[i]); |
251 | p_level = max(p_level, dp_opts->pre[i]); |
252 | } |
253 | |
254 | if (dp_opts->link_rate <= 2700) { |
255 | swing = (*cfg->swing_hbr_rbr)[v_level][p_level]; |
256 | emph = (*cfg->pre_emphasis_hbr_rbr)[v_level][p_level]; |
257 | } else { |
258 | swing = (*cfg->swing_hbr3_hbr2)[v_level][p_level]; |
259 | emph = (*cfg->pre_emphasis_hbr3_hbr2)[v_level][p_level]; |
260 | } |
261 | |
262 | if (swing == 0xff || emph == 0xff) |
263 | return -EINVAL; |
264 | |
265 | ldo_config = (cfg && cfg->is_dp) ? 0x1 : 0x0; |
266 | |
267 | writel(val: ldo_config, addr: edp->tx0 + TXn_LDO_CONFIG); |
268 | writel(val: swing, addr: edp->tx0 + TXn_TX_DRV_LVL); |
269 | writel(val: emph, addr: edp->tx0 + TXn_TX_EMP_POST1_LVL); |
270 | |
271 | writel(val: ldo_config, addr: edp->tx1 + TXn_LDO_CONFIG); |
272 | writel(val: swing, addr: edp->tx1 + TXn_TX_DRV_LVL); |
273 | writel(val: emph, addr: edp->tx1 + TXn_TX_EMP_POST1_LVL); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static int qcom_edp_phy_configure(struct phy *phy, union phy_configure_opts *opts) |
279 | { |
280 | const struct phy_configure_opts_dp *dp_opts = &opts->dp; |
281 | struct qcom_edp *edp = phy_get_drvdata(phy); |
282 | int ret = 0; |
283 | |
284 | memcpy(&edp->dp_opts, dp_opts, sizeof(*dp_opts)); |
285 | |
286 | if (dp_opts->set_voltages) |
287 | ret = qcom_edp_set_voltages(edp, dp_opts); |
288 | |
289 | return ret; |
290 | } |
291 | |
292 | static int qcom_edp_configure_ssc(const struct qcom_edp *edp) |
293 | { |
294 | const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; |
295 | u32 step1; |
296 | u32 step2; |
297 | |
298 | switch (dp_opts->link_rate) { |
299 | case 1620: |
300 | case 2700: |
301 | case 8100: |
302 | step1 = 0x45; |
303 | step2 = 0x06; |
304 | break; |
305 | |
306 | case 5400: |
307 | step1 = 0x5c; |
308 | step2 = 0x08; |
309 | break; |
310 | |
311 | default: |
312 | /* Other link rates aren't supported */ |
313 | return -EINVAL; |
314 | } |
315 | |
316 | writel(val: 0x01, addr: edp->pll + QSERDES_V4_COM_SSC_EN_CENTER); |
317 | writel(val: 0x00, addr: edp->pll + QSERDES_V4_COM_SSC_ADJ_PER1); |
318 | writel(val: 0x36, addr: edp->pll + QSERDES_V4_COM_SSC_PER1); |
319 | writel(val: 0x01, addr: edp->pll + QSERDES_V4_COM_SSC_PER2); |
320 | writel(val: step1, addr: edp->pll + QSERDES_V4_COM_SSC_STEP_SIZE1_MODE0); |
321 | writel(val: step2, addr: edp->pll + QSERDES_V4_COM_SSC_STEP_SIZE2_MODE0); |
322 | |
323 | return 0; |
324 | } |
325 | |
326 | static int qcom_edp_configure_pll(const struct qcom_edp *edp) |
327 | { |
328 | const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; |
329 | u32 div_frac_start2_mode0; |
330 | u32 div_frac_start3_mode0; |
331 | u32 dec_start_mode0; |
332 | u32 lock_cmp1_mode0; |
333 | u32 lock_cmp2_mode0; |
334 | u32 hsclk_sel; |
335 | |
336 | switch (dp_opts->link_rate) { |
337 | case 1620: |
338 | hsclk_sel = 0x5; |
339 | dec_start_mode0 = 0x69; |
340 | div_frac_start2_mode0 = 0x80; |
341 | div_frac_start3_mode0 = 0x07; |
342 | lock_cmp1_mode0 = 0x6f; |
343 | lock_cmp2_mode0 = 0x08; |
344 | break; |
345 | |
346 | case 2700: |
347 | hsclk_sel = 0x3; |
348 | dec_start_mode0 = 0x69; |
349 | div_frac_start2_mode0 = 0x80; |
350 | div_frac_start3_mode0 = 0x07; |
351 | lock_cmp1_mode0 = 0x0f; |
352 | lock_cmp2_mode0 = 0x0e; |
353 | break; |
354 | |
355 | case 5400: |
356 | hsclk_sel = 0x1; |
357 | dec_start_mode0 = 0x8c; |
358 | div_frac_start2_mode0 = 0x00; |
359 | div_frac_start3_mode0 = 0x0a; |
360 | lock_cmp1_mode0 = 0x1f; |
361 | lock_cmp2_mode0 = 0x1c; |
362 | break; |
363 | |
364 | case 8100: |
365 | hsclk_sel = 0x0; |
366 | dec_start_mode0 = 0x69; |
367 | div_frac_start2_mode0 = 0x80; |
368 | div_frac_start3_mode0 = 0x07; |
369 | lock_cmp1_mode0 = 0x2f; |
370 | lock_cmp2_mode0 = 0x2a; |
371 | break; |
372 | |
373 | default: |
374 | /* Other link rates aren't supported */ |
375 | return -EINVAL; |
376 | } |
377 | |
378 | writel(val: 0x01, addr: edp->pll + QSERDES_V4_COM_SVS_MODE_CLK_SEL); |
379 | writel(val: 0x0b, addr: edp->pll + QSERDES_V4_COM_SYSCLK_EN_SEL); |
380 | writel(val: 0x02, addr: edp->pll + QSERDES_V4_COM_SYS_CLK_CTRL); |
381 | writel(val: 0x0c, addr: edp->pll + QSERDES_V4_COM_CLK_ENABLE1); |
382 | writel(val: 0x06, addr: edp->pll + QSERDES_V4_COM_SYSCLK_BUF_ENABLE); |
383 | writel(val: 0x30, addr: edp->pll + QSERDES_V4_COM_CLK_SELECT); |
384 | writel(val: hsclk_sel, addr: edp->pll + QSERDES_V4_COM_HSCLK_SEL); |
385 | writel(val: 0x0f, addr: edp->pll + QSERDES_V4_COM_PLL_IVCO); |
386 | writel(val: 0x08, addr: edp->pll + QSERDES_V4_COM_LOCK_CMP_EN); |
387 | writel(val: 0x36, addr: edp->pll + QSERDES_V4_COM_PLL_CCTRL_MODE0); |
388 | writel(val: 0x16, addr: edp->pll + QSERDES_V4_COM_PLL_RCTRL_MODE0); |
389 | writel(val: 0x06, addr: edp->pll + QSERDES_V4_COM_CP_CTRL_MODE0); |
390 | writel(val: dec_start_mode0, addr: edp->pll + QSERDES_V4_COM_DEC_START_MODE0); |
391 | writel(val: 0x00, addr: edp->pll + QSERDES_V4_COM_DIV_FRAC_START1_MODE0); |
392 | writel(val: div_frac_start2_mode0, addr: edp->pll + QSERDES_V4_COM_DIV_FRAC_START2_MODE0); |
393 | writel(val: div_frac_start3_mode0, addr: edp->pll + QSERDES_V4_COM_DIV_FRAC_START3_MODE0); |
394 | writel(val: 0x02, addr: edp->pll + QSERDES_V4_COM_CMN_CONFIG); |
395 | writel(val: 0x3f, addr: edp->pll + QSERDES_V4_COM_INTEGLOOP_GAIN0_MODE0); |
396 | writel(val: 0x00, addr: edp->pll + QSERDES_V4_COM_INTEGLOOP_GAIN1_MODE0); |
397 | writel(val: 0x00, addr: edp->pll + QSERDES_V4_COM_VCO_TUNE_MAP); |
398 | writel(val: lock_cmp1_mode0, addr: edp->pll + QSERDES_V4_COM_LOCK_CMP1_MODE0); |
399 | writel(val: lock_cmp2_mode0, addr: edp->pll + QSERDES_V4_COM_LOCK_CMP2_MODE0); |
400 | |
401 | writel(val: 0x0a, addr: edp->pll + QSERDES_V4_COM_BG_TIMER); |
402 | writel(val: 0x14, addr: edp->pll + QSERDES_V4_COM_CORECLK_DIV_MODE0); |
403 | writel(val: 0x00, addr: edp->pll + QSERDES_V4_COM_VCO_TUNE_CTRL); |
404 | writel(val: 0x17, addr: edp->pll + QSERDES_V4_COM_BIAS_EN_CLKBUFLR_EN); |
405 | writel(val: 0x0f, addr: edp->pll + QSERDES_V4_COM_CORE_CLK_EN); |
406 | writel(val: 0xa0, addr: edp->pll + QSERDES_V4_COM_VCO_TUNE1_MODE0); |
407 | writel(val: 0x03, addr: edp->pll + QSERDES_V4_COM_VCO_TUNE2_MODE0); |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | static int qcom_edp_set_vco_div(const struct qcom_edp *edp, unsigned long *pixel_freq) |
413 | { |
414 | const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; |
415 | u32 vco_div; |
416 | |
417 | switch (dp_opts->link_rate) { |
418 | case 1620: |
419 | vco_div = 0x1; |
420 | *pixel_freq = 1620000000UL / 2; |
421 | break; |
422 | |
423 | case 2700: |
424 | vco_div = 0x1; |
425 | *pixel_freq = 2700000000UL / 2; |
426 | break; |
427 | |
428 | case 5400: |
429 | vco_div = 0x2; |
430 | *pixel_freq = 5400000000UL / 4; |
431 | break; |
432 | |
433 | case 8100: |
434 | vco_div = 0x0; |
435 | *pixel_freq = 8100000000UL / 6; |
436 | break; |
437 | |
438 | default: |
439 | /* Other link rates aren't supported */ |
440 | return -EINVAL; |
441 | } |
442 | |
443 | writel(val: vco_div, addr: edp->edp + DP_PHY_VCO_DIV); |
444 | |
445 | return 0; |
446 | } |
447 | |
448 | static int qcom_edp_phy_power_on(struct phy *phy) |
449 | { |
450 | const struct qcom_edp *edp = phy_get_drvdata(phy); |
451 | const struct qcom_edp_cfg *cfg = edp->cfg; |
452 | u32 bias0_en, drvr0_en, bias1_en, drvr1_en; |
453 | unsigned long pixel_freq; |
454 | u8 ldo_config; |
455 | int timeout; |
456 | int ret; |
457 | u32 val; |
458 | u8 cfg1; |
459 | |
460 | writel(DP_PHY_PD_CTL_PWRDN | DP_PHY_PD_CTL_AUX_PWRDN | |
461 | DP_PHY_PD_CTL_LANE_0_1_PWRDN | DP_PHY_PD_CTL_LANE_2_3_PWRDN | |
462 | DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, |
463 | addr: edp->edp + DP_PHY_PD_CTL); |
464 | writel(val: 0xfc, addr: edp->edp + DP_PHY_MODE); |
465 | |
466 | timeout = readl_poll_timeout(edp->pll + QSERDES_V4_COM_CMN_STATUS, |
467 | val, val & BIT(7), 5, 200); |
468 | if (timeout) |
469 | return timeout; |
470 | |
471 | |
472 | ldo_config = (cfg && cfg->is_dp) ? 0x1 : 0x0; |
473 | |
474 | writel(val: ldo_config, addr: edp->tx0 + TXn_LDO_CONFIG); |
475 | writel(val: ldo_config, addr: edp->tx1 + TXn_LDO_CONFIG); |
476 | writel(val: 0x00, addr: edp->tx0 + TXn_LANE_MODE_1); |
477 | writel(val: 0x00, addr: edp->tx1 + TXn_LANE_MODE_1); |
478 | |
479 | if (edp->dp_opts.ssc) { |
480 | ret = qcom_edp_configure_ssc(edp); |
481 | if (ret) |
482 | return ret; |
483 | } |
484 | |
485 | ret = qcom_edp_configure_pll(edp); |
486 | if (ret) |
487 | return ret; |
488 | |
489 | /* TX Lane configuration */ |
490 | writel(val: 0x05, addr: edp->edp + DP_PHY_TX0_TX1_LANE_CTL); |
491 | writel(val: 0x05, addr: edp->edp + DP_PHY_TX2_TX3_LANE_CTL); |
492 | |
493 | /* TX-0 register configuration */ |
494 | writel(val: 0x03, addr: edp->tx0 + TXn_TRANSCEIVER_BIAS_EN); |
495 | writel(val: 0x0f, addr: edp->tx0 + TXn_CLKBUF_ENABLE); |
496 | writel(val: 0x03, addr: edp->tx0 + TXn_RESET_TSYNC_EN); |
497 | writel(val: 0x01, addr: edp->tx0 + TXn_TRAN_DRVR_EMP_EN); |
498 | writel(val: 0x04, addr: edp->tx0 + TXn_TX_BAND); |
499 | |
500 | /* TX-1 register configuration */ |
501 | writel(val: 0x03, addr: edp->tx1 + TXn_TRANSCEIVER_BIAS_EN); |
502 | writel(val: 0x0f, addr: edp->tx1 + TXn_CLKBUF_ENABLE); |
503 | writel(val: 0x03, addr: edp->tx1 + TXn_RESET_TSYNC_EN); |
504 | writel(val: 0x01, addr: edp->tx1 + TXn_TRAN_DRVR_EMP_EN); |
505 | writel(val: 0x04, addr: edp->tx1 + TXn_TX_BAND); |
506 | |
507 | ret = qcom_edp_set_vco_div(edp, pixel_freq: &pixel_freq); |
508 | if (ret) |
509 | return ret; |
510 | |
511 | writel(val: 0x01, addr: edp->edp + DP_PHY_CFG); |
512 | writel(val: 0x05, addr: edp->edp + DP_PHY_CFG); |
513 | writel(val: 0x01, addr: edp->edp + DP_PHY_CFG); |
514 | writel(val: 0x09, addr: edp->edp + DP_PHY_CFG); |
515 | |
516 | writel(val: 0x20, addr: edp->pll + QSERDES_V4_COM_RESETSM_CNTRL); |
517 | |
518 | timeout = readl_poll_timeout(edp->pll + QSERDES_V4_COM_C_READY_STATUS, |
519 | val, val & BIT(0), 500, 10000); |
520 | if (timeout) |
521 | return timeout; |
522 | |
523 | writel(val: 0x19, addr: edp->edp + DP_PHY_CFG); |
524 | writel(val: 0x1f, addr: edp->tx0 + TXn_HIGHZ_DRVR_EN); |
525 | writel(val: 0x04, addr: edp->tx0 + TXn_HIGHZ_DRVR_EN); |
526 | writel(val: 0x00, addr: edp->tx0 + TXn_TX_POL_INV); |
527 | writel(val: 0x1f, addr: edp->tx1 + TXn_HIGHZ_DRVR_EN); |
528 | writel(val: 0x04, addr: edp->tx1 + TXn_HIGHZ_DRVR_EN); |
529 | writel(val: 0x00, addr: edp->tx1 + TXn_TX_POL_INV); |
530 | writel(val: 0x10, addr: edp->tx0 + TXn_TX_DRV_LVL_OFFSET); |
531 | writel(val: 0x10, addr: edp->tx1 + TXn_TX_DRV_LVL_OFFSET); |
532 | writel(val: 0x11, addr: edp->tx0 + TXn_RES_CODE_LANE_OFFSET_TX0); |
533 | writel(val: 0x11, addr: edp->tx0 + TXn_RES_CODE_LANE_OFFSET_TX1); |
534 | writel(val: 0x11, addr: edp->tx1 + TXn_RES_CODE_LANE_OFFSET_TX0); |
535 | writel(val: 0x11, addr: edp->tx1 + TXn_RES_CODE_LANE_OFFSET_TX1); |
536 | |
537 | writel(val: 0x10, addr: edp->tx0 + TXn_TX_EMP_POST1_LVL); |
538 | writel(val: 0x10, addr: edp->tx1 + TXn_TX_EMP_POST1_LVL); |
539 | writel(val: 0x1f, addr: edp->tx0 + TXn_TX_DRV_LVL); |
540 | writel(val: 0x1f, addr: edp->tx1 + TXn_TX_DRV_LVL); |
541 | |
542 | if (edp->dp_opts.lanes == 1) { |
543 | bias0_en = 0x01; |
544 | bias1_en = 0x00; |
545 | drvr0_en = 0x06; |
546 | drvr1_en = 0x07; |
547 | cfg1 = 0x1; |
548 | } else if (edp->dp_opts.lanes == 2) { |
549 | bias0_en = 0x03; |
550 | bias1_en = 0x00; |
551 | drvr0_en = 0x04; |
552 | drvr1_en = 0x07; |
553 | cfg1 = 0x3; |
554 | } else { |
555 | bias0_en = 0x03; |
556 | bias1_en = 0x03; |
557 | drvr0_en = 0x04; |
558 | drvr1_en = 0x04; |
559 | cfg1 = 0xf; |
560 | } |
561 | |
562 | writel(val: drvr0_en, addr: edp->tx0 + TXn_HIGHZ_DRVR_EN); |
563 | writel(val: bias0_en, addr: edp->tx0 + TXn_TRANSCEIVER_BIAS_EN); |
564 | writel(val: drvr1_en, addr: edp->tx1 + TXn_HIGHZ_DRVR_EN); |
565 | writel(val: bias1_en, addr: edp->tx1 + TXn_TRANSCEIVER_BIAS_EN); |
566 | writel(val: cfg1, addr: edp->edp + DP_PHY_CFG_1); |
567 | |
568 | writel(val: 0x18, addr: edp->edp + DP_PHY_CFG); |
569 | usleep_range(min: 100, max: 1000); |
570 | |
571 | writel(val: 0x19, addr: edp->edp + DP_PHY_CFG); |
572 | |
573 | ret = readl_poll_timeout(edp->edp + DP_PHY_STATUS, |
574 | val, val & BIT(1), 500, 10000); |
575 | if (ret) |
576 | return ret; |
577 | |
578 | clk_set_rate(clk: edp->dp_link_hw.clk, rate: edp->dp_opts.link_rate * 100000); |
579 | clk_set_rate(clk: edp->dp_pixel_hw.clk, rate: pixel_freq); |
580 | |
581 | return 0; |
582 | } |
583 | |
584 | static int qcom_edp_phy_power_off(struct phy *phy) |
585 | { |
586 | const struct qcom_edp *edp = phy_get_drvdata(phy); |
587 | |
588 | writel(DP_PHY_PD_CTL_PSR_PWRDN, addr: edp->edp + DP_PHY_PD_CTL); |
589 | |
590 | return 0; |
591 | } |
592 | |
593 | static int qcom_edp_phy_exit(struct phy *phy) |
594 | { |
595 | struct qcom_edp *edp = phy_get_drvdata(phy); |
596 | |
597 | clk_bulk_disable_unprepare(ARRAY_SIZE(edp->clks), clks: edp->clks); |
598 | regulator_bulk_disable(ARRAY_SIZE(edp->supplies), consumers: edp->supplies); |
599 | |
600 | return 0; |
601 | } |
602 | |
603 | static const struct phy_ops qcom_edp_ops = { |
604 | .init = qcom_edp_phy_init, |
605 | .configure = qcom_edp_phy_configure, |
606 | .power_on = qcom_edp_phy_power_on, |
607 | .power_off = qcom_edp_phy_power_off, |
608 | .exit = qcom_edp_phy_exit, |
609 | .owner = THIS_MODULE, |
610 | }; |
611 | |
612 | /* |
613 | * Embedded Display Port PLL driver block diagram for branch clocks |
614 | * |
615 | * +------------------------------+ |
616 | * | EDP_VCO_CLK | |
617 | * | | |
618 | * | +-------------------+ | |
619 | * | | (EDP PLL/VCO) | | |
620 | * | +---------+---------+ | |
621 | * | v | |
622 | * | +----------+-----------+ | |
623 | * | | hsclk_divsel_clk_src | | |
624 | * | +----------+-----------+ | |
625 | * +------------------------------+ |
626 | * | |
627 | * +---------<---------v------------>----------+ |
628 | * | | |
629 | * +--------v----------------+ | |
630 | * | edp_phy_pll_link_clk | | |
631 | * | link_clk | | |
632 | * +--------+----------------+ | |
633 | * | | |
634 | * | | |
635 | * v v |
636 | * Input to DISPCC block | |
637 | * for link clk, crypto clk | |
638 | * and interface clock | |
639 | * | |
640 | * | |
641 | * +--------<------------+-----------------+---<---+ |
642 | * | | | |
643 | * +----v---------+ +--------v-----+ +--------v------+ |
644 | * | vco_divided | | vco_divided | | vco_divided | |
645 | * | _clk_src | | _clk_src | | _clk_src | |
646 | * | | | | | | |
647 | * |divsel_six | | divsel_two | | divsel_four | |
648 | * +-------+------+ +-----+--------+ +--------+------+ |
649 | * | | | |
650 | * v---->----------v-------------<------v |
651 | * | |
652 | * +----------+-----------------+ |
653 | * | edp_phy_pll_vco_div_clk | |
654 | * +---------+------------------+ |
655 | * | |
656 | * v |
657 | * Input to DISPCC block |
658 | * for EDP pixel clock |
659 | * |
660 | */ |
661 | static int qcom_edp_dp_pixel_clk_determine_rate(struct clk_hw *hw, |
662 | struct clk_rate_request *req) |
663 | { |
664 | switch (req->rate) { |
665 | case 1620000000UL / 2: |
666 | case 2700000000UL / 2: |
667 | /* 5.4 and 8.1 GHz are same link rate as 2.7GHz, i.e. div 4 and div 6 */ |
668 | return 0; |
669 | |
670 | default: |
671 | return -EINVAL; |
672 | } |
673 | } |
674 | |
675 | static unsigned long |
676 | qcom_edp_dp_pixel_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
677 | { |
678 | const struct qcom_edp *edp = container_of(hw, struct qcom_edp, dp_pixel_hw); |
679 | const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; |
680 | |
681 | switch (dp_opts->link_rate) { |
682 | case 1620: |
683 | return 1620000000UL / 2; |
684 | case 2700: |
685 | return 2700000000UL / 2; |
686 | case 5400: |
687 | return 5400000000UL / 4; |
688 | case 8100: |
689 | return 8100000000UL / 6; |
690 | default: |
691 | return 0; |
692 | } |
693 | } |
694 | |
695 | static const struct clk_ops qcom_edp_dp_pixel_clk_ops = { |
696 | .determine_rate = qcom_edp_dp_pixel_clk_determine_rate, |
697 | .recalc_rate = qcom_edp_dp_pixel_clk_recalc_rate, |
698 | }; |
699 | |
700 | static int qcom_edp_dp_link_clk_determine_rate(struct clk_hw *hw, |
701 | struct clk_rate_request *req) |
702 | { |
703 | switch (req->rate) { |
704 | case 162000000: |
705 | case 270000000: |
706 | case 540000000: |
707 | case 810000000: |
708 | return 0; |
709 | |
710 | default: |
711 | return -EINVAL; |
712 | } |
713 | } |
714 | |
715 | static unsigned long |
716 | qcom_edp_dp_link_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
717 | { |
718 | const struct qcom_edp *edp = container_of(hw, struct qcom_edp, dp_link_hw); |
719 | const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; |
720 | |
721 | switch (dp_opts->link_rate) { |
722 | case 1620: |
723 | case 2700: |
724 | case 5400: |
725 | case 8100: |
726 | return dp_opts->link_rate * 100000; |
727 | |
728 | default: |
729 | return 0; |
730 | } |
731 | } |
732 | |
733 | static const struct clk_ops qcom_edp_dp_link_clk_ops = { |
734 | .determine_rate = qcom_edp_dp_link_clk_determine_rate, |
735 | .recalc_rate = qcom_edp_dp_link_clk_recalc_rate, |
736 | }; |
737 | |
738 | static int qcom_edp_clks_register(struct qcom_edp *edp, struct device_node *np) |
739 | { |
740 | struct clk_hw_onecell_data *data; |
741 | struct clk_init_data init = { }; |
742 | char name[64]; |
743 | int ret; |
744 | |
745 | data = devm_kzalloc(dev: edp->dev, struct_size(data, hws, 2), GFP_KERNEL); |
746 | if (!data) |
747 | return -ENOMEM; |
748 | data->num = 2; |
749 | |
750 | snprintf(buf: name, size: sizeof(name), fmt: "%s::link_clk", dev_name(dev: edp->dev)); |
751 | init.ops = &qcom_edp_dp_link_clk_ops; |
752 | init.name = name; |
753 | edp->dp_link_hw.init = &init; |
754 | ret = devm_clk_hw_register(dev: edp->dev, hw: &edp->dp_link_hw); |
755 | if (ret) |
756 | return ret; |
757 | |
758 | snprintf(buf: name, size: sizeof(name), fmt: "%s::vco_div_clk", dev_name(dev: edp->dev)); |
759 | init.ops = &qcom_edp_dp_pixel_clk_ops; |
760 | init.name = name; |
761 | edp->dp_pixel_hw.init = &init; |
762 | ret = devm_clk_hw_register(dev: edp->dev, hw: &edp->dp_pixel_hw); |
763 | if (ret) |
764 | return ret; |
765 | |
766 | data->hws[0] = &edp->dp_link_hw; |
767 | data->hws[1] = &edp->dp_pixel_hw; |
768 | |
769 | return devm_of_clk_add_hw_provider(dev: edp->dev, get: of_clk_hw_onecell_get, data); |
770 | } |
771 | |
772 | static int qcom_edp_phy_probe(struct platform_device *pdev) |
773 | { |
774 | struct phy_provider *phy_provider; |
775 | struct device *dev = &pdev->dev; |
776 | struct qcom_edp *edp; |
777 | int ret; |
778 | |
779 | edp = devm_kzalloc(dev, size: sizeof(*edp), GFP_KERNEL); |
780 | if (!edp) |
781 | return -ENOMEM; |
782 | |
783 | edp->dev = dev; |
784 | edp->cfg = of_device_get_match_data(dev: &pdev->dev); |
785 | |
786 | edp->edp = devm_platform_ioremap_resource(pdev, index: 0); |
787 | if (IS_ERR(ptr: edp->edp)) |
788 | return PTR_ERR(ptr: edp->edp); |
789 | |
790 | edp->tx0 = devm_platform_ioremap_resource(pdev, index: 1); |
791 | if (IS_ERR(ptr: edp->tx0)) |
792 | return PTR_ERR(ptr: edp->tx0); |
793 | |
794 | edp->tx1 = devm_platform_ioremap_resource(pdev, index: 2); |
795 | if (IS_ERR(ptr: edp->tx1)) |
796 | return PTR_ERR(ptr: edp->tx1); |
797 | |
798 | edp->pll = devm_platform_ioremap_resource(pdev, index: 3); |
799 | if (IS_ERR(ptr: edp->pll)) |
800 | return PTR_ERR(ptr: edp->pll); |
801 | |
802 | edp->clks[0].id = "aux"; |
803 | edp->clks[1].id = "cfg_ahb"; |
804 | ret = devm_clk_bulk_get(dev, ARRAY_SIZE(edp->clks), clks: edp->clks); |
805 | if (ret) |
806 | return ret; |
807 | |
808 | edp->supplies[0].supply = "vdda-phy"; |
809 | edp->supplies[1].supply = "vdda-pll"; |
810 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(edp->supplies), consumers: edp->supplies); |
811 | if (ret) |
812 | return ret; |
813 | |
814 | ret = regulator_set_load(regulator: edp->supplies[0].consumer, load_uA: 21800); /* 1.2 V vdda-phy */ |
815 | if (ret) { |
816 | dev_err(dev, "failed to set load at %s\n", edp->supplies[0].supply); |
817 | return ret; |
818 | } |
819 | |
820 | ret = regulator_set_load(regulator: edp->supplies[1].consumer, load_uA: 36000); /* 0.9 V vdda-pll */ |
821 | if (ret) { |
822 | dev_err(dev, "failed to set load at %s\n", edp->supplies[1].supply); |
823 | return ret; |
824 | } |
825 | |
826 | ret = qcom_edp_clks_register(edp, np: pdev->dev.of_node); |
827 | if (ret) |
828 | return ret; |
829 | |
830 | edp->phy = devm_phy_create(dev, node: pdev->dev.of_node, ops: &qcom_edp_ops); |
831 | if (IS_ERR(ptr: edp->phy)) { |
832 | dev_err(dev, "failed to register phy\n"); |
833 | return PTR_ERR(ptr: edp->phy); |
834 | } |
835 | |
836 | phy_set_drvdata(phy: edp->phy, data: edp); |
837 | |
838 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
839 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
840 | } |
841 | |
842 | static const struct of_device_id qcom_edp_phy_match_table[] = { |
843 | { .compatible = "qcom,sc7280-edp-phy"}, |
844 | { .compatible = "qcom,sc8180x-edp-phy"}, |
845 | { .compatible = "qcom,sc8280xp-dp-phy", .data = &dp_phy_cfg }, |
846 | { .compatible = "qcom,sc8280xp-edp-phy", .data = &edp_phy_cfg }, |
847 | { } |
848 | }; |
849 | MODULE_DEVICE_TABLE(of, qcom_edp_phy_match_table); |
850 | |
851 | static struct platform_driver qcom_edp_phy_driver = { |
852 | .probe = qcom_edp_phy_probe, |
853 | .driver = { |
854 | .name = "qcom-edp-phy", |
855 | .of_match_table = qcom_edp_phy_match_table, |
856 | }, |
857 | }; |
858 | |
859 | module_platform_driver(qcom_edp_phy_driver); |
860 | |
861 | MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@linaro.org>"); |
862 | MODULE_DESCRIPTION("Qualcomm eDP QMP PHY driver"); |
863 | MODULE_LICENSE("GPL v2"); |
864 |
Definitions
- qcom_edp_cfg
- qcom_edp
- dp_swing_hbr_rbr
- dp_pre_emp_hbr_rbr
- dp_swing_hbr2_hbr3
- dp_pre_emp_hbr2_hbr3
- dp_phy_cfg
- edp_swing_hbr_rbr
- edp_pre_emp_hbr_rbr
- edp_swing_hbr2_hbr3
- edp_pre_emp_hbr2_hbr3
- edp_phy_cfg
- qcom_edp_phy_init
- qcom_edp_set_voltages
- qcom_edp_phy_configure
- qcom_edp_configure_ssc
- qcom_edp_configure_pll
- qcom_edp_set_vco_div
- qcom_edp_phy_power_on
- qcom_edp_phy_power_off
- qcom_edp_phy_exit
- qcom_edp_ops
- qcom_edp_dp_pixel_clk_determine_rate
- qcom_edp_dp_pixel_clk_recalc_rate
- qcom_edp_dp_pixel_clk_ops
- qcom_edp_dp_link_clk_determine_rate
- qcom_edp_dp_link_clk_recalc_rate
- qcom_edp_dp_link_clk_ops
- qcom_edp_clks_register
- qcom_edp_phy_probe
- qcom_edp_phy_match_table
Improve your Profiling and Debugging skills
Find out more