diff options
author | Hai Li <hali@codeaurora.org> | 2015-03-31 14:36:33 -0400 |
---|---|---|
committer | Rob Clark <robdclark@gmail.com> | 2015-04-01 19:29:38 -0400 |
commit | a689554ba6ed81cf606c16539f6ffc2a1dcdaf8e (patch) | |
tree | eda42f5e85c4960fcc53f723b03d2e3d4b5d70cd /drivers/gpu/drm/msm/dsi/dsi_phy.c | |
parent | 7a6dc9550d0a17e3f24b2c13582f093193cd08ef (diff) |
drm/msm: Initial add DSI connector support
This change adds the DSI connector support in msm drm driver.
v1: Initial change
v2:
- Address comments from Archit + minor clean-ups
- Rebase to not depend on msm_drm_sub_dev change [Rob's comment]
v3: Fix issues when initialization is failed
Signed-off-by: Hai Li <hali@codeaurora.org>
Signed-off-by: Rob Clark <robdclark@gmail.com>
Diffstat (limited to 'drivers/gpu/drm/msm/dsi/dsi_phy.c')
-rw-r--r-- | drivers/gpu/drm/msm/dsi/dsi_phy.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/dsi/dsi_phy.c b/drivers/gpu/drm/msm/dsi/dsi_phy.c new file mode 100644 index 000000000000..f0cea8927388 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi/dsi_phy.c | |||
@@ -0,0 +1,352 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015, The Linux Foundation. All rights reserved. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 and | ||
6 | * only version 2 as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | #include "dsi.h" | ||
15 | #include "dsi.xml.h" | ||
16 | |||
17 | #define dsi_phy_read(offset) msm_readl((offset)) | ||
18 | #define dsi_phy_write(offset, data) msm_writel((data), (offset)) | ||
19 | |||
20 | struct dsi_dphy_timing { | ||
21 | u32 clk_pre; | ||
22 | u32 clk_post; | ||
23 | u32 clk_zero; | ||
24 | u32 clk_trail; | ||
25 | u32 clk_prepare; | ||
26 | u32 hs_exit; | ||
27 | u32 hs_zero; | ||
28 | u32 hs_prepare; | ||
29 | u32 hs_trail; | ||
30 | u32 hs_rqst; | ||
31 | u32 ta_go; | ||
32 | u32 ta_sure; | ||
33 | u32 ta_get; | ||
34 | }; | ||
35 | |||
36 | struct msm_dsi_phy { | ||
37 | void __iomem *base; | ||
38 | void __iomem *reg_base; | ||
39 | int id; | ||
40 | struct dsi_dphy_timing timing; | ||
41 | int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, | ||
42 | const unsigned long bit_rate, const unsigned long esc_rate); | ||
43 | int (*disable)(struct msm_dsi_phy *phy); | ||
44 | }; | ||
45 | |||
46 | #define S_DIV_ROUND_UP(n, d) \ | ||
47 | (((n) >= 0) ? (((n) + (d) - 1) / (d)) : (((n) - (d) + 1) / (d))) | ||
48 | |||
49 | static inline s32 linear_inter(s32 tmax, s32 tmin, s32 percent, | ||
50 | s32 min_result, bool even) | ||
51 | { | ||
52 | s32 v; | ||
53 | v = (tmax - tmin) * percent; | ||
54 | v = S_DIV_ROUND_UP(v, 100) + tmin; | ||
55 | if (even && (v & 0x1)) | ||
56 | return max_t(s32, min_result, v - 1); | ||
57 | else | ||
58 | return max_t(s32, min_result, v); | ||
59 | } | ||
60 | |||
61 | static void dsi_dphy_timing_calc_clk_zero(struct dsi_dphy_timing *timing, | ||
62 | s32 ui, s32 coeff, s32 pcnt) | ||
63 | { | ||
64 | s32 tmax, tmin, clk_z; | ||
65 | s32 temp; | ||
66 | |||
67 | /* reset */ | ||
68 | temp = 300 * coeff - ((timing->clk_prepare >> 1) + 1) * 2 * ui; | ||
69 | tmin = S_DIV_ROUND_UP(temp, ui) - 2; | ||
70 | if (tmin > 255) { | ||
71 | tmax = 511; | ||
72 | clk_z = linear_inter(2 * tmin, tmin, pcnt, 0, true); | ||
73 | } else { | ||
74 | tmax = 255; | ||
75 | clk_z = linear_inter(tmax, tmin, pcnt, 0, true); | ||
76 | } | ||
77 | |||
78 | /* adjust */ | ||
79 | temp = (timing->hs_rqst + timing->clk_prepare + clk_z) & 0x7; | ||
80 | timing->clk_zero = clk_z + 8 - temp; | ||
81 | } | ||
82 | |||
83 | static int dsi_dphy_timing_calc(struct dsi_dphy_timing *timing, | ||
84 | const unsigned long bit_rate, const unsigned long esc_rate) | ||
85 | { | ||
86 | s32 ui, lpx; | ||
87 | s32 tmax, tmin; | ||
88 | s32 pcnt0 = 10; | ||
89 | s32 pcnt1 = (bit_rate > 1200000000) ? 15 : 10; | ||
90 | s32 pcnt2 = 10; | ||
91 | s32 pcnt3 = (bit_rate > 180000000) ? 10 : 40; | ||
92 | s32 coeff = 1000; /* Precision, should avoid overflow */ | ||
93 | s32 temp; | ||
94 | |||
95 | if (!bit_rate || !esc_rate) | ||
96 | return -EINVAL; | ||
97 | |||
98 | ui = mult_frac(NSEC_PER_MSEC, coeff, bit_rate / 1000); | ||
99 | lpx = mult_frac(NSEC_PER_MSEC, coeff, esc_rate / 1000); | ||
100 | |||
101 | tmax = S_DIV_ROUND_UP(95 * coeff, ui) - 2; | ||
102 | tmin = S_DIV_ROUND_UP(38 * coeff, ui) - 2; | ||
103 | timing->clk_prepare = linear_inter(tmax, tmin, pcnt0, 0, true); | ||
104 | |||
105 | temp = lpx / ui; | ||
106 | if (temp & 0x1) | ||
107 | timing->hs_rqst = temp; | ||
108 | else | ||
109 | timing->hs_rqst = max_t(s32, 0, temp - 2); | ||
110 | |||
111 | /* Calculate clk_zero after clk_prepare and hs_rqst */ | ||
112 | dsi_dphy_timing_calc_clk_zero(timing, ui, coeff, pcnt2); | ||
113 | |||
114 | temp = 105 * coeff + 12 * ui - 20 * coeff; | ||
115 | tmax = S_DIV_ROUND_UP(temp, ui) - 2; | ||
116 | tmin = S_DIV_ROUND_UP(60 * coeff, ui) - 2; | ||
117 | timing->clk_trail = linear_inter(tmax, tmin, pcnt3, 0, true); | ||
118 | |||
119 | temp = 85 * coeff + 6 * ui; | ||
120 | tmax = S_DIV_ROUND_UP(temp, ui) - 2; | ||
121 | temp = 40 * coeff + 4 * ui; | ||
122 | tmin = S_DIV_ROUND_UP(temp, ui) - 2; | ||
123 | timing->hs_prepare = linear_inter(tmax, tmin, pcnt1, 0, true); | ||
124 | |||
125 | tmax = 255; | ||
126 | temp = ((timing->hs_prepare >> 1) + 1) * 2 * ui + 2 * ui; | ||
127 | temp = 145 * coeff + 10 * ui - temp; | ||
128 | tmin = S_DIV_ROUND_UP(temp, ui) - 2; | ||
129 | timing->hs_zero = linear_inter(tmax, tmin, pcnt2, 24, true); | ||
130 | |||
131 | temp = 105 * coeff + 12 * ui - 20 * coeff; | ||
132 | tmax = S_DIV_ROUND_UP(temp, ui) - 2; | ||
133 | temp = 60 * coeff + 4 * ui; | ||
134 | tmin = DIV_ROUND_UP(temp, ui) - 2; | ||
135 | timing->hs_trail = linear_inter(tmax, tmin, pcnt3, 0, true); | ||
136 | |||
137 | tmax = 255; | ||
138 | tmin = S_DIV_ROUND_UP(100 * coeff, ui) - 2; | ||
139 | timing->hs_exit = linear_inter(tmax, tmin, pcnt2, 0, true); | ||
140 | |||
141 | tmax = 63; | ||
142 | temp = ((timing->hs_exit >> 1) + 1) * 2 * ui; | ||
143 | temp = 60 * coeff + 52 * ui - 24 * ui - temp; | ||
144 | tmin = S_DIV_ROUND_UP(temp, 8 * ui) - 1; | ||
145 | timing->clk_post = linear_inter(tmax, tmin, pcnt2, 0, false); | ||
146 | |||
147 | tmax = 63; | ||
148 | temp = ((timing->clk_prepare >> 1) + 1) * 2 * ui; | ||
149 | temp += ((timing->clk_zero >> 1) + 1) * 2 * ui; | ||
150 | temp += 8 * ui + lpx; | ||
151 | tmin = S_DIV_ROUND_UP(temp, 8 * ui) - 1; | ||
152 | if (tmin > tmax) { | ||
153 | temp = linear_inter(2 * tmax, tmin, pcnt2, 0, false) >> 1; | ||
154 | timing->clk_pre = temp >> 1; | ||
155 | temp = (2 * tmax - tmin) * pcnt2; | ||
156 | } else { | ||
157 | timing->clk_pre = linear_inter(tmax, tmin, pcnt2, 0, false); | ||
158 | } | ||
159 | |||
160 | timing->ta_go = 3; | ||
161 | timing->ta_sure = 0; | ||
162 | timing->ta_get = 4; | ||
163 | |||
164 | DBG("PHY timings: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", | ||
165 | timing->clk_pre, timing->clk_post, timing->clk_zero, | ||
166 | timing->clk_trail, timing->clk_prepare, timing->hs_exit, | ||
167 | timing->hs_zero, timing->hs_prepare, timing->hs_trail, | ||
168 | timing->hs_rqst); | ||
169 | |||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static void dsi_28nm_phy_regulator_ctrl(struct msm_dsi_phy *phy, bool enable) | ||
174 | { | ||
175 | void __iomem *base = phy->reg_base; | ||
176 | |||
177 | if (!enable) { | ||
178 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CAL_PWR_CFG, 0); | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_0, 0x0); | ||
183 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CAL_PWR_CFG, 1); | ||
184 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_5, 0); | ||
185 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_3, 0); | ||
186 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_2, 0x3); | ||
187 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_1, 0x9); | ||
188 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_0, 0x7); | ||
189 | dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_4, 0x20); | ||
190 | } | ||
191 | |||
192 | static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, | ||
193 | const unsigned long bit_rate, const unsigned long esc_rate) | ||
194 | { | ||
195 | struct dsi_dphy_timing *timing = &phy->timing; | ||
196 | int i; | ||
197 | void __iomem *base = phy->base; | ||
198 | |||
199 | DBG(""); | ||
200 | |||
201 | if (dsi_dphy_timing_calc(timing, bit_rate, esc_rate)) { | ||
202 | pr_err("%s: D-PHY timing calculation failed\n", __func__); | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | |||
206 | dsi_phy_write(base + REG_DSI_28nm_PHY_STRENGTH_0, 0xff); | ||
207 | |||
208 | dsi_28nm_phy_regulator_ctrl(phy, true); | ||
209 | |||
210 | dsi_phy_write(base + REG_DSI_28nm_PHY_LDO_CNTRL, 0x00); | ||
211 | |||
212 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_0, | ||
213 | DSI_28nm_PHY_TIMING_CTRL_0_CLK_ZERO(timing->clk_zero)); | ||
214 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_1, | ||
215 | DSI_28nm_PHY_TIMING_CTRL_1_CLK_TRAIL(timing->clk_trail)); | ||
216 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_2, | ||
217 | DSI_28nm_PHY_TIMING_CTRL_2_CLK_PREPARE(timing->clk_prepare)); | ||
218 | if (timing->clk_zero & BIT(8)) | ||
219 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_3, | ||
220 | DSI_28nm_PHY_TIMING_CTRL_3_CLK_ZERO_8); | ||
221 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_4, | ||
222 | DSI_28nm_PHY_TIMING_CTRL_4_HS_EXIT(timing->hs_exit)); | ||
223 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_5, | ||
224 | DSI_28nm_PHY_TIMING_CTRL_5_HS_ZERO(timing->hs_zero)); | ||
225 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_6, | ||
226 | DSI_28nm_PHY_TIMING_CTRL_6_HS_PREPARE(timing->hs_prepare)); | ||
227 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_7, | ||
228 | DSI_28nm_PHY_TIMING_CTRL_7_HS_TRAIL(timing->hs_trail)); | ||
229 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_8, | ||
230 | DSI_28nm_PHY_TIMING_CTRL_8_HS_RQST(timing->hs_rqst)); | ||
231 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_9, | ||
232 | DSI_28nm_PHY_TIMING_CTRL_9_TA_GO(timing->ta_go) | | ||
233 | DSI_28nm_PHY_TIMING_CTRL_9_TA_SURE(timing->ta_sure)); | ||
234 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_10, | ||
235 | DSI_28nm_PHY_TIMING_CTRL_10_TA_GET(timing->ta_get)); | ||
236 | dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_11, | ||
237 | DSI_28nm_PHY_TIMING_CTRL_11_TRIG3_CMD(0)); | ||
238 | |||
239 | dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_1, 0x00); | ||
240 | dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f); | ||
241 | |||
242 | dsi_phy_write(base + REG_DSI_28nm_PHY_STRENGTH_1, 0x6); | ||
243 | |||
244 | for (i = 0; i < 4; i++) { | ||
245 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_0(i), 0); | ||
246 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_1(i), 0); | ||
247 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_2(i), 0); | ||
248 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_3(i), 0); | ||
249 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_DATAPATH(i), 0); | ||
250 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_DEBUG_SEL(i), 0); | ||
251 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_STR_0(i), 0x1); | ||
252 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_STR_1(i), 0x97); | ||
253 | } | ||
254 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(0), 0); | ||
255 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(1), 0x5); | ||
256 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(2), 0xa); | ||
257 | dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(3), 0xf); | ||
258 | |||
259 | dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_CFG_1, 0xc0); | ||
260 | dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_TEST_STR0, 0x1); | ||
261 | dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_TEST_STR1, 0xbb); | ||
262 | |||
263 | dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f); | ||
264 | |||
265 | if (is_dual_panel && (phy->id != DSI_CLOCK_MASTER)) | ||
266 | dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x00); | ||
267 | else | ||
268 | dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x01); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static int dsi_28nm_phy_disable(struct msm_dsi_phy *phy) | ||
274 | { | ||
275 | dsi_phy_write(phy->base + REG_DSI_28nm_PHY_CTRL_0, 0); | ||
276 | dsi_28nm_phy_regulator_ctrl(phy, false); | ||
277 | |||
278 | /* | ||
279 | * Wait for the registers writes to complete in order to | ||
280 | * ensure that the phy is completely disabled | ||
281 | */ | ||
282 | wmb(); | ||
283 | |||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | #define dsi_phy_func_init(name) \ | ||
288 | do { \ | ||
289 | phy->enable = dsi_##name##_phy_enable; \ | ||
290 | phy->disable = dsi_##name##_phy_disable; \ | ||
291 | } while (0) | ||
292 | |||
293 | struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev, | ||
294 | enum msm_dsi_phy_type type, int id) | ||
295 | { | ||
296 | struct msm_dsi_phy *phy; | ||
297 | |||
298 | phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); | ||
299 | if (!phy) | ||
300 | return NULL; | ||
301 | |||
302 | phy->base = msm_ioremap(pdev, "dsi_phy", "DSI_PHY"); | ||
303 | if (IS_ERR_OR_NULL(phy->base)) { | ||
304 | pr_err("%s: failed to map phy base\n", __func__); | ||
305 | return NULL; | ||
306 | } | ||
307 | phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator", "DSI_PHY_REG"); | ||
308 | if (IS_ERR_OR_NULL(phy->reg_base)) { | ||
309 | pr_err("%s: failed to map phy regulator base\n", __func__); | ||
310 | return NULL; | ||
311 | } | ||
312 | |||
313 | switch (type) { | ||
314 | case MSM_DSI_PHY_28NM: | ||
315 | dsi_phy_func_init(28nm); | ||
316 | break; | ||
317 | default: | ||
318 | pr_err("%s: unsupported type, %d\n", __func__, type); | ||
319 | return NULL; | ||
320 | } | ||
321 | |||
322 | phy->id = id; | ||
323 | |||
324 | return phy; | ||
325 | } | ||
326 | |||
327 | int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, | ||
328 | const unsigned long bit_rate, const unsigned long esc_rate) | ||
329 | { | ||
330 | if (!phy || !phy->enable) | ||
331 | return -EINVAL; | ||
332 | return phy->enable(phy, is_dual_panel, bit_rate, esc_rate); | ||
333 | } | ||
334 | |||
335 | int msm_dsi_phy_disable(struct msm_dsi_phy *phy) | ||
336 | { | ||
337 | if (!phy || !phy->disable) | ||
338 | return -EINVAL; | ||
339 | return phy->disable(phy); | ||
340 | } | ||
341 | |||
342 | void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy, | ||
343 | u32 *clk_pre, u32 *clk_post) | ||
344 | { | ||
345 | if (!phy) | ||
346 | return; | ||
347 | if (clk_pre) | ||
348 | *clk_pre = phy->timing.clk_pre; | ||
349 | if (clk_post) | ||
350 | *clk_post = phy->timing.clk_post; | ||
351 | } | ||
352 | |||