dp.c 15.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/*
 * Copyright 2014 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: Ben Skeggs
 */
#include "dp.h"
#include "conn.h"
#include "nv50.h"

#include <subdev/bios.h>
#include <subdev/bios/init.h>
#include <subdev/i2c.h>

#include <nvif/event.h>

struct lt_state {
35
	struct nvkm_dp *dp;
36 37 38 39 40 41 42 43 44 45 46 47
	int link_nr;
	u32 link_bw;
	u8  stat[6];
	u8  conf[4];
	bool pc2;
	u8  pc2stat;
	u8  pc2conf[2];
};

static int
nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay)
{
48
	struct nvkm_dp *dp = lt->dp;
49 50
	int ret;

51 52
	if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
		mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
53 54 55
	else
		udelay(delay);

56
	ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6);
57 58 59 60
	if (ret)
		return ret;

	if (pc) {
61
		ret = nvkm_rdaux(dp->aux, DPCD_LS0C, &lt->pc2stat, 1);
62 63
		if (ret)
			lt->pc2stat = 0x00;
64 65
		OUTP_TRACE(&dp->outp, "status %6ph pc2 %02x",
			   lt->stat, lt->pc2stat);
66
	} else {
67
		OUTP_TRACE(&dp->outp, "status %6ph", lt->stat);
68 69 70 71 72 73 74 75
	}

	return 0;
}

static int
nvkm_dp_train_drive(struct lt_state *lt, bool pc)
{
76
	struct nvkm_dp *dp = lt->dp;
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	int ret, i;

	for (i = 0; i < lt->link_nr; i++) {
		u8 lane = (lt->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
		u8 lpc2 = (lt->pc2stat >> (i * 2)) & 0x3;
		u8 lpre = (lane & 0x0c) >> 2;
		u8 lvsw = (lane & 0x03) >> 0;
		u8 hivs = 3 - lpre;
		u8 hipe = 3;
		u8 hipc = 3;

		if (lpc2 >= hipc)
			lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED;
		if (lpre >= hipe) {
			lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */
			lvsw = hivs = 3 - (lpre & 3);
		} else
		if (lvsw >= hivs) {
			lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED;
		}

		lt->conf[i] = (lpre << 3) | lvsw;
		lt->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);

101 102 103
		OUTP_TRACE(&dp->outp, "config lane %d %02x %02x",
			   i, lt->conf[i], lpc2);
		dp->func->drv_ctl(dp, i, lvsw & 3, lpre & 3, lpc2 & 3);
104 105
	}

106
	ret = nvkm_wraux(dp->aux, DPCD_LC03(0), lt->conf, 4);
107 108 109 110
	if (ret)
		return ret;

	if (pc) {
111
		ret = nvkm_wraux(dp->aux, DPCD_LC0F, lt->pc2conf, 2);
112 113 114 115 116 117 118 119 120 121
		if (ret)
			return ret;
	}

	return 0;
}

static void
nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern)
{
122
	struct nvkm_dp *dp = lt->dp;
123 124
	u8 sink_tp;

125 126
	OUTP_TRACE(&dp->outp, "training pattern %d", pattern);
	dp->func->pattern(dp, pattern);
127

128
	nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1);
129 130
	sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
	sink_tp |= pattern;
131
	nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1);
132 133 134 135 136 137 138 139
}

static int
nvkm_dp_train_eq(struct lt_state *lt)
{
	bool eq_done = false, cr_done = true;
	int tries = 0, i;

140
	if (lt->dp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
		nvkm_dp_train_pattern(lt, 3);
	else
		nvkm_dp_train_pattern(lt, 2);

	do {
		if ((tries &&
		    nvkm_dp_train_drive(lt, lt->pc2)) ||
		    nvkm_dp_train_sense(lt, lt->pc2, 400))
			break;

		eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
		for (i = 0; i < lt->link_nr && eq_done; i++) {
			u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
			if (!(lane & DPCD_LS02_LANE0_CR_DONE))
				cr_done = false;
			if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
			    !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
				eq_done = false;
		}
	} while (!eq_done && cr_done && ++tries <= 5);

	return eq_done ? 0 : -1;
}

static int
nvkm_dp_train_cr(struct lt_state *lt)
{
	bool cr_done = false, abort = false;
	int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
	int tries = 0, i;

	nvkm_dp_train_pattern(lt, 1);

	do {
		if (nvkm_dp_train_drive(lt, false) ||
		    nvkm_dp_train_sense(lt, false, 100))
			break;

		cr_done = true;
		for (i = 0; i < lt->link_nr; i++) {
			u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
			if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
				cr_done = false;
				if (lt->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
					abort = true;
				break;
			}
		}

		if ((lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
			voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
			tries = 0;
		}
	} while (!cr_done && !abort && ++tries < 5);

	return cr_done ? 0 : -1;
}

static int
nvkm_dp_train_links(struct lt_state *lt)
{
202 203
	struct nvkm_dp *dp = lt->dp;
	struct nvkm_disp *disp = dp->outp.disp;
204 205 206 207 208 209
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvkm_bios *bios = subdev->device->bios;
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = bios,
		.offset = 0x0000,
210
		.outp = &dp->outp.info,
211 212 213 214 215 216 217
		.crtc = -1,
		.execute = 1,
	};
	u32 lnkcmp;
	u8 sink[2];
	int ret;

218
	OUTP_DBG(&dp->outp, "%d lanes at %d KB/s", lt->link_nr, lt->link_bw);
219 220 221

	/* Intersect misc. capabilities of the OR and sink. */
	if (disp->engine.subdev.device->chipset < 0xd0)
222 223
		dp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
	lt->pc2 = dp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;
224 225

	/* Set desired link configuration on the source. */
226 227
	if ((lnkcmp = lt->dp->info.lnkcmp)) {
		if (dp->version < 0x30) {
228 229 230 231 232 233 234 235 236 237 238 239
			while ((lt->link_bw / 10) < nvbios_rd16(bios, lnkcmp))
				lnkcmp += 4;
			init.offset = nvbios_rd16(bios, lnkcmp + 2);
		} else {
			while ((lt->link_bw / 27000) < nvbios_rd08(bios, lnkcmp))
				lnkcmp += 3;
			init.offset = nvbios_rd16(bios, lnkcmp + 1);
		}

		nvbios_exec(&init);
	}

240 241 242
	ret = dp->func->lnk_ctl(dp, lt->link_nr, lt->link_bw / 27000,
				dp->dpcd[DPCD_RC02] &
					 DPCD_RC02_ENHANCED_FRAME_CAP);
243 244
	if (ret) {
		if (ret < 0)
245
			OUTP_ERR(&dp->outp, "lnk_ctl failed with %d", ret);
246 247 248
		return ret;
	}

249
	dp->func->lnk_pwr(dp, lt->link_nr);
250 251 252 253

	/* Set desired link configuration on the sink. */
	sink[0] = lt->link_bw / 27000;
	sink[1] = lt->link_nr;
254
	if (dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
255 256
		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;

257
	return nvkm_wraux(dp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
258 259 260 261 262
}

static void
nvkm_dp_train_fini(struct lt_state *lt)
{
263 264
	struct nvkm_dp *dp = lt->dp;
	struct nvkm_subdev *subdev = &dp->outp.disp->engine.subdev;
265 266 267
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = subdev->device->bios,
268
		.outp = &dp->outp.info,
269 270 271 272 273
		.crtc = -1,
		.execute = 1,
	};

	/* Execute AfterLinkTraining script from DP Info table. */
274
	init.offset = dp->info.script[1],
275 276 277 278 279 280
	nvbios_exec(&init);
}

static void
nvkm_dp_train_init(struct lt_state *lt, bool spread)
{
281 282
	struct nvkm_dp *dp = lt->dp;
	struct nvkm_subdev *subdev = &dp->outp.disp->engine.subdev;
283 284 285
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = subdev->device->bios,
286
		.outp = &dp->outp.info,
287 288 289 290 291 292
		.crtc = -1,
		.execute = 1,
	};

	/* Execute EnableSpread/DisableSpread script from DP Info table. */
	if (spread)
293
		init.offset = dp->info.script[2];
294
	else
295
		init.offset = dp->info.script[3];
296 297
	nvbios_exec(&init);

298 299
	/* Execute BeforeLinkTraining script from DP Info table. */
	init.offset = dp->info.script[0];
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	nvbios_exec(&init);
}

static const struct dp_rates {
	u32 rate;
	u8  bw;
	u8  nr;
} nvkm_dp_rates[] = {
	{ 2160000, 0x14, 4 },
	{ 1080000, 0x0a, 4 },
	{ 1080000, 0x14, 2 },
	{  648000, 0x06, 4 },
	{  540000, 0x0a, 2 },
	{  540000, 0x14, 1 },
	{  324000, 0x06, 2 },
	{  270000, 0x0a, 1 },
	{  162000, 0x06, 1 },
	{}
};

static void
321
nvkm_dp_train(struct nvkm_dp *dp)
322
{
323
	struct nv50_disp *disp = nv50_disp(dp->outp.disp);
324 325
	const struct dp_rates *cfg = nvkm_dp_rates - 1;
	struct lt_state lt = {
326
		.dp = dp,
327 328 329 330
	};
	u8  pwr;
	int ret;

331 332
	if (!dp->outp.info.location && disp->func->sor.magic)
		disp->func->sor.magic(&dp->outp);
333

334 335 336
	if ((dp->dpcd[2] & 0x1f) > dp->outp.info.dpconf.link_nr) {
		dp->dpcd[2] &= ~DPCD_RC02_MAX_LANE_COUNT;
		dp->dpcd[2] |= dp->outp.info.dpconf.link_nr;
337
	}
338 339
	if (dp->dpcd[1] > dp->outp.info.dpconf.link_bw)
		dp->dpcd[1] = dp->outp.info.dpconf.link_bw;
340 341

	/* Ensure sink is not in a low-power state. */
342
	if (!nvkm_rdaux(dp->aux, DPCD_SC00, &pwr, 1)) {
343 344 345
		if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
			pwr &= ~DPCD_SC00_SET_POWER;
			pwr |=  DPCD_SC00_SET_POWER_D0;
346
			nvkm_wraux(dp->aux, DPCD_SC00, &pwr, 1);
347 348 349 350
		}
	}

	/* Link training. */
351
	nvkm_dp_train_init(&lt, dp->dpcd[3] & 0x01);
352 353
	while (ret = -EIO, (++cfg)->rate) {
		/* Skip configurations not supported by both OR and sink. */
354 355
		while (cfg->nr > (dp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
		       cfg->bw > (dp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
			cfg++;
		lt.link_bw = cfg->bw * 27000;
		lt.link_nr = cfg->nr;

		/* Program selected link configuration. */
		ret = nvkm_dp_train_links(&lt);
		if (ret == 0) {
			/* Attempt to train the link in this configuration. */
			memset(lt.stat, 0x00, sizeof(lt.stat));
			if (!nvkm_dp_train_cr(&lt) &&
			    !nvkm_dp_train_eq(&lt))
				break;
		} else
		if (ret) {
			/* nvkm_dp_train_links() handled training, or
			 * we failed to communicate with the sink.
			 */
			break;
		}
	}
	nvkm_dp_train_pattern(&lt, 0);
	nvkm_dp_train_fini(&lt);
	if (ret < 0)
379
		OUTP_ERR(&dp->outp, "training failed");
380

381 382
	OUTP_DBG(&dp->outp, "training done");
	atomic_set(&dp->lt.done, 1);
383 384 385
}

int
386
nvkm_output_dp_train(struct nvkm_outp *outp, u32 datarate)
387
{
388
	struct nvkm_dp *dp = nvkm_dp(outp);
389 390 391 392 393
	bool retrain = true;
	u8 link[2], stat[3];
	u32 linkrate;
	int ret, i;

394
	mutex_lock(&dp->mutex);
395 396

	/* check that the link is trained at a high enough rate */
397
	ret = nvkm_rdaux(dp->aux, DPCD_LC00_LINK_BW_SET, link, 2);
398
	if (ret) {
399
		OUTP_DBG(&dp->outp,
400 401 402 403 404 405 406 407
			 "failed to read link config, assuming no sink");
		goto done;
	}

	linkrate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET);
	linkrate = (linkrate * 8) / 10; /* 8B/10B coding overhead */
	datarate = (datarate + 9) / 10; /* -> decakilobits */
	if (linkrate < datarate) {
408
		OUTP_DBG(&dp->outp, "link not trained at sufficient rate");
409 410 411 412
		goto done;
	}

	/* check that link is still trained */
413
	ret = nvkm_rdaux(dp->aux, DPCD_LS02, stat, 3);
414
	if (ret) {
415
		OUTP_DBG(&dp->outp,
416 417 418 419 420 421 422 423 424 425
			 "failed to read link status, assuming no sink");
		goto done;
	}

	if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) {
		for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); i++) {
			u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f;
			if (!(lane & DPCD_LS02_LANE0_CR_DONE) ||
			    !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
			    !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) {
426
				OUTP_DBG(&dp->outp,
427 428 429 430 431 432
					 "lane %d not equalised", lane);
				goto done;
			}
		}
		retrain = false;
	} else {
433
		OUTP_DBG(&dp->outp, "no inter-lane alignment");
434 435 436
	}

done:
437
	if (retrain || !atomic_read(&dp->lt.done)) {
438
		/* no sink, but still need to configure source */
439 440 441 442 443
		if (dp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) {
			dp->dpcd[DPCD_RC01_MAX_LINK_RATE] =
				dp->outp.info.dpconf.link_bw;
			dp->dpcd[DPCD_RC02] =
				dp->outp.info.dpconf.link_nr;
444
		}
445
		nvkm_dp_train(dp);
446 447
	}

448
	mutex_unlock(&dp->mutex);
449 450 451 452
	return ret;
}

static void
453
nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
454
{
455
	struct nvkm_i2c_aux *aux = dp->aux;
456 457

	if (enable) {
458 459
		if (!dp->present) {
			OUTP_DBG(&dp->outp, "aux power -> always");
460
			nvkm_i2c_aux_monitor(aux, true);
461
			dp->present = true;
462 463
		}

464 465 466
		if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd,
				sizeof(dp->dpcd))) {
			nvkm_output_dp_train(&dp->outp, 0);
467 468 469 470
			return;
		}
	}

471 472
	if (dp->present) {
		OUTP_DBG(&dp->outp, "aux power -> demand");
473
		nvkm_i2c_aux_monitor(aux, false);
474
		dp->present = false;
475 476
	}

477
	atomic_set(&dp->lt.done, 0);
478 479 480
}

static int
481
nvkm_dp_hpd(struct nvkm_notify *notify)
482 483
{
	const struct nvkm_i2c_ntfy_rep *line = notify->data;
484
	struct nvkm_dp *dp = container_of(notify, typeof(*dp), hpd);
485
	struct nvkm_conn *conn = dp->outp.conn;
486
	struct nvkm_disp *disp = dp->outp.disp;
487 488
	struct nvif_notify_conn_rep_v0 rep = {};

489 490
	OUTP_DBG(&dp->outp, "HPD: %d", line->mask);
	nvkm_dp_enable(dp, true);
491 492 493 494 495 496 497 498 499 500 501

	if (line->mask & NVKM_I2C_UNPLUG)
		rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG;
	if (line->mask & NVKM_I2C_PLUG)
		rep.mask |= NVIF_NOTIFY_CONN_V0_PLUG;

	nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep));
	return NVKM_NOTIFY_KEEP;
}

static int
502
nvkm_dp_irq(struct nvkm_notify *notify)
503 504
{
	const struct nvkm_i2c_ntfy_rep *line = notify->data;
505
	struct nvkm_dp *dp = container_of(notify, typeof(*dp), irq);
506
	struct nvkm_conn *conn = dp->outp.conn;
507
	struct nvkm_disp *disp = dp->outp.disp;
508 509 510 511
	struct nvif_notify_conn_rep_v0 rep = {
		.mask = NVIF_NOTIFY_CONN_V0_IRQ,
	};

512 513
	OUTP_DBG(&dp->outp, "IRQ: %d", line->mask);
	nvkm_output_dp_train(&dp->outp, 0);
514 515 516 517 518 519

	nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep));
	return NVKM_NOTIFY_KEEP;
}

static void
520
nvkm_dp_fini(struct nvkm_outp *outp)
521
{
522 523 524 525
	struct nvkm_dp *dp = nvkm_dp(outp);
	nvkm_notify_put(&dp->hpd);
	nvkm_notify_put(&dp->irq);
	nvkm_dp_enable(dp, false);
526 527 528
}

static void
529
nvkm_dp_init(struct nvkm_outp *outp)
530
{
531 532 533 534 535
	struct nvkm_dp *dp = nvkm_dp(outp);
	nvkm_notify_put(&dp->outp.conn->hpd);
	nvkm_dp_enable(dp, true);
	nvkm_notify_get(&dp->irq);
	nvkm_notify_get(&dp->hpd);
536 537 538
}

static void *
539
nvkm_dp_dtor(struct nvkm_outp *outp)
540
{
541 542 543 544
	struct nvkm_dp *dp = nvkm_dp(outp);
	nvkm_notify_fini(&dp->hpd);
	nvkm_notify_fini(&dp->irq);
	return dp;
545 546
}

547 548 549 550 551
static const struct nvkm_outp_func
nvkm_dp_func = {
	.dtor = nvkm_dp_dtor,
	.init = nvkm_dp_init,
	.fini = nvkm_dp_fini,
552 553
};

554 555 556
static int
nvkm_dp_ctor(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
	     struct nvkm_i2c_aux *aux, struct nvkm_dp *dp)
557 558 559 560 561 562 563 564
{
	struct nvkm_device *device = disp->engine.subdev.device;
	struct nvkm_bios *bios = device->bios;
	struct nvkm_i2c *i2c = device->i2c;
	u8  hdr, cnt, len;
	u32 data;
	int ret;

565 566 567 568
	ret = nvkm_outp_ctor(&nvkm_dp_func, disp, index, dcbE, &dp->outp);
	if (ret)
		return ret;

569 570 571
	dp->aux = aux;
	if (!dp->aux) {
		OUTP_ERR(&dp->outp, "no aux");
572 573 574 575
		return -ENODEV;
	}

	/* bios data is not optional */
576 577 578
	data = nvbios_dpout_match(bios, dp->outp.info.hasht,
				  dp->outp.info.hashm, &dp->version,
				  &hdr, &cnt, &len, &dp->info);
579
	if (!data) {
580
		OUTP_ERR(&dp->outp, "no bios dp data");
581 582 583
		return -ENODEV;
	}

584 585
	OUTP_DBG(&dp->outp, "bios dp %02x %02x %02x %02x",
		 dp->version, hdr, cnt, len);
586 587

	/* link maintenance */
588
	ret = nvkm_notify_init(NULL, &i2c->event, nvkm_dp_irq, true,
589 590
			       &(struct nvkm_i2c_ntfy_req) {
				.mask = NVKM_I2C_IRQ,
591
				.port = dp->aux->id,
592 593 594
			       },
			       sizeof(struct nvkm_i2c_ntfy_req),
			       sizeof(struct nvkm_i2c_ntfy_rep),
595
			       &dp->irq);
596
	if (ret) {
597
		OUTP_ERR(&dp->outp, "error monitoring aux irq: %d", ret);
598 599 600
		return ret;
	}

601 602
	mutex_init(&dp->mutex);
	atomic_set(&dp->lt.done, 0);
603 604

	/* hotplug detect, replaces gpio-based mechanism with aux events */
605
	ret = nvkm_notify_init(NULL, &i2c->event, nvkm_dp_hpd, true,
606 607
			       &(struct nvkm_i2c_ntfy_req) {
				.mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG,
608
				.port = dp->aux->id,
609 610 611
			       },
			       sizeof(struct nvkm_i2c_ntfy_req),
			       sizeof(struct nvkm_i2c_ntfy_rep),
612
			       &dp->hpd);
613
	if (ret) {
614
		OUTP_ERR(&dp->outp, "error monitoring aux hpd: %d", ret);
615 616 617 618 619 620 621 622 623
		return ret;
	}

	return 0;
}

int
nvkm_output_dp_new_(const struct nvkm_output_dp_func *func,
		    struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
624
		    struct nvkm_outp **poutp)
625 626
{
	struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
627 628 629 630 631 632 633
	struct nvkm_i2c_aux *aux;
	struct nvkm_dp *dp;

	if (dcbE->location == 0)
		aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_CCB(dcbE->i2c_index));
	else
		aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbE->extdev));
634

635
	if (!(dp = kzalloc(sizeof(*dp), GFP_KERNEL)))
636
		return -ENOMEM;
637 638
	dp->func = func;
	*poutp = &dp->outp;
639

640
	return nvkm_dp_ctor(disp, index, dcbE, aux, dp);
641
}