diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/dpaux.c')
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c new file mode 100644 index 000000000000..005c19bd92df --- /dev/null +++ b/drivers/gpu/drm/tegra/dpaux.c | |||
@@ -0,0 +1,562 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 NVIDIA Corporation | ||
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 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #include <linux/clk.h> | ||
10 | #include <linux/delay.h> | ||
11 | #include <linux/gpio.h> | ||
12 | #include <linux/interrupt.h> | ||
13 | #include <linux/io.h> | ||
14 | #include <linux/of_gpio.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/reset.h> | ||
17 | #include <linux/regulator/consumer.h> | ||
18 | |||
19 | #include <drm/drm_dp_helper.h> | ||
20 | #include <drm/drm_panel.h> | ||
21 | |||
22 | #include "dpaux.h" | ||
23 | #include "drm.h" | ||
24 | |||
25 | static DEFINE_MUTEX(dpaux_lock); | ||
26 | static LIST_HEAD(dpaux_list); | ||
27 | |||
28 | struct tegra_dpaux { | ||
29 | struct drm_dp_aux aux; | ||
30 | struct device *dev; | ||
31 | |||
32 | void __iomem *regs; | ||
33 | int irq; | ||
34 | |||
35 | struct tegra_output *output; | ||
36 | |||
37 | struct reset_control *rst; | ||
38 | struct clk *clk_parent; | ||
39 | struct clk *clk; | ||
40 | |||
41 | struct regulator *vdd; | ||
42 | |||
43 | struct completion complete; | ||
44 | struct list_head list; | ||
45 | }; | ||
46 | |||
47 | static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux) | ||
48 | { | ||
49 | return container_of(aux, struct tegra_dpaux, aux); | ||
50 | } | ||
51 | |||
52 | static inline unsigned long tegra_dpaux_readl(struct tegra_dpaux *dpaux, | ||
53 | unsigned long offset) | ||
54 | { | ||
55 | return readl(dpaux->regs + (offset << 2)); | ||
56 | } | ||
57 | |||
58 | static inline void tegra_dpaux_writel(struct tegra_dpaux *dpaux, | ||
59 | unsigned long value, | ||
60 | unsigned long offset) | ||
61 | { | ||
62 | writel(value, dpaux->regs + (offset << 2)); | ||
63 | } | ||
64 | |||
65 | static void tegra_dpaux_write_fifo(struct tegra_dpaux *dpaux, const u8 *buffer, | ||
66 | size_t size) | ||
67 | { | ||
68 | unsigned long offset = DPAUX_DP_AUXDATA_WRITE(0); | ||
69 | size_t i, j; | ||
70 | |||
71 | for (i = 0; i < size; i += 4) { | ||
72 | size_t num = min_t(size_t, size - i, 4); | ||
73 | unsigned long value = 0; | ||
74 | |||
75 | for (j = 0; j < num; j++) | ||
76 | value |= buffer[i + j] << (j * 8); | ||
77 | |||
78 | tegra_dpaux_writel(dpaux, value, offset++); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | static void tegra_dpaux_read_fifo(struct tegra_dpaux *dpaux, u8 *buffer, | ||
83 | size_t size) | ||
84 | { | ||
85 | unsigned long offset = DPAUX_DP_AUXDATA_READ(0); | ||
86 | size_t i, j; | ||
87 | |||
88 | for (i = 0; i < size; i += 4) { | ||
89 | size_t num = min_t(size_t, size - i, 4); | ||
90 | unsigned long value; | ||
91 | |||
92 | value = tegra_dpaux_readl(dpaux, offset++); | ||
93 | |||
94 | for (j = 0; j < num; j++) | ||
95 | buffer[i + j] = value >> (j * 8); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, | ||
100 | struct drm_dp_aux_msg *msg) | ||
101 | { | ||
102 | unsigned long timeout = msecs_to_jiffies(250); | ||
103 | struct tegra_dpaux *dpaux = to_dpaux(aux); | ||
104 | unsigned long status; | ||
105 | ssize_t ret = 0; | ||
106 | u32 value; | ||
107 | |||
108 | /* Tegra has 4x4 byte DP AUX transmit and receive FIFOs. */ | ||
109 | if (msg->size > 16) | ||
110 | return -EINVAL; | ||
111 | |||
112 | /* | ||
113 | * Allow zero-sized messages only for I2C, in which case they specify | ||
114 | * address-only transactions. | ||
115 | */ | ||
116 | if (msg->size < 1) { | ||
117 | switch (msg->request & ~DP_AUX_I2C_MOT) { | ||
118 | case DP_AUX_I2C_WRITE: | ||
119 | case DP_AUX_I2C_READ: | ||
120 | value = DPAUX_DP_AUXCTL_CMD_ADDRESS_ONLY; | ||
121 | break; | ||
122 | |||
123 | default: | ||
124 | return -EINVAL; | ||
125 | } | ||
126 | } else { | ||
127 | /* For non-zero-sized messages, set the CMDLEN field. */ | ||
128 | value = DPAUX_DP_AUXCTL_CMDLEN(msg->size - 1); | ||
129 | } | ||
130 | |||
131 | switch (msg->request & ~DP_AUX_I2C_MOT) { | ||
132 | case DP_AUX_I2C_WRITE: | ||
133 | if (msg->request & DP_AUX_I2C_MOT) | ||
134 | value |= DPAUX_DP_AUXCTL_CMD_MOT_WR; | ||
135 | else | ||
136 | value |= DPAUX_DP_AUXCTL_CMD_I2C_WR; | ||
137 | |||
138 | break; | ||
139 | |||
140 | case DP_AUX_I2C_READ: | ||
141 | if (msg->request & DP_AUX_I2C_MOT) | ||
142 | value |= DPAUX_DP_AUXCTL_CMD_MOT_RD; | ||
143 | else | ||
144 | value |= DPAUX_DP_AUXCTL_CMD_I2C_RD; | ||
145 | |||
146 | break; | ||
147 | |||
148 | case DP_AUX_I2C_STATUS: | ||
149 | if (msg->request & DP_AUX_I2C_MOT) | ||
150 | value |= DPAUX_DP_AUXCTL_CMD_MOT_RQ; | ||
151 | else | ||
152 | value |= DPAUX_DP_AUXCTL_CMD_I2C_RQ; | ||
153 | |||
154 | break; | ||
155 | |||
156 | case DP_AUX_NATIVE_WRITE: | ||
157 | value |= DPAUX_DP_AUXCTL_CMD_AUX_WR; | ||
158 | break; | ||
159 | |||
160 | case DP_AUX_NATIVE_READ: | ||
161 | value |= DPAUX_DP_AUXCTL_CMD_AUX_RD; | ||
162 | break; | ||
163 | |||
164 | default: | ||
165 | return -EINVAL; | ||
166 | } | ||
167 | |||
168 | tegra_dpaux_writel(dpaux, msg->address, DPAUX_DP_AUXADDR); | ||
169 | tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL); | ||
170 | |||
171 | if ((msg->request & DP_AUX_I2C_READ) == 0) { | ||
172 | tegra_dpaux_write_fifo(dpaux, msg->buffer, msg->size); | ||
173 | ret = msg->size; | ||
174 | } | ||
175 | |||
176 | /* start transaction */ | ||
177 | value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXCTL); | ||
178 | value |= DPAUX_DP_AUXCTL_TRANSACTREQ; | ||
179 | tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL); | ||
180 | |||
181 | status = wait_for_completion_timeout(&dpaux->complete, timeout); | ||
182 | if (!status) | ||
183 | return -ETIMEDOUT; | ||
184 | |||
185 | /* read status and clear errors */ | ||
186 | value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT); | ||
187 | tegra_dpaux_writel(dpaux, 0xf00, DPAUX_DP_AUXSTAT); | ||
188 | |||
189 | if (value & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR) | ||
190 | return -ETIMEDOUT; | ||
191 | |||
192 | if ((value & DPAUX_DP_AUXSTAT_RX_ERROR) || | ||
193 | (value & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR) || | ||
194 | (value & DPAUX_DP_AUXSTAT_NO_STOP_ERROR)) | ||
195 | return -EIO; | ||
196 | |||
197 | switch ((value & DPAUX_DP_AUXSTAT_REPLY_TYPE_MASK) >> 16) { | ||
198 | case 0x00: | ||
199 | msg->reply = DP_AUX_NATIVE_REPLY_ACK; | ||
200 | break; | ||
201 | |||
202 | case 0x01: | ||
203 | msg->reply = DP_AUX_NATIVE_REPLY_NACK; | ||
204 | break; | ||
205 | |||
206 | case 0x02: | ||
207 | msg->reply = DP_AUX_NATIVE_REPLY_DEFER; | ||
208 | break; | ||
209 | |||
210 | case 0x04: | ||
211 | msg->reply = DP_AUX_I2C_REPLY_NACK; | ||
212 | break; | ||
213 | |||
214 | case 0x08: | ||
215 | msg->reply = DP_AUX_I2C_REPLY_DEFER; | ||
216 | break; | ||
217 | } | ||
218 | |||
219 | if ((msg->size > 0) && (msg->reply == DP_AUX_NATIVE_REPLY_ACK)) { | ||
220 | if (msg->request & DP_AUX_I2C_READ) { | ||
221 | size_t count = value & DPAUX_DP_AUXSTAT_REPLY_MASK; | ||
222 | |||
223 | if (WARN_ON(count != msg->size)) | ||
224 | count = min_t(size_t, count, msg->size); | ||
225 | |||
226 | tegra_dpaux_read_fifo(dpaux, msg->buffer, count); | ||
227 | ret = count; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | return ret; | ||
232 | } | ||
233 | |||
234 | static irqreturn_t tegra_dpaux_irq(int irq, void *data) | ||
235 | { | ||
236 | struct tegra_dpaux *dpaux = data; | ||
237 | irqreturn_t ret = IRQ_HANDLED; | ||
238 | unsigned long value; | ||
239 | |||
240 | /* clear interrupts */ | ||
241 | value = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX); | ||
242 | tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); | ||
243 | |||
244 | if (value & DPAUX_INTR_PLUG_EVENT) { | ||
245 | if (dpaux->output) { | ||
246 | drm_helper_hpd_irq_event(dpaux->output->connector.dev); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | if (value & DPAUX_INTR_UNPLUG_EVENT) { | ||
251 | if (dpaux->output) | ||
252 | drm_helper_hpd_irq_event(dpaux->output->connector.dev); | ||
253 | } | ||
254 | |||
255 | if (value & DPAUX_INTR_IRQ_EVENT) { | ||
256 | /* TODO: handle this */ | ||
257 | } | ||
258 | |||
259 | if (value & DPAUX_INTR_AUX_DONE) | ||
260 | complete(&dpaux->complete); | ||
261 | |||
262 | return ret; | ||
263 | } | ||
264 | |||
265 | static int tegra_dpaux_probe(struct platform_device *pdev) | ||
266 | { | ||
267 | struct tegra_dpaux *dpaux; | ||
268 | struct resource *regs; | ||
269 | unsigned long value; | ||
270 | int err; | ||
271 | |||
272 | dpaux = devm_kzalloc(&pdev->dev, sizeof(*dpaux), GFP_KERNEL); | ||
273 | if (!dpaux) | ||
274 | return -ENOMEM; | ||
275 | |||
276 | init_completion(&dpaux->complete); | ||
277 | INIT_LIST_HEAD(&dpaux->list); | ||
278 | dpaux->dev = &pdev->dev; | ||
279 | |||
280 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
281 | dpaux->regs = devm_ioremap_resource(&pdev->dev, regs); | ||
282 | if (IS_ERR(dpaux->regs)) | ||
283 | return PTR_ERR(dpaux->regs); | ||
284 | |||
285 | dpaux->irq = platform_get_irq(pdev, 0); | ||
286 | if (dpaux->irq < 0) { | ||
287 | dev_err(&pdev->dev, "failed to get IRQ\n"); | ||
288 | return -ENXIO; | ||
289 | } | ||
290 | |||
291 | dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux"); | ||
292 | if (IS_ERR(dpaux->rst)) | ||
293 | return PTR_ERR(dpaux->rst); | ||
294 | |||
295 | dpaux->clk = devm_clk_get(&pdev->dev, NULL); | ||
296 | if (IS_ERR(dpaux->clk)) | ||
297 | return PTR_ERR(dpaux->clk); | ||
298 | |||
299 | err = clk_prepare_enable(dpaux->clk); | ||
300 | if (err < 0) | ||
301 | return err; | ||
302 | |||
303 | reset_control_deassert(dpaux->rst); | ||
304 | |||
305 | dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent"); | ||
306 | if (IS_ERR(dpaux->clk_parent)) | ||
307 | return PTR_ERR(dpaux->clk_parent); | ||
308 | |||
309 | err = clk_prepare_enable(dpaux->clk_parent); | ||
310 | if (err < 0) | ||
311 | return err; | ||
312 | |||
313 | err = clk_set_rate(dpaux->clk_parent, 270000000); | ||
314 | if (err < 0) { | ||
315 | dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n", | ||
316 | err); | ||
317 | return err; | ||
318 | } | ||
319 | |||
320 | dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); | ||
321 | if (IS_ERR(dpaux->vdd)) | ||
322 | return PTR_ERR(dpaux->vdd); | ||
323 | |||
324 | err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0, | ||
325 | dev_name(dpaux->dev), dpaux); | ||
326 | if (err < 0) { | ||
327 | dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n", | ||
328 | dpaux->irq, err); | ||
329 | return err; | ||
330 | } | ||
331 | |||
332 | dpaux->aux.transfer = tegra_dpaux_transfer; | ||
333 | dpaux->aux.dev = &pdev->dev; | ||
334 | |||
335 | err = drm_dp_aux_register_i2c_bus(&dpaux->aux); | ||
336 | if (err < 0) | ||
337 | return err; | ||
338 | |||
339 | /* enable and clear all interrupts */ | ||
340 | value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT | | ||
341 | DPAUX_INTR_UNPLUG_EVENT | DPAUX_INTR_PLUG_EVENT; | ||
342 | tegra_dpaux_writel(dpaux, value, DPAUX_INTR_EN_AUX); | ||
343 | tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); | ||
344 | |||
345 | mutex_lock(&dpaux_lock); | ||
346 | list_add_tail(&dpaux->list, &dpaux_list); | ||
347 | mutex_unlock(&dpaux_lock); | ||
348 | |||
349 | platform_set_drvdata(pdev, dpaux); | ||
350 | |||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | static int tegra_dpaux_remove(struct platform_device *pdev) | ||
355 | { | ||
356 | struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); | ||
357 | |||
358 | drm_dp_aux_unregister_i2c_bus(&dpaux->aux); | ||
359 | |||
360 | mutex_lock(&dpaux_lock); | ||
361 | list_del(&dpaux->list); | ||
362 | mutex_unlock(&dpaux_lock); | ||
363 | |||
364 | clk_disable_unprepare(dpaux->clk_parent); | ||
365 | reset_control_assert(dpaux->rst); | ||
366 | clk_disable_unprepare(dpaux->clk); | ||
367 | |||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static const struct of_device_id tegra_dpaux_of_match[] = { | ||
372 | { .compatible = "nvidia,tegra124-dpaux", }, | ||
373 | { }, | ||
374 | }; | ||
375 | |||
376 | struct platform_driver tegra_dpaux_driver = { | ||
377 | .driver = { | ||
378 | .name = "tegra-dpaux", | ||
379 | .of_match_table = tegra_dpaux_of_match, | ||
380 | }, | ||
381 | .probe = tegra_dpaux_probe, | ||
382 | .remove = tegra_dpaux_remove, | ||
383 | }; | ||
384 | |||
385 | struct tegra_dpaux *tegra_dpaux_find_by_of_node(struct device_node *np) | ||
386 | { | ||
387 | struct tegra_dpaux *dpaux; | ||
388 | |||
389 | mutex_lock(&dpaux_lock); | ||
390 | |||
391 | list_for_each_entry(dpaux, &dpaux_list, list) | ||
392 | if (np == dpaux->dev->of_node) { | ||
393 | mutex_unlock(&dpaux_lock); | ||
394 | return dpaux; | ||
395 | } | ||
396 | |||
397 | mutex_unlock(&dpaux_lock); | ||
398 | |||
399 | return NULL; | ||
400 | } | ||
401 | |||
402 | int tegra_dpaux_attach(struct tegra_dpaux *dpaux, struct tegra_output *output) | ||
403 | { | ||
404 | unsigned long timeout; | ||
405 | int err; | ||
406 | |||
407 | dpaux->output = output; | ||
408 | |||
409 | err = regulator_enable(dpaux->vdd); | ||
410 | if (err < 0) | ||
411 | return err; | ||
412 | |||
413 | timeout = jiffies + msecs_to_jiffies(250); | ||
414 | |||
415 | while (time_before(jiffies, timeout)) { | ||
416 | enum drm_connector_status status; | ||
417 | |||
418 | status = tegra_dpaux_detect(dpaux); | ||
419 | if (status == connector_status_connected) | ||
420 | return 0; | ||
421 | |||
422 | usleep_range(1000, 2000); | ||
423 | } | ||
424 | |||
425 | return -ETIMEDOUT; | ||
426 | } | ||
427 | |||
428 | int tegra_dpaux_detach(struct tegra_dpaux *dpaux) | ||
429 | { | ||
430 | unsigned long timeout; | ||
431 | int err; | ||
432 | |||
433 | err = regulator_disable(dpaux->vdd); | ||
434 | if (err < 0) | ||
435 | return err; | ||
436 | |||
437 | timeout = jiffies + msecs_to_jiffies(250); | ||
438 | |||
439 | while (time_before(jiffies, timeout)) { | ||
440 | enum drm_connector_status status; | ||
441 | |||
442 | status = tegra_dpaux_detect(dpaux); | ||
443 | if (status == connector_status_disconnected) { | ||
444 | dpaux->output = NULL; | ||
445 | return 0; | ||
446 | } | ||
447 | |||
448 | usleep_range(1000, 2000); | ||
449 | } | ||
450 | |||
451 | return -ETIMEDOUT; | ||
452 | } | ||
453 | |||
454 | enum drm_connector_status tegra_dpaux_detect(struct tegra_dpaux *dpaux) | ||
455 | { | ||
456 | unsigned long value; | ||
457 | |||
458 | value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT); | ||
459 | |||
460 | if (value & DPAUX_DP_AUXSTAT_HPD_STATUS) | ||
461 | return connector_status_connected; | ||
462 | |||
463 | return connector_status_disconnected; | ||
464 | } | ||
465 | |||
466 | int tegra_dpaux_enable(struct tegra_dpaux *dpaux) | ||
467 | { | ||
468 | unsigned long value; | ||
469 | |||
470 | value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | | ||
471 | DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | | ||
472 | DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | | ||
473 | DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV | | ||
474 | DPAUX_HYBRID_PADCTL_MODE_AUX; | ||
475 | tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL); | ||
476 | |||
477 | value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); | ||
478 | value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; | ||
479 | tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); | ||
480 | |||
481 | return 0; | ||
482 | } | ||
483 | |||
484 | int tegra_dpaux_disable(struct tegra_dpaux *dpaux) | ||
485 | { | ||
486 | unsigned long value; | ||
487 | |||
488 | value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); | ||
489 | value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; | ||
490 | tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); | ||
491 | |||
492 | return 0; | ||
493 | } | ||
494 | |||
495 | int tegra_dpaux_prepare(struct tegra_dpaux *dpaux, u8 encoding) | ||
496 | { | ||
497 | int err; | ||
498 | |||
499 | err = drm_dp_dpcd_writeb(&dpaux->aux, DP_MAIN_LINK_CHANNEL_CODING_SET, | ||
500 | encoding); | ||
501 | if (err < 0) | ||
502 | return err; | ||
503 | |||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | int tegra_dpaux_train(struct tegra_dpaux *dpaux, struct drm_dp_link *link, | ||
508 | u8 pattern) | ||
509 | { | ||
510 | u8 tp = pattern & DP_TRAINING_PATTERN_MASK; | ||
511 | u8 status[DP_LINK_STATUS_SIZE], values[4]; | ||
512 | unsigned int i; | ||
513 | int err; | ||
514 | |||
515 | err = drm_dp_dpcd_writeb(&dpaux->aux, DP_TRAINING_PATTERN_SET, pattern); | ||
516 | if (err < 0) | ||
517 | return err; | ||
518 | |||
519 | if (tp == DP_TRAINING_PATTERN_DISABLE) | ||
520 | return 0; | ||
521 | |||
522 | for (i = 0; i < link->num_lanes; i++) | ||
523 | values[i] = DP_TRAIN_MAX_PRE_EMPHASIS_REACHED | | ||
524 | DP_TRAIN_PRE_EMPHASIS_0 | | ||
525 | DP_TRAIN_MAX_SWING_REACHED | | ||
526 | DP_TRAIN_VOLTAGE_SWING_400; | ||
527 | |||
528 | err = drm_dp_dpcd_write(&dpaux->aux, DP_TRAINING_LANE0_SET, values, | ||
529 | link->num_lanes); | ||
530 | if (err < 0) | ||
531 | return err; | ||
532 | |||
533 | usleep_range(500, 1000); | ||
534 | |||
535 | err = drm_dp_dpcd_read_link_status(&dpaux->aux, status); | ||
536 | if (err < 0) | ||
537 | return err; | ||
538 | |||
539 | switch (tp) { | ||
540 | case DP_TRAINING_PATTERN_1: | ||
541 | if (!drm_dp_clock_recovery_ok(status, link->num_lanes)) | ||
542 | return -EAGAIN; | ||
543 | |||
544 | break; | ||
545 | |||
546 | case DP_TRAINING_PATTERN_2: | ||
547 | if (!drm_dp_channel_eq_ok(status, link->num_lanes)) | ||
548 | return -EAGAIN; | ||
549 | |||
550 | break; | ||
551 | |||
552 | default: | ||
553 | dev_err(dpaux->dev, "unsupported training pattern %u\n", tp); | ||
554 | return -EINVAL; | ||
555 | } | ||
556 | |||
557 | err = drm_dp_dpcd_writeb(&dpaux->aux, DP_EDP_CONFIGURATION_SET, 0); | ||
558 | if (err < 0) | ||
559 | return err; | ||
560 | |||
561 | return 0; | ||
562 | } | ||