浏览代码

drm/nouveau/disp: introduce acquire/release display path methods

These exist to give NVKM information on the set of display paths that
the DD needs to be active at any given time.

Previously, the supervisor attempted to determine this solely from OR
state, but there's a few configurations where this information on its
own isn't enough to determine the specific display paths in question:

- ANX9805, where the PIOR protocol for both DP and TMDS is TMDS.
- On a device using DCB Switched Outputs.
- On GM20x and newer, with a crossbar between the SOR and macro links.

After this commit, the DD tells NVKM *exactly* which display path it's
attempting a modeset on.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Ben Skeggs 8 年之前
父节点
当前提交
6c22ea3747

+ 9 - 0
drivers/gpu/drm/nouveau/include/nvif/cl5070.h

@@ -27,6 +27,8 @@ struct nv50_disp_scanoutpos_v0 {
 
 
 struct nv50_disp_mthd_v1 {
 struct nv50_disp_mthd_v1 {
 	__u8  version;
 	__u8  version;
+#define NV50_DISP_MTHD_V1_ACQUIRE                                          0x01
+#define NV50_DISP_MTHD_V1_RELEASE                                          0x02
 #define NV50_DISP_MTHD_V1_DAC_LOAD                                         0x11
 #define NV50_DISP_MTHD_V1_DAC_LOAD                                         0x11
 #define NV50_DISP_MTHD_V1_SOR_HDA_ELD                                      0x21
 #define NV50_DISP_MTHD_V1_SOR_HDA_ELD                                      0x21
 #define NV50_DISP_MTHD_V1_SOR_HDMI_PWR                                     0x22
 #define NV50_DISP_MTHD_V1_SOR_HDMI_PWR                                     0x22
@@ -39,6 +41,13 @@ struct nv50_disp_mthd_v1 {
 	__u8  pad06[2];
 	__u8  pad06[2];
 };
 };
 
 
+struct nv50_disp_acquire_v0 {
+	__u8  version;
+	__u8  or;
+	__u8  link;
+	__u8  pad03[5];
+};
+
 struct nv50_disp_dac_load_v0 {
 struct nv50_disp_dac_load_v0 {
 	__u8  version;
 	__u8  version;
 	__u8  load;
 	__u8  load;

+ 2 - 1
drivers/gpu/drm/nouveau/nouveau_bios.c

@@ -1533,7 +1533,8 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb,
 	if (conf & 0x100000)
 	if (conf & 0x100000)
 		entry->i2c_upper_default = true;
 		entry->i2c_upper_default = true;
 
 
-	entry->hasht = (entry->location << 4) | entry->type;
+	entry->hasht = (entry->extdev << 8) | (entry->location << 4) |
+			entry->type;
 	entry->hashm = (entry->heads << 8) | (link << 6) | entry->or;
 	entry->hashm = (entry->heads << 8) | (link << 6) | entry->or;
 	return true;
 	return true;
 }
 }

+ 1 - 0
drivers/gpu/drm/nouveau/nouveau_encoder.h

@@ -42,6 +42,7 @@ struct nouveau_encoder {
 
 
 	struct dcb_output *dcb;
 	struct dcb_output *dcb;
 	int or;
 	int or;
+	int link;
 
 
 	struct i2c_adapter *i2c;
 	struct i2c_adapter *i2c;
 	struct nvkm_i2c_aux *aux;
 	struct nvkm_i2c_aux *aux;

+ 69 - 6
drivers/gpu/drm/nouveau/nv50_display.c

@@ -2403,6 +2403,51 @@ out:
 /******************************************************************************
 /******************************************************************************
  * Output path helpers
  * Output path helpers
  *****************************************************************************/
  *****************************************************************************/
+static void
+nv50_outp_release(struct nouveau_encoder *nv_encoder)
+{
+	struct nv50_disp *disp = nv50_disp(nv_encoder->base.base.dev);
+	struct {
+		struct nv50_disp_mthd_v1 base;
+	} args = {
+		.base.version = 1,
+		.base.method = NV50_DISP_MTHD_V1_RELEASE,
+		.base.hasht  = nv_encoder->dcb->hasht,
+		.base.hashm  = nv_encoder->dcb->hashm,
+	};
+
+	nvif_mthd(disp->disp, 0, &args, sizeof(args));
+	nv_encoder->or = -1;
+	nv_encoder->link = 0;
+}
+
+static int
+nv50_outp_acquire(struct nouveau_encoder *nv_encoder)
+{
+	struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
+	struct nv50_disp *disp = nv50_disp(drm->dev);
+	struct {
+		struct nv50_disp_mthd_v1 base;
+		struct nv50_disp_acquire_v0 info;
+	} args = {
+		.base.version = 1,
+		.base.method = NV50_DISP_MTHD_V1_ACQUIRE,
+		.base.hasht  = nv_encoder->dcb->hasht,
+		.base.hashm  = nv_encoder->dcb->hashm,
+	};
+	int ret;
+
+	ret = nvif_mthd(disp->disp, 0, &args, sizeof(args));
+	if (ret) {
+		NV_ERROR(drm, "error acquiring output path: %d\n", ret);
+		return ret;
+	}
+
+	nv_encoder->or = args.info.or;
+	nv_encoder->link = args.info.link;
+	return 0;
+}
+
 static int
 static int
 nv50_outp_atomic_check_view(struct drm_encoder *encoder,
 nv50_outp_atomic_check_view(struct drm_encoder *encoder,
 			    struct drm_crtc_state *crtc_state,
 			    struct drm_crtc_state *crtc_state,
@@ -2482,6 +2527,7 @@ nv50_dac_disable(struct drm_encoder *encoder)
 	}
 	}
 
 
 	nv_encoder->crtc = NULL;
 	nv_encoder->crtc = NULL;
+	nv50_outp_release(nv_encoder);
 }
 }
 
 
 static void
 static void
@@ -2493,6 +2539,8 @@ nv50_dac_enable(struct drm_encoder *encoder)
 	struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
 	struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
 	u32 *push;
 	u32 *push;
 
 
+	nv50_outp_acquire(nv_encoder);
+
 	push = evo_wait(mast, 8);
 	push = evo_wait(mast, 8);
 	if (push) {
 	if (push) {
 		if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
 		if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
@@ -2592,7 +2640,6 @@ nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 	if (!nv_encoder)
 		return -ENOMEM;
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 
 
 	bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
 	bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
 	if (bus)
 	if (bus)
@@ -2759,6 +2806,8 @@ struct nv50_mstm {
 	struct nv50_msto *msto[4];
 	struct nv50_msto *msto[4];
 
 
 	bool modified;
 	bool modified;
+	bool disabled;
+	int links;
 };
 };
 
 
 struct nv50_mstc {
 struct nv50_mstc {
@@ -2907,7 +2956,10 @@ nv50_msto_enable(struct drm_encoder *encoder)
 	r = drm_dp_mst_allocate_vcpi(&mstm->mgr, mstc->port, mstc->pbn, slots);
 	r = drm_dp_mst_allocate_vcpi(&mstm->mgr, mstc->port, mstc->pbn, slots);
 	WARN_ON(!r);
 	WARN_ON(!r);
 
 
-	if (mstm->outp->dcb->sorconf.link & 1)
+	if (!mstm->links++)
+		nv50_outp_acquire(mstm->outp);
+
+	if (mstm->outp->link & 1)
 		proto = 0x8;
 		proto = 0x8;
 	else
 	else
 		proto = 0x9;
 		proto = 0x9;
@@ -2939,6 +2991,8 @@ nv50_msto_disable(struct drm_encoder *encoder)
 
 
 	mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
 	mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
 	mstm->modified = true;
 	mstm->modified = true;
+	if (!--mstm->links)
+		mstm->disabled = true;
 	msto->disabled = true;
 	msto->disabled = true;
 }
 }
 
 
@@ -3154,6 +3208,12 @@ nv50_mstm_prepare(struct nv50_mstm *mstm)
 				nv50_msto_prepare(msto);
 				nv50_msto_prepare(msto);
 		}
 		}
 	}
 	}
+
+	if (mstm->disabled) {
+		if (!mstm->links)
+			nv50_outp_release(mstm->outp);
+		mstm->disabled = false;
+	}
 }
 }
 
 
 static void
 static void
@@ -3452,6 +3512,7 @@ nv50_sor_disable(struct drm_encoder *encoder)
 		nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
 		nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
 		nv50_audio_disable(encoder, nv_crtc);
 		nv50_audio_disable(encoder, nv_crtc);
 		nv50_hdmi_disable(&nv_encoder->base.base, nv_crtc);
 		nv50_hdmi_disable(&nv_encoder->base.base, nv_crtc);
+		nv50_outp_release(nv_encoder);
 	}
 	}
 }
 }
 
 
@@ -3480,10 +3541,11 @@ nv50_sor_enable(struct drm_encoder *encoder)
 
 
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	nv_encoder->crtc = encoder->crtc;
 	nv_encoder->crtc = encoder->crtc;
+	nv50_outp_acquire(nv_encoder);
 
 
 	switch (nv_encoder->dcb->type) {
 	switch (nv_encoder->dcb->type) {
 	case DCB_OUTPUT_TMDS:
 	case DCB_OUTPUT_TMDS:
-		if (nv_encoder->dcb->sorconf.link & 1) {
+		if (nv_encoder->link & 1) {
 			proto = 0x1;
 			proto = 0x1;
 			/* Only enable dual-link if:
 			/* Only enable dual-link if:
 			 *  - Need to (i.e. rate > 165MHz)
 			 *  - Need to (i.e. rate > 165MHz)
@@ -3541,7 +3603,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
 		else
 		else
 			depth = 0x6;
 			depth = 0x6;
 
 
-		if (nv_encoder->dcb->sorconf.link & 1)
+		if (nv_encoder->link & 1)
 			proto = 0x8;
 			proto = 0x8;
 		else
 		else
 			proto = 0x9;
 			proto = 0x9;
@@ -3600,7 +3662,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 	if (!nv_encoder)
 		return -ENOMEM;
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 	nv_encoder->update = nv50_sor_update;
 	nv_encoder->update = nv50_sor_update;
 
 
 	encoder = to_drm_encoder(nv_encoder);
 	encoder = to_drm_encoder(nv_encoder);
@@ -3673,6 +3734,7 @@ nv50_pior_disable(struct drm_encoder *encoder)
 	}
 	}
 
 
 	nv_encoder->crtc = NULL;
 	nv_encoder->crtc = NULL;
+	nv50_outp_release(nv_encoder);
 }
 }
 
 
 static void
 static void
@@ -3687,6 +3749,8 @@ nv50_pior_enable(struct drm_encoder *encoder)
 	u8 proto, depth;
 	u8 proto, depth;
 	u32 *push;
 	u32 *push;
 
 
+	nv50_outp_acquire(nv_encoder);
+
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	nv_connector = nouveau_encoder_connector_get(nv_encoder);
 	switch (nv_connector->base.display_info.bpc) {
 	switch (nv_connector->base.display_info.bpc) {
 	case 10: depth = 0x6; break;
 	case 10: depth = 0x6; break;
@@ -3774,7 +3838,6 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	if (!nv_encoder)
 	if (!nv_encoder)
 		return -ENOMEM;
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
 	nv_encoder->dcb = dcbe;
-	nv_encoder->or = ffs(dcbe->or) - 1;
 	nv_encoder->i2c = ddc;
 	nv_encoder->i2c = ddc;
 	nv_encoder->aux = aux;
 	nv_encoder->aux = aux;
 
 

+ 0 - 5
drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c

@@ -24,7 +24,6 @@
 #include "dp.h"
 #include "dp.h"
 #include "conn.h"
 #include "conn.h"
 #include "ior.h"
 #include "ior.h"
-#include "nv50.h"
 
 
 #include <subdev/bios.h>
 #include <subdev/bios.h>
 #include <subdev/bios/init.h>
 #include <subdev/bios/init.h>
@@ -351,7 +350,6 @@ static const struct dp_rates {
 static int
 static int
 nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
 nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
 {
 {
-	struct nv50_disp *disp = nv50_disp(dp->outp.disp);
 	struct nvkm_ior *ior = dp->outp.ior;
 	struct nvkm_ior *ior = dp->outp.ior;
 	const u8 sink_nr = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT;
 	const u8 sink_nr = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT;
 	const u8 sink_bw = dp->dpcd[DPCD_RC01_MAX_LINK_RATE];
 	const u8 sink_bw = dp->dpcd[DPCD_RC01_MAX_LINK_RATE];
@@ -361,9 +359,6 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
 	int ret = -EINVAL;
 	int ret = -EINVAL;
 	u8  pwr;
 	u8  pwr;
 
 
-	if (!dp->outp.info.location && disp->func->sor.magic)
-		disp->func->sor.magic(&dp->outp);
-
 	/* Find the lowest configuration of the OR that can support
 	/* Find the lowest configuration of the OR that can support
 	 * the required link rate.
 	 * the required link rate.
 	 *
 	 *

+ 1 - 3
drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c

@@ -306,9 +306,6 @@ gf119_disp_intr_unk2_2(struct nv50_disp *disp, int head)
 
 
 		if (nvkm_output_dp_train(outp, pclk))
 		if (nvkm_output_dp_train(outp, pclk))
 			OUTP_ERR(outp, "link not trained before attach");
 			OUTP_ERR(outp, "link not trained before attach");
-	} else {
-		if (disp->func->sor.magic)
-			disp->func->sor.magic(outp);
 	}
 	}
 
 
 	exec_clkcmp(disp, head, 0, pclk, &conf);
 	exec_clkcmp(disp, head, 0, pclk, &conf);
@@ -377,6 +374,7 @@ gf119_disp_super(struct work_struct *work)
 			nvkm_debug(subdev, "supervisor 2.0 - head %d\n", head->id);
 			nvkm_debug(subdev, "supervisor 2.0 - head %d\n", head->id);
 			gf119_disp_intr_unk2_0(disp, head->id);
 			gf119_disp_intr_unk2_0(disp, head->id);
 		}
 		}
+		nvkm_outp_route(&disp->base);
 		list_for_each_entry(head, &disp->base.head, head) {
 		list_for_each_entry(head, &disp->base.head, head) {
 			if (!(mask[head->id] & 0x00010000))
 			if (!(mask[head->id] & 0x00010000))
 				continue;
 				continue;

+ 1 - 3
drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c

@@ -35,9 +35,7 @@ gm200_disp = {
 	.root = &gm200_disp_root_oclass,
 	.root = &gm200_disp_root_oclass,
 	.head.new = gf119_head_new,
 	.head.new = gf119_head_new,
 	.dac = { .nr = 3, .new = gf119_dac_new },
 	.dac = { .nr = 3, .new = gf119_dac_new },
-	.sor.nr = 4,
-	.sor.new = gm200_sor_new,
-	.sor.magic = gm200_sor_magic,
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 };
 
 
 int
 int

+ 1 - 3
drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c

@@ -34,9 +34,7 @@ gp100_disp = {
 	.super = gf119_disp_super,
 	.super = gf119_disp_super,
 	.root = &gp100_disp_root_oclass,
 	.root = &gp100_disp_root_oclass,
 	.head.new = gf119_head_new,
 	.head.new = gf119_head_new,
-	.sor.nr = 4,
-	.sor.new = gm200_sor_new,
-	.sor.magic = gm200_sor_magic,
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 };
 
 
 int
 int

+ 1 - 3
drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c

@@ -60,9 +60,7 @@ gp102_disp = {
 	.super = gf119_disp_super,
 	.super = gf119_disp_super,
 	.root = &gp102_disp_root_oclass,
 	.root = &gp102_disp_root_oclass,
 	.head.new = gf119_head_new,
 	.head.new = gf119_head_new,
-	.sor.nr = 4,
-	.sor.new = gm200_sor_new,
-	.sor.magic = gm200_sor_magic,
+	.sor = { .nr = 4, .new = gm200_sor_new },
 };
 };
 
 
 int
 int

+ 6 - 0
drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h

@@ -17,6 +17,7 @@ struct nvkm_ior {
 	struct list_head head;
 	struct list_head head;
 
 
 	struct nvkm_ior_state {
 	struct nvkm_ior_state {
+		struct nvkm_outp *outp;
 		unsigned rgdiv;
 		unsigned rgdiv;
 		unsigned proto_evo:4;
 		unsigned proto_evo:4;
 		enum nvkm_ior_proto {
 		enum nvkm_ior_proto {
@@ -40,6 +41,11 @@ struct nvkm_ior {
 };
 };
 
 
 struct nvkm_ior_func {
 struct nvkm_ior_func {
+	struct {
+		int (*get)(struct nvkm_outp *, int *link);
+		void (*set)(struct nvkm_outp *, struct nvkm_ior *);
+	} route;
+
 	void (*state)(struct nvkm_ior *, struct nvkm_ior_state *);
 	void (*state)(struct nvkm_ior *, struct nvkm_ior_state *);
 	void (*power)(struct nvkm_ior *, bool normal, bool pu,
 	void (*power)(struct nvkm_ior *, bool normal, bool pu,
 		      bool data, bool vsync, bool hsync);
 		      bool data, bool vsync, bool hsync);

+ 1 - 0
drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c

@@ -661,6 +661,7 @@ nv50_disp_super(struct work_struct *work)
 				continue;
 				continue;
 			nv50_disp_intr_unk20_0(disp, head->id);
 			nv50_disp_intr_unk20_0(disp, head->id);
 		}
 		}
+		nvkm_outp_route(&disp->base);
 		list_for_each_entry(head, &disp->base.head, head) {
 		list_for_each_entry(head, &disp->base.head, head) {
 			if (!(super & (0x00000200 << head->id)))
 			if (!(super & (0x00000200 << head->id)))
 				continue;
 				continue;

+ 0 - 1
drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h

@@ -53,7 +53,6 @@ struct nv50_disp_func {
 	struct {
 	struct {
 		int nr;
 		int nr;
 		int (*new)(struct nvkm_disp *, int id);
 		int (*new)(struct nvkm_disp *, int id);
-		void (*magic)(struct nvkm_output *);
 	} sor;
 	} sor;
 
 
 	struct {
 	struct {

+ 125 - 6
drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c

@@ -28,8 +28,35 @@
 #include <subdev/bios/dcb.h>
 #include <subdev/bios/dcb.h>
 #include <subdev/i2c.h>
 #include <subdev/i2c.h>
 
 
+void
+nvkm_outp_route(struct nvkm_disp *disp)
+{
+	struct nvkm_outp *outp;
+	struct nvkm_ior *ior;
+
+	list_for_each_entry(ior, &disp->ior, head) {
+		if ((outp = ior->arm.outp) && ior->arm.outp != ior->asy.outp) {
+			OUTP_DBG(outp, "release %s", ior->name);
+			if (ior->func->route.set)
+				ior->func->route.set(outp, NULL);
+			ior->arm.outp = NULL;
+		}
+	}
+
+	list_for_each_entry(ior, &disp->ior, head) {
+		if ((outp = ior->asy.outp)) {
+			OUTP_DBG(outp, "acquire %s", ior->name);
+			if (ior->asy.outp != ior->arm.outp) {
+				if (ior->func->route.set)
+					ior->func->route.set(outp, ior);
+				ior->arm.outp = ior->asy.outp;
+			}
+		}
+	}
+}
+
 static enum nvkm_ior_proto
 static enum nvkm_ior_proto
-nvkm_outp_xlat(struct nvkm_output *outp, enum nvkm_ior_type *type)
+nvkm_outp_xlat(struct nvkm_outp *outp, enum nvkm_ior_type *type)
 {
 {
 	switch (outp->info.location) {
 	switch (outp->info.location) {
 	case 0:
 	case 0:
@@ -57,6 +84,75 @@ nvkm_outp_xlat(struct nvkm_output *outp, enum nvkm_ior_type *type)
 	return UNKNOWN;
 	return UNKNOWN;
 }
 }
 
 
+void
+nvkm_outp_release(struct nvkm_outp *outp, u8 user)
+{
+	struct nvkm_ior *ior = outp->ior;
+	OUTP_TRACE(outp, "release %02x &= %02x %p", outp->acquired, ~user, ior);
+	if (ior) {
+		outp->acquired &= ~user;
+		if (!outp->acquired) {
+			outp->ior->asy.outp = NULL;
+			outp->ior = NULL;
+		}
+	}
+}
+
+static inline int
+nvkm_outp_acquire_ior(struct nvkm_outp *outp, u8 user, struct nvkm_ior *ior)
+{
+	outp->ior = ior;
+	outp->ior->asy.outp = outp;
+	outp->ior->asy.link = outp->info.sorconf.link;
+	outp->acquired |= user;
+	return 0;
+}
+
+int
+nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
+{
+	struct nvkm_ior *ior = outp->ior;
+	enum nvkm_ior_proto proto;
+	enum nvkm_ior_type type;
+
+	OUTP_TRACE(outp, "acquire %02x |= %02x %p", outp->acquired, user, ior);
+	if (ior) {
+		outp->acquired |= user;
+		return 0;
+	}
+
+	/* Lookup a compatible, and unused, OR to assign to the device. */
+	proto = nvkm_outp_xlat(outp, &type);
+	if (proto == UNKNOWN)
+		return -ENOSYS;
+
+	/* First preference is to reuse the OR that is currently armed
+	 * on HW, if any, in order to prevent unnecessary switching.
+	 */
+	list_for_each_entry(ior, &outp->disp->ior, head) {
+		if (!ior->asy.outp && ior->arm.outp == outp)
+			return nvkm_outp_acquire_ior(outp, user, ior);
+	}
+
+	/* Failing that, a completely unused OR is the next best thing. */
+	list_for_each_entry(ior, &outp->disp->ior, head) {
+		if (!ior->asy.outp && ior->type == type && !ior->arm.outp &&
+		    ior->id == __ffs(outp->info.or))
+			return nvkm_outp_acquire_ior(outp, user, ior);
+	}
+
+	/* Last resort is to assign an OR that's already active on HW,
+	 * but will be released during the next modeset.
+	 */
+	list_for_each_entry(ior, &outp->disp->ior, head) {
+		if (!ior->asy.outp && ior->type == type &&
+		    ior->id == __ffs(outp->info.or))
+			return nvkm_outp_acquire_ior(outp, user, ior);
+	}
+
+	return -ENOSPC;
+}
+
 void
 void
 nvkm_outp_fini(struct nvkm_outp *outp)
 nvkm_outp_fini(struct nvkm_outp *outp)
 {
 {
@@ -65,22 +161,36 @@ nvkm_outp_fini(struct nvkm_outp *outp)
 }
 }
 
 
 static void
 static void
-nvkm_outp_init_route(struct nvkm_output *outp)
+nvkm_outp_init_route(struct nvkm_outp *outp)
 {
 {
 	struct nvkm_disp *disp = outp->disp;
 	struct nvkm_disp *disp = outp->disp;
 	enum nvkm_ior_proto proto;
 	enum nvkm_ior_proto proto;
 	enum nvkm_ior_type type;
 	enum nvkm_ior_type type;
 	struct nvkm_ior *ior;
 	struct nvkm_ior *ior;
-	int id;
+	int id, link;
 
 
+	/* Find any OR from the class that is able to support this device. */
 	proto = nvkm_outp_xlat(outp, &type);
 	proto = nvkm_outp_xlat(outp, &type);
 	if (proto == UNKNOWN)
 	if (proto == UNKNOWN)
 		return;
 		return;
 
 
+	ior = nvkm_ior_find(disp, type, -1);
+	if (!ior) {
+		WARN_ON(1);
+		return;
+	}
+
 	/* Determine the specific OR, if any, this device is attached to. */
 	/* Determine the specific OR, if any, this device is attached to. */
-	if (1) {
+	if (ior->func->route.get) {
+		id = ior->func->route.get(outp, &link);
+		if (id < 0) {
+			OUTP_DBG(outp, "no route");
+			return;
+		}
+	} else {
 		/* Prior to DCB 4.1, this is hardwired like so. */
 		/* Prior to DCB 4.1, this is hardwired like so. */
-		id = ffs(outp->info.or) - 1;
+		id   = ffs(outp->info.or) - 1;
+		link = (ior->type == SOR) ? outp->info.sorconf.link : 0;
 	}
 	}
 
 
 	ior = nvkm_ior_find(disp, type, id);
 	ior = nvkm_ior_find(disp, type, id);
@@ -89,7 +199,16 @@ nvkm_outp_init_route(struct nvkm_output *outp)
 		return;
 		return;
 	}
 	}
 
 
-	outp->ior = ior;
+	/* Determine if the OR is already configured for this device. */
+	ior->func->state(ior, &ior->arm);
+	if (!ior->arm.head || ior->arm.proto != proto) {
+		OUTP_DBG(outp, "no heads (%x %d %d)", ior->arm.head,
+			 ior->arm.proto, proto);
+		return;
+	}
+
+	OUTP_DBG(outp, "on %s link %x", ior->name, ior->arm.link);
+	ior->arm.outp = outp;
 }
 }
 
 
 void
 void

+ 6 - 2
drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h

@@ -18,6 +18,9 @@ struct nvkm_outp {
 	struct nvkm_conn *conn;
 	struct nvkm_conn *conn;
 
 
 	/* Assembly state. */
 	/* Assembly state. */
+#define NVKM_OUTP_PRIV 1
+#define NVKM_OUTP_USER 2
+	u8 acquired:2;
 	struct nvkm_ior *ior;
 	struct nvkm_ior *ior;
 };
 };
 
 
@@ -28,6 +31,9 @@ int nvkm_outp_new(struct nvkm_disp *, int index, struct dcb_output *,
 void nvkm_outp_del(struct nvkm_outp **);
 void nvkm_outp_del(struct nvkm_outp **);
 void nvkm_outp_init(struct nvkm_outp *);
 void nvkm_outp_init(struct nvkm_outp *);
 void nvkm_outp_fini(struct nvkm_outp *);
 void nvkm_outp_fini(struct nvkm_outp *);
+int nvkm_outp_acquire(struct nvkm_outp *, u8 user);
+void nvkm_outp_release(struct nvkm_outp *, u8 user);
+void nvkm_outp_route(struct nvkm_disp *);
 
 
 struct nvkm_outp_func {
 struct nvkm_outp_func {
 	void *(*dtor)(struct nvkm_outp *);
 	void *(*dtor)(struct nvkm_outp *);
@@ -39,8 +45,6 @@ struct nvkm_outp_func {
 #define nvkm_output_func nvkm_outp_func
 #define nvkm_output_func nvkm_outp_func
 #define nvkm_output_new_ nvkm_outp_new_
 #define nvkm_output_new_ nvkm_outp_new_
 
 
-void gm200_sor_magic(struct nvkm_output *outp);
-
 #define OUTP_MSG(o,l,f,a...) do {                                              \
 #define OUTP_MSG(o,l,f,a...) do {                                              \
 	struct nvkm_outp *_outp = (o);                                         \
 	struct nvkm_outp *_outp = (o);                                         \
 	nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n",    \
 	nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n",    \

+ 22 - 0
drivers/gpu/drm/nouveau/nvkm/engine/disp/rootnv50.c

@@ -94,6 +94,24 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 	}
 	}
 
 
 	switch (mthd * !!outp) {
 	switch (mthd * !!outp) {
+	case NV50_DISP_MTHD_V1_ACQUIRE: {
+		union {
+			struct nv50_disp_acquire_v0 v0;
+		} *args = data;
+		int ret = -ENOSYS;
+		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
+			ret = nvkm_outp_acquire(outp, NVKM_OUTP_USER);
+			if (ret == 0) {
+				args->v0.or = outp->ior->id;
+				args->v0.link = outp->ior->asy.link;
+			}
+		}
+		return ret;
+	}
+		break;
+	case NV50_DISP_MTHD_V1_RELEASE:
+		nvkm_outp_release(outp, NVKM_OUTP_USER);
+		return 0;
 	case NV50_DISP_MTHD_V1_DAC_LOAD: {
 	case NV50_DISP_MTHD_V1_DAC_LOAD: {
 		union {
 		union {
 			struct nv50_disp_dac_load_v0 v0;
 			struct nv50_disp_dac_load_v0 v0;
@@ -102,7 +120,11 @@ nv50_disp_root_mthd_(struct nvkm_object *object, u32 mthd, void *data, u32 size)
 		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
 		if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
 			if (args->v0.data & 0xfff00000)
 			if (args->v0.data & 0xfff00000)
 				return -EINVAL;
 				return -EINVAL;
+			ret = nvkm_outp_acquire(outp, NVKM_OUTP_PRIV);
+			if (ret)
+				return ret;
 			ret = outp->ior->func->sense(outp->ior, args->v0.data);
 			ret = outp->ior->func->sense(outp->ior, args->v0.data);
+			nvkm_outp_release(outp, NVKM_OUTP_PRIV);
 			if (ret < 0)
 			if (ret < 0)
 				return ret;
 				return ret;
 			args->v0.load = ret;
 			args->v0.load = ret;

+ 42 - 7
drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c

@@ -45,20 +45,55 @@ gm200_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
 	nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
 	nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
 }
 }
 
 
-void
-gm200_sor_magic(struct nvkm_output *outp)
+static void
+gm200_sor_route_set(struct nvkm_outp *outp, struct nvkm_ior *ior)
 {
 {
 	struct nvkm_device *device = outp->disp->engine.subdev.device;
 	struct nvkm_device *device = outp->disp->engine.subdev.device;
-	const u32 soff = outp->or * 0x100;
-	const u32 data = outp->or + 1;
-	if (outp->info.sorconf.link & 1)
-		nvkm_mask(device, 0x612308 + soff, 0x0000001f, 0x00000000 | data);
+	const u32 moff = __ffs(outp->info.or) * 0x100;
+	const u32  sor = ior ? ior->id + 1 : 0;
+	u32 link = ior ? (ior->asy.link == 2) : 0;
+
+	if (outp->info.sorconf.link & 1) {
+		nvkm_mask(device, 0x612308 + moff, 0x0000001f, link << 4 | sor);
+		link++;
+	}
+
 	if (outp->info.sorconf.link & 2)
 	if (outp->info.sorconf.link & 2)
-		nvkm_mask(device, 0x612388 + soff, 0x0000001f, 0x00000010 | data);
+		nvkm_mask(device, 0x612388 + moff, 0x0000001f, link << 4 | sor);
+}
+
+static int
+gm200_sor_route_get(struct nvkm_outp *outp, int *link)
+{
+	struct nvkm_device *device = outp->disp->engine.subdev.device;
+	const int sublinks = outp->info.sorconf.link;
+	int lnk[2], sor[2], m, s;
+
+	for (*link = 0, m = __ffs(outp->info.or) * 2, s = 0; s < 2; m++, s++) {
+		if (sublinks & BIT(s)) {
+			u32 data = nvkm_rd32(device, 0x612308 + (m * 0x80));
+			lnk[s] = (data & 0x00000010) >> 4;
+			sor[s] = (data & 0x0000000f);
+			if (!sor[s])
+				return -1;
+			*link |= lnk[s];
+		}
+	}
+
+	if (sublinks == 3) {
+		if (sor[0] != sor[1] || WARN_ON(lnk[0] || !lnk[1]))
+			return -1;
+	}
+
+	return ((sublinks & 1) ? sor[0] : sor[1]) - 1;
 }
 }
 
 
 static const struct nvkm_ior_func
 static const struct nvkm_ior_func
 gm200_sor = {
 gm200_sor = {
+	.route = {
+		.get = gm200_sor_route_get,
+		.set = gm200_sor_route_set,
+	},
 	.state = gf119_sor_state,
 	.state = gf119_sor_state,
 	.power = nv50_sor_power,
 	.power = nv50_sor_power,
 	.hdmi = {
 	.hdmi = {