]>
Commit | Line | Data |
---|---|---|
af85389c BS |
1 | /* |
2 | * Copyright 2014 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
24 | #include "dp.h" | |
25 | #include "conn.h" | |
9648da5a | 26 | #include "head.h" |
75eefe95 | 27 | #include "ior.h" |
af85389c BS |
28 | |
29 | #include <subdev/bios.h> | |
30 | #include <subdev/bios/init.h> | |
31 | #include <subdev/i2c.h> | |
32 | ||
33 | #include <nvif/event.h> | |
34 | ||
35 | struct lt_state { | |
f3e70d29 | 36 | struct nvkm_dp *dp; |
af85389c BS |
37 | u8 stat[6]; |
38 | u8 conf[4]; | |
39 | bool pc2; | |
40 | u8 pc2stat; | |
41 | u8 pc2conf[2]; | |
42 | }; | |
43 | ||
44 | static int | |
45 | nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay) | |
46 | { | |
f3e70d29 | 47 | struct nvkm_dp *dp = lt->dp; |
af85389c BS |
48 | int ret; |
49 | ||
f3e70d29 BS |
50 | if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL]) |
51 | mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4); | |
af85389c BS |
52 | else |
53 | udelay(delay); | |
54 | ||
f3e70d29 | 55 | ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6); |
af85389c BS |
56 | if (ret) |
57 | return ret; | |
58 | ||
59 | if (pc) { | |
f3e70d29 | 60 | ret = nvkm_rdaux(dp->aux, DPCD_LS0C, <->pc2stat, 1); |
af85389c BS |
61 | if (ret) |
62 | lt->pc2stat = 0x00; | |
f3e70d29 BS |
63 | OUTP_TRACE(&dp->outp, "status %6ph pc2 %02x", |
64 | lt->stat, lt->pc2stat); | |
af85389c | 65 | } else { |
f3e70d29 | 66 | OUTP_TRACE(&dp->outp, "status %6ph", lt->stat); |
af85389c BS |
67 | } |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | static int | |
73 | nvkm_dp_train_drive(struct lt_state *lt, bool pc) | |
74 | { | |
f3e70d29 | 75 | struct nvkm_dp *dp = lt->dp; |
7d1fede0 BS |
76 | struct nvkm_ior *ior = dp->outp.ior; |
77 | struct nvkm_bios *bios = ior->disp->engine.subdev.device->bios; | |
78 | struct nvbios_dpout info; | |
79 | struct nvbios_dpcfg ocfg; | |
80 | u8 ver, hdr, cnt, len; | |
81 | u32 data; | |
af85389c BS |
82 | int ret, i; |
83 | ||
7d1fede0 | 84 | for (i = 0; i < ior->dp.nr; i++) { |
af85389c BS |
85 | u8 lane = (lt->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; |
86 | u8 lpc2 = (lt->pc2stat >> (i * 2)) & 0x3; | |
87 | u8 lpre = (lane & 0x0c) >> 2; | |
88 | u8 lvsw = (lane & 0x03) >> 0; | |
89 | u8 hivs = 3 - lpre; | |
90 | u8 hipe = 3; | |
91 | u8 hipc = 3; | |
92 | ||
93 | if (lpc2 >= hipc) | |
94 | lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED; | |
95 | if (lpre >= hipe) { | |
96 | lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */ | |
97 | lvsw = hivs = 3 - (lpre & 3); | |
98 | } else | |
99 | if (lvsw >= hivs) { | |
100 | lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED; | |
101 | } | |
102 | ||
103 | lt->conf[i] = (lpre << 3) | lvsw; | |
104 | lt->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4); | |
105 | ||
f3e70d29 BS |
106 | OUTP_TRACE(&dp->outp, "config lane %d %02x %02x", |
107 | i, lt->conf[i], lpc2); | |
7d1fede0 BS |
108 | |
109 | data = nvbios_dpout_match(bios, dp->outp.info.hasht, | |
110 | dp->outp.info.hashm, | |
111 | &ver, &hdr, &cnt, &len, &info); | |
112 | if (!data) | |
113 | continue; | |
114 | ||
115 | data = nvbios_dpcfg_match(bios, data, lpc2 & 3, lvsw & 3, | |
116 | lpre & 3, &ver, &hdr, &cnt, &len, | |
117 | &ocfg); | |
118 | if (!data) | |
119 | continue; | |
120 | ||
121 | ior->func->dp.drive(ior, i, ocfg.pc, ocfg.dc, | |
122 | ocfg.pe, ocfg.tx_pu); | |
af85389c BS |
123 | } |
124 | ||
f3e70d29 | 125 | ret = nvkm_wraux(dp->aux, DPCD_LC03(0), lt->conf, 4); |
af85389c BS |
126 | if (ret) |
127 | return ret; | |
128 | ||
129 | if (pc) { | |
f3e70d29 | 130 | ret = nvkm_wraux(dp->aux, DPCD_LC0F, lt->pc2conf, 2); |
af85389c BS |
131 | if (ret) |
132 | return ret; | |
133 | } | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | static void | |
139 | nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern) | |
140 | { | |
f3e70d29 | 141 | struct nvkm_dp *dp = lt->dp; |
af85389c BS |
142 | u8 sink_tp; |
143 | ||
f3e70d29 | 144 | OUTP_TRACE(&dp->outp, "training pattern %d", pattern); |
a1de2b52 | 145 | dp->outp.ior->func->dp.pattern(dp->outp.ior, pattern); |
af85389c | 146 | |
f3e70d29 | 147 | nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); |
af85389c BS |
148 | sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; |
149 | sink_tp |= pattern; | |
f3e70d29 | 150 | nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1); |
af85389c BS |
151 | } |
152 | ||
153 | static int | |
154 | nvkm_dp_train_eq(struct lt_state *lt) | |
155 | { | |
af85389c BS |
156 | bool eq_done = false, cr_done = true; |
157 | int tries = 0, i; | |
158 | ||
75eefe95 | 159 | if (lt->dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED) |
af85389c BS |
160 | nvkm_dp_train_pattern(lt, 3); |
161 | else | |
162 | nvkm_dp_train_pattern(lt, 2); | |
163 | ||
164 | do { | |
165 | if ((tries && | |
166 | nvkm_dp_train_drive(lt, lt->pc2)) || | |
167 | nvkm_dp_train_sense(lt, lt->pc2, 400)) | |
168 | break; | |
169 | ||
170 | eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE); | |
75eefe95 | 171 | for (i = 0; i < lt->dp->outp.ior->dp.nr && eq_done; i++) { |
af85389c BS |
172 | u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; |
173 | if (!(lane & DPCD_LS02_LANE0_CR_DONE)) | |
174 | cr_done = false; | |
175 | if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || | |
176 | !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) | |
177 | eq_done = false; | |
178 | } | |
179 | } while (!eq_done && cr_done && ++tries <= 5); | |
180 | ||
181 | return eq_done ? 0 : -1; | |
182 | } | |
183 | ||
184 | static int | |
185 | nvkm_dp_train_cr(struct lt_state *lt) | |
186 | { | |
187 | bool cr_done = false, abort = false; | |
188 | int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; | |
189 | int tries = 0, i; | |
190 | ||
191 | nvkm_dp_train_pattern(lt, 1); | |
192 | ||
193 | do { | |
194 | if (nvkm_dp_train_drive(lt, false) || | |
195 | nvkm_dp_train_sense(lt, false, 100)) | |
196 | break; | |
197 | ||
198 | cr_done = true; | |
75eefe95 | 199 | for (i = 0; i < lt->dp->outp.ior->dp.nr; i++) { |
af85389c BS |
200 | u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; |
201 | if (!(lane & DPCD_LS02_LANE0_CR_DONE)) { | |
202 | cr_done = false; | |
203 | if (lt->conf[i] & DPCD_LC03_MAX_SWING_REACHED) | |
204 | abort = true; | |
205 | break; | |
206 | } | |
207 | } | |
208 | ||
209 | if ((lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) { | |
210 | voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; | |
211 | tries = 0; | |
212 | } | |
213 | } while (!cr_done && !abort && ++tries < 5); | |
214 | ||
215 | return cr_done ? 0 : -1; | |
216 | } | |
217 | ||
218 | static int | |
4423c743 | 219 | nvkm_dp_train_links(struct nvkm_dp *dp) |
af85389c | 220 | { |
75eefe95 | 221 | struct nvkm_ior *ior = dp->outp.ior; |
f3e70d29 | 222 | struct nvkm_disp *disp = dp->outp.disp; |
af85389c BS |
223 | struct nvkm_subdev *subdev = &disp->engine.subdev; |
224 | struct nvkm_bios *bios = subdev->device->bios; | |
4423c743 BS |
225 | struct lt_state lt = { |
226 | .dp = dp, | |
227 | }; | |
af85389c BS |
228 | u32 lnkcmp; |
229 | u8 sink[2]; | |
230 | int ret; | |
231 | ||
75eefe95 BS |
232 | OUTP_DBG(&dp->outp, "training %d x %d MB/s", |
233 | ior->dp.nr, ior->dp.bw * 27); | |
af85389c BS |
234 | |
235 | /* Intersect misc. capabilities of the OR and sink. */ | |
236 | if (disp->engine.subdev.device->chipset < 0xd0) | |
4423c743 BS |
237 | dp->dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED; |
238 | lt.pc2 = dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED; | |
af85389c BS |
239 | |
240 | /* Set desired link configuration on the source. */ | |
4423c743 | 241 | if ((lnkcmp = lt.dp->info.lnkcmp)) { |
f3e70d29 | 242 | if (dp->version < 0x30) { |
75eefe95 | 243 | while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp)) |
af85389c | 244 | lnkcmp += 4; |
32a232c5 | 245 | lnkcmp = nvbios_rd16(bios, lnkcmp + 2); |
af85389c | 246 | } else { |
75eefe95 | 247 | while (ior->dp.bw < nvbios_rd08(bios, lnkcmp)) |
af85389c | 248 | lnkcmp += 3; |
32a232c5 | 249 | lnkcmp = nvbios_rd16(bios, lnkcmp + 1); |
af85389c BS |
250 | } |
251 | ||
32a232c5 BS |
252 | nvbios_init(subdev, lnkcmp, |
253 | init.outp = &dp->outp.info; | |
254 | init.or = ior->id; | |
255 | init.link = ior->asy.link; | |
256 | ); | |
af85389c BS |
257 | } |
258 | ||
7dc0bac4 | 259 | ret = ior->func->dp.links(ior, dp->aux); |
af85389c | 260 | if (ret) { |
7dc0bac4 BS |
261 | if (ret < 0) { |
262 | OUTP_ERR(&dp->outp, "train failed with %d", ret); | |
263 | return ret; | |
264 | } | |
265 | return 0; | |
af85389c BS |
266 | } |
267 | ||
a3e81117 | 268 | ior->func->dp.power(ior, ior->dp.nr); |
af85389c BS |
269 | |
270 | /* Set desired link configuration on the sink. */ | |
75eefe95 BS |
271 | sink[0] = ior->dp.bw; |
272 | sink[1] = ior->dp.nr; | |
273 | if (ior->dp.ef) | |
af85389c BS |
274 | sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN; |
275 | ||
4423c743 BS |
276 | ret = nvkm_wraux(dp->aux, DPCD_LC00_LINK_BW_SET, sink, 2); |
277 | if (ret) | |
278 | return ret; | |
279 | ||
280 | /* Attempt to train the link in this configuration. */ | |
281 | memset(lt.stat, 0x00, sizeof(lt.stat)); | |
282 | ret = nvkm_dp_train_cr(<); | |
283 | if (ret == 0) | |
284 | ret = nvkm_dp_train_eq(<); | |
285 | nvkm_dp_train_pattern(<, 0); | |
286 | return ret; | |
af85389c BS |
287 | } |
288 | ||
289 | static void | |
4423c743 | 290 | nvkm_dp_train_fini(struct nvkm_dp *dp) |
af85389c | 291 | { |
af85389c | 292 | /* Execute AfterLinkTraining script from DP Info table. */ |
32a232c5 BS |
293 | nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[1], |
294 | init.outp = &dp->outp.info; | |
295 | init.or = dp->outp.ior->id; | |
296 | init.link = dp->outp.ior->asy.link; | |
297 | ); | |
af85389c BS |
298 | } |
299 | ||
300 | static void | |
4423c743 | 301 | nvkm_dp_train_init(struct nvkm_dp *dp) |
af85389c | 302 | { |
af85389c | 303 | /* Execute EnableSpread/DisableSpread script from DP Info table. */ |
32a232c5 BS |
304 | if (dp->dpcd[DPCD_RC03] & DPCD_RC03_MAX_DOWNSPREAD) { |
305 | nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[2], | |
306 | init.outp = &dp->outp.info; | |
307 | init.or = dp->outp.ior->id; | |
308 | init.link = dp->outp.ior->asy.link; | |
309 | ); | |
310 | } else { | |
311 | nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[3], | |
312 | init.outp = &dp->outp.info; | |
313 | init.or = dp->outp.ior->id; | |
314 | init.link = dp->outp.ior->asy.link; | |
315 | ); | |
316 | } | |
af85389c | 317 | |
f3e70d29 | 318 | /* Execute BeforeLinkTraining script from DP Info table. */ |
32a232c5 BS |
319 | nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[0], |
320 | init.outp = &dp->outp.info; | |
321 | init.or = dp->outp.ior->id; | |
322 | init.link = dp->outp.ior->asy.link; | |
323 | ); | |
af85389c BS |
324 | } |
325 | ||
326 | static const struct dp_rates { | |
327 | u32 rate; | |
328 | u8 bw; | |
329 | u8 nr; | |
330 | } nvkm_dp_rates[] = { | |
331 | { 2160000, 0x14, 4 }, | |
332 | { 1080000, 0x0a, 4 }, | |
333 | { 1080000, 0x14, 2 }, | |
334 | { 648000, 0x06, 4 }, | |
335 | { 540000, 0x0a, 2 }, | |
336 | { 540000, 0x14, 1 }, | |
337 | { 324000, 0x06, 2 }, | |
338 | { 270000, 0x0a, 1 }, | |
339 | { 162000, 0x06, 1 }, | |
340 | {} | |
341 | }; | |
342 | ||
fafa8b5c | 343 | static int |
49f2b376 | 344 | nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps) |
af85389c | 345 | { |
75eefe95 BS |
346 | struct nvkm_ior *ior = dp->outp.ior; |
347 | const u8 sink_nr = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT; | |
348 | const u8 sink_bw = dp->dpcd[DPCD_RC01_MAX_LINK_RATE]; | |
349 | const u8 outp_nr = dp->outp.info.dpconf.link_nr; | |
350 | const u8 outp_bw = dp->outp.info.dpconf.link_bw; | |
49f2b376 BS |
351 | const struct dp_rates *failsafe = NULL, *cfg; |
352 | int ret = -EINVAL; | |
af85389c | 353 | u8 pwr; |
af85389c | 354 | |
49f2b376 BS |
355 | /* Find the lowest configuration of the OR that can support |
356 | * the required link rate. | |
357 | * | |
358 | * We will refuse to program the OR to lower rates, even if | |
359 | * link training fails at higher rates (or even if the sink | |
360 | * can't support the rate at all, though the DD is supposed | |
361 | * to prevent such situations from happening). | |
362 | * | |
363 | * Attempting to do so can cause the entire display to hang, | |
364 | * and it's better to have a failed modeset than that. | |
365 | */ | |
366 | for (cfg = nvkm_dp_rates; cfg->rate; cfg++) { | |
367 | if (cfg->nr <= outp_nr && cfg->nr <= outp_bw) | |
368 | failsafe = cfg; | |
369 | if (failsafe && cfg[1].rate < dataKBps) | |
370 | break; | |
371 | } | |
372 | ||
373 | if (WARN_ON(!failsafe)) | |
374 | return ret; | |
375 | ||
af85389c | 376 | /* Ensure sink is not in a low-power state. */ |
f3e70d29 | 377 | if (!nvkm_rdaux(dp->aux, DPCD_SC00, &pwr, 1)) { |
af85389c BS |
378 | if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) { |
379 | pwr &= ~DPCD_SC00_SET_POWER; | |
380 | pwr |= DPCD_SC00_SET_POWER_D0; | |
f3e70d29 | 381 | nvkm_wraux(dp->aux, DPCD_SC00, &pwr, 1); |
af85389c BS |
382 | } |
383 | } | |
384 | ||
385 | /* Link training. */ | |
49f2b376 BS |
386 | OUTP_DBG(&dp->outp, "training (min: %d x %d MB/s)", |
387 | failsafe->nr, failsafe->bw * 27); | |
4423c743 | 388 | nvkm_dp_train_init(dp); |
49f2b376 | 389 | for (cfg = nvkm_dp_rates; ret < 0 && cfg <= failsafe; cfg++) { |
af85389c | 390 | /* Skip configurations not supported by both OR and sink. */ |
49f2b376 BS |
391 | if ((cfg->nr > outp_nr || cfg->bw > outp_bw || |
392 | cfg->nr > sink_nr || cfg->bw > sink_bw)) { | |
393 | if (cfg != failsafe) | |
394 | continue; | |
395 | OUTP_ERR(&dp->outp, "link rate unsupported by sink"); | |
396 | } | |
75eefe95 BS |
397 | ior->dp.mst = dp->lt.mst; |
398 | ior->dp.ef = dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP; | |
399 | ior->dp.bw = cfg->bw; | |
400 | ior->dp.nr = cfg->nr; | |
af85389c BS |
401 | |
402 | /* Program selected link configuration. */ | |
4423c743 | 403 | ret = nvkm_dp_train_links(dp); |
af85389c | 404 | } |
4423c743 | 405 | nvkm_dp_train_fini(dp); |
af85389c | 406 | if (ret < 0) |
f3e70d29 | 407 | OUTP_ERR(&dp->outp, "training failed"); |
fafa8b5c BS |
408 | else |
409 | OUTP_DBG(&dp->outp, "training done"); | |
f3e70d29 | 410 | atomic_set(&dp->lt.done, 1); |
fafa8b5c | 411 | return ret; |
af85389c BS |
412 | } |
413 | ||
d52e948c BS |
414 | static void |
415 | nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior) | |
416 | { | |
417 | struct nvkm_dp *dp = nvkm_dp(outp); | |
418 | ||
419 | /* Prevent link from being retrained if sink sends an IRQ. */ | |
420 | atomic_set(&dp->lt.done, 0); | |
421 | ior->dp.nr = 0; | |
422 | ||
423 | /* Execute DisableLT script from DP Info Table. */ | |
424 | nvbios_init(&ior->disp->engine.subdev, dp->info.script[4], | |
425 | init.outp = &dp->outp.info; | |
426 | init.or = ior->id; | |
427 | init.link = ior->arm.link; | |
428 | ); | |
429 | } | |
430 | ||
8d7ef84d BS |
431 | static int |
432 | nvkm_dp_acquire(struct nvkm_outp *outp) | |
af85389c | 433 | { |
f3e70d29 | 434 | struct nvkm_dp *dp = nvkm_dp(outp); |
fafa8b5c | 435 | struct nvkm_ior *ior = dp->outp.ior; |
9648da5a | 436 | struct nvkm_head *head; |
af85389c | 437 | bool retrain = true; |
9648da5a | 438 | u32 datakbps = 0; |
fafa8b5c | 439 | u32 dataKBps; |
9648da5a | 440 | u32 linkKBps; |
fafa8b5c | 441 | u8 stat[3]; |
af85389c BS |
442 | int ret, i; |
443 | ||
f3e70d29 | 444 | mutex_lock(&dp->mutex); |
af85389c | 445 | |
fafa8b5c | 446 | /* Check that link configuration meets current requirements. */ |
9648da5a BS |
447 | list_for_each_entry(head, &outp->disp->head, head) { |
448 | if (ior->asy.head & (1 << head->id)) { | |
449 | u32 khz = (head->asy.hz >> ior->asy.rgdiv) / 1000; | |
450 | datakbps += khz * head->asy.or.depth; | |
451 | } | |
452 | } | |
453 | ||
fafa8b5c BS |
454 | linkKBps = ior->dp.bw * 27000 * ior->dp.nr; |
455 | dataKBps = DIV_ROUND_UP(datakbps, 8); | |
7d0a01a6 BS |
456 | OUTP_DBG(&dp->outp, "data %d KB/s link %d KB/s mst %d->%d", |
457 | dataKBps, linkKBps, ior->dp.mst, dp->lt.mst); | |
458 | if (linkKBps < dataKBps || ior->dp.mst != dp->lt.mst) { | |
fafa8b5c | 459 | OUTP_DBG(&dp->outp, "link requirements changed"); |
af85389c BS |
460 | goto done; |
461 | } | |
462 | ||
fafa8b5c | 463 | /* Check that link is still trained. */ |
f3e70d29 | 464 | ret = nvkm_rdaux(dp->aux, DPCD_LS02, stat, 3); |
af85389c | 465 | if (ret) { |
f3e70d29 | 466 | OUTP_DBG(&dp->outp, |
af85389c BS |
467 | "failed to read link status, assuming no sink"); |
468 | goto done; | |
469 | } | |
470 | ||
471 | if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { | |
fafa8b5c | 472 | for (i = 0; i < ior->dp.nr; i++) { |
af85389c BS |
473 | u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f; |
474 | if (!(lane & DPCD_LS02_LANE0_CR_DONE) || | |
475 | !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || | |
476 | !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) { | |
f3e70d29 | 477 | OUTP_DBG(&dp->outp, |
af85389c BS |
478 | "lane %d not equalised", lane); |
479 | goto done; | |
480 | } | |
481 | } | |
482 | retrain = false; | |
483 | } else { | |
f3e70d29 | 484 | OUTP_DBG(&dp->outp, "no inter-lane alignment"); |
af85389c BS |
485 | } |
486 | ||
487 | done: | |
49f2b376 BS |
488 | if (retrain || !atomic_read(&dp->lt.done)) |
489 | ret = nvkm_dp_train(dp, dataKBps); | |
f3e70d29 | 490 | mutex_unlock(&dp->mutex); |
af85389c BS |
491 | return ret; |
492 | } | |
493 | ||
494 | static void | |
f3e70d29 | 495 | nvkm_dp_enable(struct nvkm_dp *dp, bool enable) |
af85389c | 496 | { |
f3e70d29 | 497 | struct nvkm_i2c_aux *aux = dp->aux; |
af85389c BS |
498 | |
499 | if (enable) { | |
f3e70d29 BS |
500 | if (!dp->present) { |
501 | OUTP_DBG(&dp->outp, "aux power -> always"); | |
af85389c | 502 | nvkm_i2c_aux_monitor(aux, true); |
f3e70d29 | 503 | dp->present = true; |
af85389c BS |
504 | } |
505 | ||
f3e70d29 | 506 | if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, |
7d0a01a6 | 507 | sizeof(dp->dpcd))) |
af85389c | 508 | return; |
af85389c BS |
509 | } |
510 | ||
f3e70d29 BS |
511 | if (dp->present) { |
512 | OUTP_DBG(&dp->outp, "aux power -> demand"); | |
af85389c | 513 | nvkm_i2c_aux_monitor(aux, false); |
f3e70d29 | 514 | dp->present = false; |
af85389c BS |
515 | } |
516 | ||
f3e70d29 | 517 | atomic_set(&dp->lt.done, 0); |
af85389c BS |
518 | } |
519 | ||
520 | static int | |
f3e70d29 | 521 | nvkm_dp_hpd(struct nvkm_notify *notify) |
af85389c BS |
522 | { |
523 | const struct nvkm_i2c_ntfy_rep *line = notify->data; | |
f3e70d29 | 524 | struct nvkm_dp *dp = container_of(notify, typeof(*dp), hpd); |
981a8162 | 525 | struct nvkm_conn *conn = dp->outp.conn; |
f3e70d29 | 526 | struct nvkm_disp *disp = dp->outp.disp; |
af85389c BS |
527 | struct nvif_notify_conn_rep_v0 rep = {}; |
528 | ||
f3e70d29 | 529 | OUTP_DBG(&dp->outp, "HPD: %d", line->mask); |
22e008f9 BS |
530 | if (line->mask & NVKM_I2C_IRQ) { |
531 | if (atomic_read(&dp->lt.done)) | |
8d7ef84d | 532 | dp->outp.func->acquire(&dp->outp); |
22e008f9 BS |
533 | rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ; |
534 | } else { | |
535 | nvkm_dp_enable(dp, true); | |
536 | } | |
af85389c BS |
537 | |
538 | if (line->mask & NVKM_I2C_UNPLUG) | |
539 | rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG; | |
540 | if (line->mask & NVKM_I2C_PLUG) | |
541 | rep.mask |= NVIF_NOTIFY_CONN_V0_PLUG; | |
542 | ||
543 | nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep)); | |
544 | return NVKM_NOTIFY_KEEP; | |
545 | } | |
546 | ||
af85389c | 547 | static void |
f3e70d29 | 548 | nvkm_dp_fini(struct nvkm_outp *outp) |
af85389c | 549 | { |
f3e70d29 BS |
550 | struct nvkm_dp *dp = nvkm_dp(outp); |
551 | nvkm_notify_put(&dp->hpd); | |
f3e70d29 | 552 | nvkm_dp_enable(dp, false); |
af85389c BS |
553 | } |
554 | ||
555 | static void | |
f3e70d29 | 556 | nvkm_dp_init(struct nvkm_outp *outp) |
af85389c | 557 | { |
f3e70d29 BS |
558 | struct nvkm_dp *dp = nvkm_dp(outp); |
559 | nvkm_notify_put(&dp->outp.conn->hpd); | |
560 | nvkm_dp_enable(dp, true); | |
f3e70d29 | 561 | nvkm_notify_get(&dp->hpd); |
af85389c BS |
562 | } |
563 | ||
564 | static void * | |
f3e70d29 | 565 | nvkm_dp_dtor(struct nvkm_outp *outp) |
af85389c | 566 | { |
f3e70d29 BS |
567 | struct nvkm_dp *dp = nvkm_dp(outp); |
568 | nvkm_notify_fini(&dp->hpd); | |
f3e70d29 | 569 | return dp; |
af85389c BS |
570 | } |
571 | ||
f3e70d29 BS |
572 | static const struct nvkm_outp_func |
573 | nvkm_dp_func = { | |
574 | .dtor = nvkm_dp_dtor, | |
575 | .init = nvkm_dp_init, | |
576 | .fini = nvkm_dp_fini, | |
8d7ef84d | 577 | .acquire = nvkm_dp_acquire, |
d52e948c | 578 | .release = nvkm_dp_release, |
af85389c BS |
579 | }; |
580 | ||
f3e70d29 BS |
581 | static int |
582 | nvkm_dp_ctor(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, | |
583 | struct nvkm_i2c_aux *aux, struct nvkm_dp *dp) | |
af85389c BS |
584 | { |
585 | struct nvkm_device *device = disp->engine.subdev.device; | |
586 | struct nvkm_bios *bios = device->bios; | |
587 | struct nvkm_i2c *i2c = device->i2c; | |
588 | u8 hdr, cnt, len; | |
589 | u32 data; | |
590 | int ret; | |
591 | ||
01a97637 BS |
592 | ret = nvkm_outp_ctor(&nvkm_dp_func, disp, index, dcbE, &dp->outp); |
593 | if (ret) | |
594 | return ret; | |
595 | ||
f3e70d29 BS |
596 | dp->aux = aux; |
597 | if (!dp->aux) { | |
598 | OUTP_ERR(&dp->outp, "no aux"); | |
3c66c87d | 599 | return -EINVAL; |
af85389c BS |
600 | } |
601 | ||
602 | /* bios data is not optional */ | |
f3e70d29 BS |
603 | data = nvbios_dpout_match(bios, dp->outp.info.hasht, |
604 | dp->outp.info.hashm, &dp->version, | |
605 | &hdr, &cnt, &len, &dp->info); | |
af85389c | 606 | if (!data) { |
f3e70d29 | 607 | OUTP_ERR(&dp->outp, "no bios dp data"); |
3c66c87d | 608 | return -EINVAL; |
af85389c BS |
609 | } |
610 | ||
f3e70d29 BS |
611 | OUTP_DBG(&dp->outp, "bios dp %02x %02x %02x %02x", |
612 | dp->version, hdr, cnt, len); | |
af85389c | 613 | |
af85389c | 614 | /* hotplug detect, replaces gpio-based mechanism with aux events */ |
f3e70d29 | 615 | ret = nvkm_notify_init(NULL, &i2c->event, nvkm_dp_hpd, true, |
af85389c | 616 | &(struct nvkm_i2c_ntfy_req) { |
22e008f9 BS |
617 | .mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG | |
618 | NVKM_I2C_IRQ, | |
f3e70d29 | 619 | .port = dp->aux->id, |
af85389c BS |
620 | }, |
621 | sizeof(struct nvkm_i2c_ntfy_req), | |
622 | sizeof(struct nvkm_i2c_ntfy_rep), | |
f3e70d29 | 623 | &dp->hpd); |
af85389c | 624 | if (ret) { |
f3e70d29 | 625 | OUTP_ERR(&dp->outp, "error monitoring aux hpd: %d", ret); |
af85389c BS |
626 | return ret; |
627 | } | |
628 | ||
22e008f9 BS |
629 | mutex_init(&dp->mutex); |
630 | atomic_set(&dp->lt.done, 0); | |
af85389c BS |
631 | return 0; |
632 | } | |
633 | ||
634 | int | |
3c66c87d BS |
635 | nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, |
636 | struct nvkm_outp **poutp) | |
af85389c BS |
637 | { |
638 | struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c; | |
f3e70d29 BS |
639 | struct nvkm_i2c_aux *aux; |
640 | struct nvkm_dp *dp; | |
641 | ||
642 | if (dcbE->location == 0) | |
643 | aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_CCB(dcbE->i2c_index)); | |
644 | else | |
645 | aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbE->extdev)); | |
af85389c | 646 | |
f3e70d29 | 647 | if (!(dp = kzalloc(sizeof(*dp), GFP_KERNEL))) |
af85389c | 648 | return -ENOMEM; |
f3e70d29 | 649 | *poutp = &dp->outp; |
af85389c | 650 | |
f3e70d29 | 651 | return nvkm_dp_ctor(disp, index, dcbE, aux, dp); |
af85389c | 652 | } |